@livestore/livestore 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  3. package/dist/__tests__/react/fixture.js +8 -9
  4. package/dist/__tests__/react/fixture.js.map +1 -1
  5. package/dist/__tests__/reactive.test.js +3 -4
  6. package/dist/__tests__/reactive.test.js.map +1 -1
  7. package/dist/effect/LiveStore.d.ts +3 -9
  8. package/dist/effect/LiveStore.d.ts.map +1 -1
  9. package/dist/effect/LiveStore.js +11 -7
  10. package/dist/effect/LiveStore.js.map +1 -1
  11. package/dist/inMemoryDatabase.d.ts +15 -19
  12. package/dist/inMemoryDatabase.d.ts.map +1 -1
  13. package/dist/inMemoryDatabase.js +2 -9
  14. package/dist/inMemoryDatabase.js.map +1 -1
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +3 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/migrations.d.ts +7 -0
  20. package/dist/migrations.d.ts.map +1 -1
  21. package/dist/migrations.js +18 -13
  22. package/dist/migrations.js.map +1 -1
  23. package/dist/react/LiveStoreProvider.d.ts +1 -3
  24. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  25. package/dist/react/LiveStoreProvider.js +13 -10
  26. package/dist/react/LiveStoreProvider.js.map +1 -1
  27. package/dist/react/index.d.ts +1 -1
  28. package/dist/react/index.d.ts.map +1 -1
  29. package/dist/react/index.js +1 -1
  30. package/dist/react/index.js.map +1 -1
  31. package/dist/react/useGraphQL.d.ts +3 -1
  32. package/dist/react/useGraphQL.d.ts.map +1 -1
  33. package/dist/react/useGraphQL.js +19 -1
  34. package/dist/react/useGraphQL.js.map +1 -1
  35. package/dist/react/useLiveStoreComponent.d.ts +12 -12
  36. package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
  37. package/dist/react/useLiveStoreComponent.js +23 -7
  38. package/dist/react/useLiveStoreComponent.js.map +1 -1
  39. package/dist/react/useQuery.d.ts +3 -0
  40. package/dist/react/useQuery.d.ts.map +1 -0
  41. package/dist/react/useQuery.js +38 -0
  42. package/dist/react/useQuery.js.map +1 -0
  43. package/dist/reactive.d.ts.map +1 -1
  44. package/dist/reactive.js +3 -3
  45. package/dist/reactive.js.map +1 -1
  46. package/dist/reactiveQueries/base-class.d.ts +6 -3
  47. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  48. package/dist/reactiveQueries/base-class.js +1 -0
  49. package/dist/reactiveQueries/base-class.js.map +1 -1
  50. package/dist/reactiveQueries/graphql.d.ts +4 -4
  51. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  52. package/dist/reactiveQueries/graphql.js.map +1 -1
  53. package/dist/reactiveQueries/js.d.ts +2 -2
  54. package/dist/reactiveQueries/js.d.ts.map +1 -1
  55. package/dist/reactiveQueries/js.js.map +1 -1
  56. package/dist/reactiveQueries/sql.d.ts +5 -5
  57. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  58. package/dist/reactiveQueries/sql.js +2 -2
  59. package/dist/reactiveQueries/sql.js.map +1 -1
  60. package/dist/schema.d.ts +0 -2
  61. package/dist/schema.d.ts.map +1 -1
  62. package/dist/schema.js +3 -6
  63. package/dist/schema.js.map +1 -1
  64. package/dist/storage/in-memory/index.d.ts +2 -2
  65. package/dist/storage/in-memory/index.d.ts.map +1 -1
  66. package/dist/storage/in-memory/index.js.map +1 -1
  67. package/dist/storage/index.d.ts +2 -2
  68. package/dist/storage/index.d.ts.map +1 -1
  69. package/dist/storage/tauri/index.d.ts +2 -2
  70. package/dist/storage/tauri/index.d.ts.map +1 -1
  71. package/dist/storage/tauri/index.js.map +1 -1
  72. package/dist/storage/web-worker/index.d.ts +4 -4
  73. package/dist/storage/web-worker/index.d.ts.map +1 -1
  74. package/dist/storage/web-worker/index.js +3 -5
  75. package/dist/storage/web-worker/index.js.map +1 -1
  76. package/dist/storage/web-worker/worker.js +2 -2
  77. package/dist/storage/web-worker/worker.js.map +1 -1
  78. package/dist/store.d.ts +14 -7
  79. package/dist/store.d.ts.map +1 -1
  80. package/dist/store.js +80 -46
  81. package/dist/store.js.map +1 -1
  82. package/dist/util.d.ts +3 -1
  83. package/dist/util.d.ts.map +1 -1
  84. package/dist/util.js +2 -0
  85. package/dist/util.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/__tests__/react/fixture.tsx +9 -9
  88. package/src/__tests__/reactive.test.ts +3 -4
  89. package/src/effect/LiveStore.ts +14 -18
  90. package/src/inMemoryDatabase.ts +20 -28
  91. package/src/index.ts +10 -4
  92. package/src/migrations.ts +39 -21
  93. package/src/react/LiveStoreProvider.tsx +13 -16
  94. package/src/react/index.ts +1 -1
  95. package/src/react/useGraphQL.ts +28 -2
  96. package/src/react/useLiveStoreComponent.ts +50 -24
  97. package/src/react/useQuery.ts +56 -0
  98. package/src/reactive.ts +6 -4
  99. package/src/reactiveQueries/base-class.ts +9 -3
  100. package/src/reactiveQueries/graphql.ts +4 -4
  101. package/src/reactiveQueries/js.ts +2 -2
  102. package/src/reactiveQueries/sql.ts +6 -6
  103. package/src/schema.ts +2 -5
  104. package/src/storage/in-memory/index.ts +2 -2
  105. package/src/storage/index.ts +2 -2
  106. package/src/storage/tauri/index.ts +2 -2
  107. package/src/storage/web-worker/index.ts +6 -8
  108. package/src/storage/web-worker/worker.ts +2 -2
  109. package/src/store.ts +99 -59
  110. package/src/util.ts +8 -2
  111. package/dist/backends/base.d.ts +0 -13
  112. package/dist/backends/base.d.ts.map +0 -1
  113. package/dist/backends/base.js +0 -53
  114. package/dist/backends/base.js.map +0 -1
  115. package/dist/backends/in-memory/index.d.ts +0 -22
  116. package/dist/backends/in-memory/index.d.ts.map +0 -1
  117. package/dist/backends/in-memory/index.js +0 -45
  118. package/dist/backends/in-memory/index.js.map +0 -1
  119. package/dist/backends/index.d.ts +0 -41
  120. package/dist/backends/index.d.ts.map +0 -1
  121. package/dist/backends/index.js +0 -16
  122. package/dist/backends/index.js.map +0 -1
  123. package/dist/backends/tauri/index.d.ts +0 -21
  124. package/dist/backends/tauri/index.d.ts.map +0 -1
  125. package/dist/backends/tauri/index.js +0 -48
  126. package/dist/backends/tauri/index.js.map +0 -1
  127. package/dist/backends/utils/idb.d.ts +0 -10
  128. package/dist/backends/utils/idb.d.ts.map +0 -1
  129. package/dist/backends/utils/idb.js +0 -58
  130. package/dist/backends/utils/idb.js.map +0 -1
  131. package/dist/backends/web-worker/index.d.ts +0 -26
  132. package/dist/backends/web-worker/index.d.ts.map +0 -1
  133. package/dist/backends/web-worker/index.js +0 -63
  134. package/dist/backends/web-worker/index.js.map +0 -1
  135. package/dist/backends/web-worker/worker.d.ts +0 -17
  136. package/dist/backends/web-worker/worker.d.ts.map +0 -1
  137. package/dist/backends/web-worker/worker.js +0 -139
  138. package/dist/backends/web-worker/worker.js.map +0 -1
  139. package/dist/react/useGlobalQuery.d.ts +0 -3
  140. package/dist/react/useGlobalQuery.d.ts.map +0 -1
  141. package/dist/react/useGlobalQuery.js +0 -23
  142. package/dist/react/useGlobalQuery.js.map +0 -1
  143. package/dist/storage/base.d.ts +0 -10
  144. package/dist/storage/base.d.ts.map +0 -1
  145. package/dist/storage/base.js +0 -14
  146. package/dist/storage/base.js.map +0 -1
  147. package/src/react/useGlobalQuery.ts +0 -37
@@ -5,13 +5,13 @@ import type { Store } from '../store.js'
5
5
 
6
6
  export type UnsubscribeQuery = () => void
7
7
 
8
- export abstract class LiveStoreQueryBase {
8
+ export abstract class LiveStoreQueryBase<TResult> {
9
9
  /** The key for the associated component */
10
10
  componentKey: ComponentKey
11
11
  /** Human-readable label for the query for debugging */
12
12
  label: string
13
13
  /** A pointer back to the store containing this query */
14
- store: Store<any>
14
+ store: Store
15
15
  /** Otel Span is started in LiveStore store but ended in this query */
16
16
  otelContext: otel.Context
17
17
 
@@ -26,7 +26,7 @@ export abstract class LiveStoreQueryBase {
26
26
  }: {
27
27
  componentKey: ComponentKey
28
28
  label: string
29
- store: Store<any>
29
+ store: Store
30
30
  otelContext: otel.Context
31
31
  }) {
32
32
  this.componentKey = componentKey
@@ -46,4 +46,10 @@ export abstract class LiveStoreQueryBase {
46
46
  unsubscribe()
47
47
  }
48
48
  }
49
+
50
+ subscribe = (
51
+ onNewValue: (value: TResult) => void,
52
+ onSubsubscribe?: () => void,
53
+ options?: { label?: string } | undefined,
54
+ ): (() => void) => this.store.subscribe(this as any, onNewValue as any, onSubsubscribe, options)
49
55
  }
@@ -2,8 +2,8 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
2
2
  import type * as otel from '@opentelemetry/api'
3
3
 
4
4
  import type { ComponentKey } from '../componentKey.js'
5
- import type { GetAtom, Thunk } from '../reactive.js'
6
- import type { BaseGraphQLContext, Store } from '../store.js'
5
+ import type { Thunk } from '../reactive.js'
6
+ import type { BaseGraphQLContext, GetAtomResult, Store } from '../store.js'
7
7
  import { LiveStoreQueryBase } from './base-class.js'
8
8
  import type { LiveStoreJSQuery } from './js.js'
9
9
 
@@ -11,7 +11,7 @@ export class LiveStoreGraphQLQuery<
11
11
  TResult extends Record<string, any>,
12
12
  VariableValues extends Record<string, any>,
13
13
  TContext extends BaseGraphQLContext,
14
- > extends LiveStoreQueryBase {
14
+ > extends LiveStoreQueryBase<TResult> {
15
15
  _tag: 'graphql' = 'graphql'
16
16
 
17
17
  /** The abstract GraphQL query */
@@ -39,7 +39,7 @@ export class LiveStoreGraphQLQuery<
39
39
  this.results$ = results$
40
40
  }
41
41
 
42
- pipe = <U>(f: (x: TResult, get: GetAtom) => U): LiveStoreJSQuery<U> =>
42
+ pipe = <U>(f: (x: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
43
43
  this.store.queryJS(
44
44
  (get) => {
45
45
  const results = get(this.results$)
@@ -5,7 +5,7 @@ import type { GetAtom, Thunk } from '../reactive.js'
5
5
  import type { Store } from '../store.js'
6
6
  import { LiveStoreQueryBase } from './base-class.js'
7
7
 
8
- export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase {
8
+ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
9
9
  _tag: 'js' = 'js'
10
10
  /** A reactive thunk representing the query results */
11
11
  results$: Thunk<TResult>
@@ -17,7 +17,7 @@ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase {
17
17
  results$: Thunk<TResult>
18
18
  componentKey: ComponentKey
19
19
  label: string
20
- store: Store<any>
20
+ store: Store
21
21
  otelContext: otel.Context
22
22
  }) {
23
23
  super(baseProps)
@@ -7,12 +7,12 @@ import { LiveStoreQueryBase } from './base-class.js'
7
7
  import type { LiveStoreJSQuery } from './js.js'
8
8
 
9
9
  /* An object encapsulating a reactive SQL query */
10
- export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
10
+ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase<Row> {
11
11
  _tag: 'sql' = 'sql'
12
12
  /** A reactive thunk representing the query text */
13
13
  queryString$: Thunk<string>
14
14
  /** A reactive thunk representing the query results */
15
- results$: Thunk<Row[]>
15
+ results$: Thunk<ReadonlyArray<Row>>
16
16
 
17
17
  constructor({
18
18
  queryString$,
@@ -20,10 +20,10 @@ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
20
20
  ...baseProps
21
21
  }: {
22
22
  queryString$: Thunk<string>
23
- results$: Thunk<Row[]>
23
+ results$: Thunk<ReadonlyArray<Row>>
24
24
  componentKey: ComponentKey
25
25
  label: string
26
- store: Store<any>
26
+ store: Store
27
27
  otelContext: otel.Context
28
28
  }) {
29
29
  super(baseProps)
@@ -36,11 +36,11 @@ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
36
36
  * Returns a new reactive query that contains the result of
37
37
  * running an arbitrary JS computation on the results of this SQL query.
38
38
  */
39
- pipe = <U>(f: (result: Row[], get: GetAtom) => U): LiveStoreJSQuery<U> =>
39
+ pipe = <U>(fn: (result: ReadonlyArray<Row>, get: GetAtom) => U): LiveStoreJSQuery<U> =>
40
40
  this.store.queryJS(
41
41
  (get) => {
42
42
  const results = get(this.results$)
43
- return f(results, get)
43
+ return fn(results, get)
44
44
  },
45
45
  {
46
46
  componentKey: this.componentKey,
package/src/schema.ts CHANGED
@@ -38,6 +38,7 @@ export type ComponentStateSchema = SqliteDsl.TableDefinition<any, any> & {
38
38
 
39
39
  // TODO get rid of "side effect" in this function (via explicit register fn)
40
40
  export const defineComponentStateSchema = <TName extends string, TColumns extends SqliteDsl.Columns>(
41
+ // TODO get rid of the `name` param here and use the `componentKey` name instead
41
42
  name: TName,
42
43
  columns: TColumns,
43
44
  ): SqliteDsl.TableDefinition<
@@ -92,7 +93,6 @@ export type ActionDefinitions<TArgsMap extends Record<string, any>> = {
92
93
  [key in keyof TArgsMap]: ActionDefinition<TArgsMap[key]>
93
94
  }
94
95
 
95
- export const EVENT_CURSOR_TABLE = '__livestore_event_cursor'
96
96
  export const SCHEMA_META_TABLE = '__livestore_schema'
97
97
 
98
98
  const schemaMetaTable = SqliteDsl.table(SCHEMA_META_TABLE, {
@@ -110,10 +110,6 @@ export const systemTables = {
110
110
  // type: SqliteDsl.text({ nullable: false }),
111
111
  // args: SqliteDsl.text({ nullable: false }),
112
112
  // }).ast,
113
- [EVENT_CURSOR_TABLE]: SqliteDsl.table(EVENT_CURSOR_TABLE, {
114
- id: SqliteDsl.text({ primaryKey: true }),
115
- cursor: SqliteDsl.text({ nullable: false }),
116
- }).ast,
117
113
  [SCHEMA_META_TABLE]: schemaMetaTable.ast,
118
114
  } satisfies TableDefinitions
119
115
 
@@ -138,6 +134,7 @@ type RecordValues<T> = T extends Record<string, infer V> ? V : never
138
134
 
139
135
  export type GetActionArgs<A> = A extends ActionDefinition<infer TArgs> ? TArgs : never
140
136
 
137
+ // TODO get rid of this
141
138
  declare global {
142
139
  // NOTE Can be extended
143
140
  interface LiveStoreActionDefinitionsTypes {
@@ -1,6 +1,6 @@
1
1
  import type * as otel from '@opentelemetry/api'
2
2
 
3
- import type { ParamsObject } from '../../util.js'
3
+ import type { PreparedBindValues } from '../../util.js'
4
4
  import type { Storage, StorageOtelProps } from '../index.js'
5
5
 
6
6
  export type StorageOptionsWebInMemory = {
@@ -15,7 +15,7 @@ export class InMemoryStorage implements Storage {
15
15
  return ({ otelTracer }: StorageOtelProps) => new InMemoryStorage(otelTracer)
16
16
  }
17
17
 
18
- execute = (_query: string, _bindValues?: ParamsObject): void => {}
18
+ execute = (_query: string, _bindValues?: PreparedBindValues): void => {}
19
19
 
20
20
  getPersistedData = async (): Promise<Uint8Array> => new Uint8Array()
21
21
  }
@@ -8,12 +8,12 @@
8
8
 
9
9
  import type * as otel from '@opentelemetry/api'
10
10
 
11
- import type { ParamsObject } from '../util.js'
11
+ import type { PreparedBindValues } from '../util.js'
12
12
 
13
13
  export type StorageInit = (otelProps: StorageOtelProps) => Promise<Storage> | Storage
14
14
 
15
15
  export interface Storage {
16
- execute(query: string, bindValues?: ParamsObject, parentSpan?: otel.Span): void
16
+ execute(query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void
17
17
 
18
18
  /** Return a snapshot of persisted data from the storage */
19
19
  getPersistedData(parentSpan?: otel.Span): Promise<Uint8Array>
@@ -2,7 +2,7 @@ import { getTraceParentHeader } from '@livestore/utils'
2
2
  import type * as otel from '@opentelemetry/api'
3
3
  import { invoke } from '@tauri-apps/api'
4
4
 
5
- import type { ParamsObject } from '../../util.js'
5
+ import type { PreparedBindValues } from '../../util.js'
6
6
  import { prepareBindValues } from '../../util.js'
7
7
  import type { Storage, StorageOtelProps } from '../index.js'
8
8
 
@@ -28,7 +28,7 @@ export class TauriStorage implements Storage {
28
28
  return new TauriStorage(dbFilePath, dbDirPath, otelTracer, parentSpan)
29
29
  }
30
30
 
31
- execute = (query: string, bindValues?: ParamsObject, parentSpan?: otel.Span): void => {
31
+ execute = (query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void => {
32
32
  // console.log({ query, bindValues, prepared: prepareBindValues(bindValues ?? {}, query) })
33
33
  void invoke('execute', {
34
34
  dbName: this.dbFilePath,
@@ -2,8 +2,7 @@ import { casesHandled } from '@livestore/utils'
2
2
  import type * as otel from '@opentelemetry/api'
3
3
  import * as Comlink from 'comlink'
4
4
 
5
- import type { ParamsObject } from '../../util.js'
6
- import { prepareBindValues } from '../../util.js'
5
+ import type { PreparedBindValues } from '../../util.js'
7
6
  import type { Storage, StorageOtelProps } from '../index.js'
8
7
  import { IDB } from '../utils/idb.js'
9
8
  import type { WrappedWorker } from './worker.js'
@@ -13,7 +12,7 @@ export type StorageType = 'opfs' | 'indexeddb'
13
12
  export type StorageOptionsWeb = {
14
13
  /** Specifies where to persist data for this storage */
15
14
  type: StorageType
16
- virtualFilename: string
15
+ fileName: string
17
16
  }
18
17
 
19
18
  export class WebWorkerStorage implements Storage {
@@ -21,7 +20,7 @@ export class WebWorkerStorage implements Storage {
21
20
  options: StorageOptionsWeb
22
21
  otelTracer: otel.Tracer
23
22
 
24
- executionBacklog: { query: string; bindValues?: ParamsObject }[] = []
23
+ executionBacklog: { query: string; bindValues?: PreparedBindValues }[] = []
25
24
  executionPromise: Promise<void> | undefined
26
25
 
27
26
  private constructor({
@@ -61,8 +60,7 @@ export class WebWorkerStorage implements Storage {
61
60
  })
62
61
  }
63
62
 
64
- execute = (query: string, bindValues_?: ParamsObject) => {
65
- const bindValues = prepareBindValues(bindValues_ ?? {}, query)
63
+ execute = (query: string, bindValues?: PreparedBindValues) => {
66
64
  this.executionBacklog.push({ query, bindValues })
67
65
 
68
66
  // Instead of sending the queries to the worker immediately, we wait a bit and batch them up (which reduces the number of messages sent to the worker)
@@ -91,7 +89,7 @@ const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array>
91
89
  case 'opfs': {
92
90
  try {
93
91
  const rootHandle = await navigator.storage.getDirectory()
94
- const fileHandle = await rootHandle.getFileHandle(options.virtualFilename + '.db')
92
+ const fileHandle = await rootHandle.getFileHandle(options.fileName + '.db')
95
93
  const file = await fileHandle.getFile()
96
94
  const buffer = await file.arrayBuffer()
97
95
  const data = new Uint8Array(buffer)
@@ -107,7 +105,7 @@ const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array>
107
105
  }
108
106
 
109
107
  case 'indexeddb': {
110
- const idb = new IDB(options.virtualFilename)
108
+ const idb = new IDB(options.fileName)
111
109
 
112
110
  return (await idb.get('db')) ?? new Uint8Array()
113
111
  }
@@ -46,7 +46,7 @@ const initialize = async (options: StorageOptionsWeb) => {
46
46
  switch (options.type) {
47
47
  case 'opfs': {
48
48
  try {
49
- db = new sqlite3.oo1.OpfsDb(fullyQualifiedFilename(options.virtualFilename)) // , 'c'
49
+ db = new sqlite3.oo1.OpfsDb(fullyQualifiedFilename(options.fileName)) // , 'c'
50
50
  } catch (e) {
51
51
  debugger
52
52
  }
@@ -55,7 +55,7 @@ const initialize = async (options: StorageOptionsWeb) => {
55
55
  case 'indexeddb': {
56
56
  try {
57
57
  db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' })
58
- idb = new IDB(options.virtualFilename)
58
+ idb = new IDB(options.fileName)
59
59
 
60
60
  const bytes = await idb.get('db')
61
61
 
package/src/store.ts CHANGED
@@ -6,16 +6,17 @@ import type { GraphQLSchema } from 'graphql'
6
6
  import * as graphql from 'graphql'
7
7
  import { uniqueId } from 'lodash-es'
8
8
  import * as ReactDOM from 'react-dom'
9
- import initSqlite3Wasm from 'sqlite-esm'
9
+ import type * as Sqlite from 'sqlite-esm'
10
10
  import { v4 as uuid } from 'uuid'
11
11
 
12
12
  import type { ComponentKey } from './componentKey.js'
13
13
  import { tableNameForComponentKey } from './componentKey.js'
14
+ import type { QueryDefinition } from './effect/LiveStore.js'
14
15
  import type { LiveStoreEvent } from './events.js'
15
16
  import { InMemoryDatabase } from './inMemoryDatabase.js'
16
17
  import { migrateDb } from './migrations.js'
17
18
  import { getDurationMsFromSpan } from './otel.js'
18
- import type { GetAtom, Ref } from './reactive.js'
19
+ import type { Atom, Ref } from './reactive.js'
19
20
  import { ReactiveGraph } from './reactive.js'
20
21
  import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
21
22
  import { LiveStoreJSQuery } from './reactiveQueries/js.js'
@@ -24,7 +25,9 @@ import type { ActionDefinition, GetActionArgs, Schema, SQLWriteStatement } from
24
25
  import { componentStateTables } from './schema.js'
25
26
  import type { Storage, StorageInit } from './storage/index.js'
26
27
  import type { Bindable, ParamsObject } from './util.js'
27
- import { isPromise, sql } from './util.js'
28
+ import { isPromise, prepareBindValues, sql } from './util.js'
29
+
30
+ export type GetAtomResult = <T>(atom: Atom<T> | LiveStoreJSQuery<T>) => T
28
31
 
29
32
  export type LiveStoreQuery<TResult extends Record<string, any> = any> =
30
33
  | LiveStoreSQLQuery<TResult>
@@ -37,8 +40,6 @@ export type BaseGraphQLContext = {
37
40
  otelContext?: otel.Context
38
41
  }
39
42
 
40
- export const RESET_DB_LOCAL_STORAGE_KEY = 'livestore-reset'
41
-
42
43
  export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
43
44
  ? ReadonlyArray<Readonly<R>>
44
45
  : TQuery extends LiveStoreJSQuery<infer S>
@@ -56,6 +57,8 @@ export type GraphQLOptions<TContext> = {
56
57
 
57
58
  export type StoreOptions<TGraphQLContext extends BaseGraphQLContext> = {
58
59
  db: InMemoryDatabase
60
+ /** A `Proxy`d version of `db` except that it also mirrors `execute` calls to the storage */
61
+ dbProxy: InMemoryDatabase
59
62
  schema: Schema
60
63
  storage?: Storage
61
64
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
@@ -100,9 +103,11 @@ export type StoreOtel = {
100
103
  queriesSpanContext: otel.Context
101
104
  }
102
105
 
103
- export class Store<TGraphQLContext extends BaseGraphQLContext> {
106
+ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext> {
104
107
  graph: ReactiveGraph<RefreshReason, QueryDebugInfo>
105
108
  inMemoryDB: InMemoryDatabase
109
+ // TODO refactor
110
+ _proxyDb: InMemoryDatabase
106
111
  schema: Schema
107
112
  graphQLSchema?: GraphQLSchema
108
113
  graphQLContext?: TGraphQLContext
@@ -118,6 +123,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
118
123
 
119
124
  private constructor({
120
125
  db,
126
+ dbProxy,
121
127
  schema,
122
128
  storage,
123
129
  graphQLOptions,
@@ -125,6 +131,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
125
131
  otelRootSpanContext,
126
132
  }: StoreOptions<TGraphQLContext>) {
127
133
  this.inMemoryDB = db
134
+ this._proxyDb = dbProxy
128
135
  this.graph = new ReactiveGraph({
129
136
  // TODO move this into React module
130
137
  // Do all our updates inside a single React setState batch to avoid multiple UI re-renders
@@ -188,7 +195,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
188
195
  * NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
189
196
  */
190
197
  querySQL = <TResult>(
191
- genQueryString: (get: GetAtom) => string,
198
+ genQueryString: string | ((get: GetAtomResult) => string),
192
199
  {
193
200
  queriedTables,
194
201
  bindValues,
@@ -218,9 +225,17 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
218
225
 
219
226
  const queryString$ = this.graph.makeThunk(
220
227
  (get, addDebugInfo) => {
221
- const queryString = genQueryString(get)
222
- addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
223
- return queryString
228
+ if (typeof genQueryString === 'function') {
229
+ const getAtom: GetAtomResult = (atom) => {
230
+ if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom)
231
+ return get(atom.results$)
232
+ }
233
+ const queryString = genQueryString(getAtom)
234
+ addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
235
+ return queryString
236
+ } else {
237
+ return genQueryString
238
+ }
224
239
  },
225
240
  { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
226
241
  otelContext,
@@ -231,10 +246,10 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
231
246
 
232
247
  const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
233
248
 
234
- const results$ = this.graph.makeThunk<TResult[]>(
249
+ const results$ = this.graph.makeThunk<ReadonlyArray<TResult>>(
235
250
  (get, addDebugInfo) =>
236
251
  this.otel.tracer.startActiveSpan(
237
- 'sql', // NOTE span name will be overridden further down
252
+ 'sql:', // NOTE span name will be overridden further down
238
253
  {},
239
254
  otelContext,
240
255
  (span) => {
@@ -252,12 +267,16 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
252
267
  span.setAttribute('sql.query', sqlString)
253
268
  span.updateName(`sql:${sqlString.slice(0, 50)}`)
254
269
 
255
- const results = this.inMemoryDB.select(sqlString, { queriedTables, bindValues, otelContext })
270
+ const results = this.inMemoryDB.select<TResult>(sqlString, {
271
+ queriedTables,
272
+ bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
273
+ otelContext,
274
+ })
256
275
 
257
276
  span.setAttribute('sql.rowsCount', results.length)
258
277
  addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
259
278
 
260
- return results as unknown as TResult[]
279
+ return results
261
280
  } finally {
262
281
  span.end()
263
282
  }
@@ -289,7 +308,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
289
308
  )
290
309
 
291
310
  queryJS = <TResult>(
292
- genResults: (get: GetAtom) => TResult,
311
+ genResults: (get: GetAtomResult) => TResult,
293
312
  {
294
313
  componentKey = globalComponentKey,
295
314
  label = `js${uniqueId()}`,
@@ -301,8 +320,12 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
301
320
  const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
302
321
  const results$ = this.graph.makeThunk(
303
322
  (get, addDebugInfo) => {
323
+ const getAtom: GetAtomResult = (atom) => {
324
+ if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom)
325
+ return get(atom.results$)
326
+ }
304
327
  addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
305
- return genResults(get)
328
+ return genResults(getAtom)
306
329
  },
307
330
  { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
308
331
  otelContext,
@@ -329,7 +352,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
329
352
 
330
353
  queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
331
354
  document: DocumentNode<TResult, TVariableValues>,
332
- genVariableValues: (get: GetAtom) => TVariableValues,
355
+ genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
333
356
  {
334
357
  componentKey,
335
358
  label,
@@ -356,7 +379,17 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
356
379
  span.updateName(`queryGraphQL:${labelWithDefault}`)
357
380
 
358
381
  const variableValues$ = this.graph.makeThunk(
359
- genVariableValues,
382
+ (get) => {
383
+ if (typeof genVariableValues === 'function') {
384
+ const getAtom: GetAtomResult = (atom) => {
385
+ if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom)
386
+ return get(atom.results$)
387
+ }
388
+ return genVariableValues(getAtom)
389
+ } else {
390
+ return genVariableValues
391
+ }
392
+ },
360
393
  { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
361
394
  otelContext,
362
395
  )
@@ -720,6 +753,13 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
720
753
  )
721
754
  }
722
755
 
756
+ // TODO get rid of this as part of new query definition approach https://www.notion.so/schickling/New-query-definition-approach-1097a78ef0e9495bac25f90417374756?pvs=4
757
+ runOnce = <TQueryDef extends QueryDefinition>(queryDef: TQueryDef): QueryResult<ReturnType<TQueryDef>> => {
758
+ return this.inTempQueryContext(() => {
759
+ return queryDef(this).results$.result
760
+ })
761
+ }
762
+
723
763
  /**
724
764
  * Apply an event to the store.
725
765
  * Returns the tables that were affected by the event.
@@ -778,14 +818,19 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
778
818
  // Synchronously apply the event to the in-memory database
779
819
  // const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
780
820
  const { statement, bindValues } = eventToSql(eventWithId, actionDefinition)
781
- const { durationMs } = this.inMemoryDB.execute(statement.sql, bindValues, statement.writeTables, {
782
- otelContext,
783
- })
821
+ const { durationMs } = this.inMemoryDB.execute(
822
+ statement.sql,
823
+ prepareBindValues(bindValues, statement.sql),
824
+ statement.writeTables,
825
+ {
826
+ otelContext,
827
+ },
828
+ )
784
829
 
785
830
  // Asynchronously apply the event to a persistent storage (we're not awaiting this promise here)
786
831
  if (this.storage !== undefined) {
787
832
  // this.storage.applyEvent(eventWithId, actionDefinition, span)
788
- this.storage.execute(statement.sql, bindValues, span)
833
+ this.storage.execute(statement.sql, prepareBindValues(bindValues, statement.sql), span)
789
834
  }
790
835
 
791
836
  // Uncomment to print a list of queries currently registered on the store
@@ -809,11 +854,11 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
809
854
  * all app writes should go through applyEvent.
810
855
  */
811
856
  execute = async (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
812
- this.inMemoryDB.execute(query, params, writeTables)
857
+ this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables)
813
858
 
814
859
  if (this.storage !== undefined) {
815
860
  const parentSpan = otel.trace.getSpan(otel.context.active())
816
- this.storage.execute(query, params, parentSpan)
861
+ this.storage.execute(query, prepareBindValues(params, query), parentSpan)
817
862
  }
818
863
  }
819
864
  }
@@ -826,6 +871,7 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
826
871
  otelTracer = makeNoopTracer(),
827
872
  otelRootSpanContext = otel.context.active(),
828
873
  boot,
874
+ sqlite3,
829
875
  }: {
830
876
  schema: Schema
831
877
  loadStorage: () => StorageInit | Promise<StorageInit>
@@ -833,50 +879,36 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
833
879
  otelTracer?: otel.Tracer
834
880
  otelRootSpanContext?: otel.Context
835
881
  boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
882
+ sqlite3: Sqlite.Sqlite3Static
836
883
  }): Promise<Store<TGraphQLContext>> => {
837
884
  return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
838
885
  try {
839
886
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
840
887
 
841
- const loadStorageAndPersistedData = async () => {
842
- const storage = await otelTracer.startActiveSpan('storage:load', {}, otelContext, async (span) => {
888
+ const storage = await otelTracer.startActiveSpan('storage:load', {}, otelContext, async (span) => {
889
+ try {
890
+ const init = await loadStorage()
891
+ const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
892
+ return init({ otelTracer, parentSpan })
893
+ } finally {
894
+ span.end()
895
+ }
896
+ })
897
+
898
+ const persistedData = await otelTracer.startActiveSpan(
899
+ 'storage:getPersistedData',
900
+ {},
901
+ otelContext,
902
+ async (span) => {
843
903
  try {
844
- const init = await loadStorage()
845
- const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
846
- return init({ otelTracer, parentSpan })
904
+ return await storage.getPersistedData(span)
847
905
  } finally {
848
906
  span.end()
849
907
  }
850
- })
851
-
852
- const persistedData = await otelTracer.startActiveSpan(
853
- 'storage:getPersistedData',
854
- {},
855
- otelContext,
856
- async (span) => {
857
- try {
858
- return await storage.getPersistedData(span)
859
- } finally {
860
- span.end()
861
- }
862
- },
863
- )
864
-
865
- return { storage, persistedData }
866
- }
867
-
868
- const loadSqlite3 = () =>
869
- initSqlite3Wasm({
870
- // Required to load the wasm binary asynchronously. Of course, you can host it wherever you want
871
- // You can omit locateFile completely when running in node
872
- // locateFile: () => `/sql-wasm.wasm`,
873
- print: (message) => console.log(`[livestore sqlite] ${message}`),
874
- printErr: (message) => console.error(`[livestore sqlite] ${message}`),
875
- })
876
-
877
- const [{ storage, persistedData }, sqlite3] = await Promise.all([loadStorageAndPersistedData(), loadSqlite3()])
908
+ },
909
+ )
878
910
 
879
- const db = InMemoryDatabase.load(persistedData, otelTracer, otelRootSpanContext, sqlite3)
911
+ const db = InMemoryDatabase.load({ data: persistedData, otelTracer, otelRootSpanContext, sqlite3 })
880
912
 
881
913
  // Proxy to `db` that also mirrors `execute` calls to `storage`
882
914
  const dbProxy = new Proxy(db, {
@@ -887,6 +919,14 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
887
919
  return db.execute(query, bindValues, writeTables, options)
888
920
  }
889
921
  return execute
922
+ } else if (prop === 'select') {
923
+ // NOTE we're even proxying `select` calls here as some apps (e.g. Overtone) currently rely on this
924
+ // TODO remove this once we've migrated all apps to use `execute` instead of `select`
925
+ const select: InMemoryDatabase['select'] = (query, options = {}) => {
926
+ storage.execute(query, options.bindValues as any)
927
+ return db.select(query, options)
928
+ }
929
+ return select
890
930
  } else {
891
931
  return Reflect.get(db, prop, receiver)
892
932
  }
@@ -914,7 +954,7 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
914
954
  // Think about what to do about this case.
915
955
  // await applySchema(db, schema)
916
956
  return Store.createStore<TGraphQLContext>(
917
- { db, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
957
+ { db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
918
958
  span,
919
959
  )
920
960
  } finally {
package/src/util.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  /// <reference lib="es2022" />
2
2
 
3
+ import type { Brand } from '@livestore/utils/effect'
4
+
3
5
  export type ParamsObject = Record<string, SqlValue>
4
6
  export type SqlValue = string | number | Uint8Array | null
5
7
 
6
8
  export type Bindable = SqlValue[] | ParamsObject
7
9
 
10
+ export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
11
+
8
12
  /**
9
13
  * This is a tag function for tagged literals.
10
14
  * it lets us get syntax highlighting on SQL queries in VSCode, but
@@ -25,7 +29,9 @@ export const sql = (template: TemplateStringsArray, ...args: unknown[]): string
25
29
  /* because rusqlite doesn't allow unused named params
26
30
  /* TODO: Search for unused params via proper parsing, not string search
27
31
  **/
28
- export const prepareBindValues = (values: ParamsObject, statement: string): ParamsObject => {
32
+ export const prepareBindValues = (values: Bindable, statement: string): PreparedBindValues => {
33
+ if (Array.isArray(values)) return values as PreparedBindValues
34
+
29
35
  const result: ParamsObject = {}
30
36
  for (const [key, value] of Object.entries(values)) {
31
37
  if (statement.includes(key)) {
@@ -33,7 +39,7 @@ export const prepareBindValues = (values: ParamsObject, statement: string): Para
33
39
  }
34
40
  }
35
41
 
36
- return result
42
+ return result as PreparedBindValues
37
43
  }
38
44
 
39
45
  /**