@livestore/livestore 0.0.13 → 0.0.14

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 (205) hide show
  1. package/README.md +18 -21
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.d.ts +1 -1
  4. package/dist/QueryCache.d.ts.map +1 -1
  5. package/dist/QueryCache.js.map +1 -1
  6. package/dist/__tests__/react/fixture.d.ts +5 -4
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +13 -14
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
  11. package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
  12. package/dist/__tests__/react/useComponentState.test.js +68 -0
  13. package/dist/__tests__/react/useComponentState.test.js.map +1 -0
  14. package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
  15. package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
  16. package/dist/__tests__/react/useLQuery.test.js +38 -0
  17. package/dist/__tests__/react/useLQuery.test.js.map +1 -0
  18. package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
  19. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
  20. package/dist/__tests__/react/useQuery.test.d.ts +2 -0
  21. package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
  22. package/dist/__tests__/react/useQuery.test.js +33 -0
  23. package/dist/__tests__/react/useQuery.test.js.map +1 -0
  24. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
  25. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
  26. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
  27. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
  28. package/dist/__tests__/reactive.test.js +168 -95
  29. package/dist/__tests__/reactive.test.js.map +1 -1
  30. package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
  31. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
  32. package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
  33. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
  34. package/dist/effect/LiveStore.d.ts +3 -9
  35. package/dist/effect/LiveStore.d.ts.map +1 -1
  36. package/dist/effect/LiveStore.js +11 -7
  37. package/dist/effect/LiveStore.js.map +1 -1
  38. package/dist/inMemoryDatabase.d.ts +17 -21
  39. package/dist/inMemoryDatabase.d.ts.map +1 -1
  40. package/dist/inMemoryDatabase.js +2 -9
  41. package/dist/inMemoryDatabase.js.map +1 -1
  42. package/dist/index.d.ts +9 -7
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +7 -3
  45. package/dist/index.js.map +1 -1
  46. package/dist/migrations.d.ts +7 -0
  47. package/dist/migrations.d.ts.map +1 -1
  48. package/dist/migrations.js +18 -13
  49. package/dist/migrations.js.map +1 -1
  50. package/dist/react/LiveStoreProvider.d.ts +1 -3
  51. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  52. package/dist/react/LiveStoreProvider.js +13 -10
  53. package/dist/react/LiveStoreProvider.js.map +1 -1
  54. package/dist/react/index.d.ts +4 -4
  55. package/dist/react/index.d.ts.map +1 -1
  56. package/dist/react/index.js +3 -3
  57. package/dist/react/index.js.map +1 -1
  58. package/dist/react/useComponentState.d.ts +50 -0
  59. package/dist/react/useComponentState.d.ts.map +1 -0
  60. package/dist/react/useComponentState.js +248 -0
  61. package/dist/react/useComponentState.js.map +1 -0
  62. package/dist/react/useGlobalQuery.d.ts +2 -2
  63. package/dist/react/useGlobalQuery.d.ts.map +1 -1
  64. package/dist/react/useGlobalQuery.js +5 -2
  65. package/dist/react/useGlobalQuery.js.map +1 -1
  66. package/dist/react/useGraphQL.d.ts +5 -3
  67. package/dist/react/useGraphQL.d.ts.map +1 -1
  68. package/dist/react/useGraphQL.js +27 -7
  69. package/dist/react/useGraphQL.js.map +1 -1
  70. package/dist/react/useLiveStoreComponent.d.ts +14 -14
  71. package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
  72. package/dist/react/useLiveStoreComponent.js +151 -91
  73. package/dist/react/useLiveStoreComponent.js.map +1 -1
  74. package/dist/react/useQuery.d.ts +3 -0
  75. package/dist/react/useQuery.d.ts.map +1 -0
  76. package/dist/react/useQuery.js +42 -0
  77. package/dist/react/useQuery.js.map +1 -0
  78. package/dist/react/useTemporaryQuery.d.ts +8 -0
  79. package/dist/react/useTemporaryQuery.d.ts.map +1 -0
  80. package/dist/react/useTemporaryQuery.js +17 -0
  81. package/dist/react/useTemporaryQuery.js.map +1 -0
  82. package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
  83. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
  84. package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
  85. package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
  86. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
  87. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
  88. package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
  89. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
  90. package/dist/reactive.d.ts +42 -48
  91. package/dist/reactive.d.ts.map +1 -1
  92. package/dist/reactive.js +293 -186
  93. package/dist/reactive.js.map +1 -1
  94. package/dist/reactiveQueries/base-class.d.ts +28 -20
  95. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  96. package/dist/reactiveQueries/base-class.js +25 -17
  97. package/dist/reactiveQueries/base-class.js.map +1 -1
  98. package/dist/reactiveQueries/graph.d.ts +10 -0
  99. package/dist/reactiveQueries/graph.d.ts.map +1 -0
  100. package/dist/reactiveQueries/graph.js +6 -0
  101. package/dist/reactiveQueries/graph.js.map +1 -0
  102. package/dist/reactiveQueries/graphql.d.ts +35 -18
  103. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  104. package/dist/reactiveQueries/graphql.js +91 -10
  105. package/dist/reactiveQueries/graphql.js.map +1 -1
  106. package/dist/reactiveQueries/js.d.ts +17 -13
  107. package/dist/reactiveQueries/js.d.ts.map +1 -1
  108. package/dist/reactiveQueries/js.js +31 -8
  109. package/dist/reactiveQueries/js.js.map +1 -1
  110. package/dist/reactiveQueries/sql.d.ts +22 -18
  111. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  112. package/dist/reactiveQueries/sql.js +81 -16
  113. package/dist/reactiveQueries/sql.js.map +1 -1
  114. package/dist/schema.d.ts +0 -2
  115. package/dist/schema.d.ts.map +1 -1
  116. package/dist/schema.js +3 -6
  117. package/dist/schema.js.map +1 -1
  118. package/dist/storage/in-memory/index.d.ts +2 -2
  119. package/dist/storage/in-memory/index.d.ts.map +1 -1
  120. package/dist/storage/in-memory/index.js.map +1 -1
  121. package/dist/storage/index.d.ts +2 -2
  122. package/dist/storage/index.d.ts.map +1 -1
  123. package/dist/storage/tauri/index.d.ts +2 -2
  124. package/dist/storage/tauri/index.d.ts.map +1 -1
  125. package/dist/storage/tauri/index.js.map +1 -1
  126. package/dist/storage/web-worker/index.d.ts +4 -4
  127. package/dist/storage/web-worker/index.d.ts.map +1 -1
  128. package/dist/storage/web-worker/index.js +3 -5
  129. package/dist/storage/web-worker/index.js.map +1 -1
  130. package/dist/storage/web-worker/worker.js +2 -2
  131. package/dist/storage/web-worker/worker.js.map +1 -1
  132. package/dist/store.d.ts +19 -52
  133. package/dist/store.d.ts.map +1 -1
  134. package/dist/store.js +323 -266
  135. package/dist/store.js.map +1 -1
  136. package/dist/util.d.ts +3 -1
  137. package/dist/util.d.ts.map +1 -1
  138. package/dist/util.js +2 -0
  139. package/dist/util.js.map +1 -1
  140. package/package.json +2 -1
  141. package/src/QueryCache.ts +1 -1
  142. package/src/__tests__/react/fixture.tsx +21 -16
  143. package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
  144. package/src/__tests__/react/useQuery.test.tsx +48 -0
  145. package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
  146. package/src/__tests__/reactive.test.ts +194 -142
  147. package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
  148. package/src/effect/LiveStore.ts +14 -18
  149. package/src/inMemoryDatabase.ts +22 -30
  150. package/src/index.ts +8 -6
  151. package/src/migrations.ts +39 -21
  152. package/src/react/LiveStoreProvider.tsx +13 -16
  153. package/src/react/index.ts +4 -8
  154. package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +98 -230
  155. package/src/react/useQuery.ts +58 -0
  156. package/src/react/useTemporaryQuery.ts +21 -0
  157. package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
  158. package/src/reactive.ts +386 -267
  159. package/src/reactiveQueries/base-class.ts +61 -39
  160. package/src/reactiveQueries/graph.ts +15 -0
  161. package/src/reactiveQueries/graphql.ts +147 -31
  162. package/src/reactiveQueries/js.ts +54 -21
  163. package/src/reactiveQueries/sql.ts +128 -37
  164. package/src/schema.ts +2 -5
  165. package/src/storage/in-memory/index.ts +2 -2
  166. package/src/storage/index.ts +2 -2
  167. package/src/storage/tauri/index.ts +2 -2
  168. package/src/storage/web-worker/index.ts +6 -8
  169. package/src/storage/web-worker/worker.ts +2 -2
  170. package/src/store.ts +394 -418
  171. package/src/util.ts +8 -2
  172. package/dist/backends/base.d.ts +0 -13
  173. package/dist/backends/base.d.ts.map +0 -1
  174. package/dist/backends/base.js +0 -53
  175. package/dist/backends/base.js.map +0 -1
  176. package/dist/backends/in-memory/index.d.ts +0 -22
  177. package/dist/backends/in-memory/index.d.ts.map +0 -1
  178. package/dist/backends/in-memory/index.js +0 -45
  179. package/dist/backends/in-memory/index.js.map +0 -1
  180. package/dist/backends/index.d.ts +0 -41
  181. package/dist/backends/index.d.ts.map +0 -1
  182. package/dist/backends/index.js +0 -16
  183. package/dist/backends/index.js.map +0 -1
  184. package/dist/backends/tauri/index.d.ts +0 -21
  185. package/dist/backends/tauri/index.d.ts.map +0 -1
  186. package/dist/backends/tauri/index.js +0 -48
  187. package/dist/backends/tauri/index.js.map +0 -1
  188. package/dist/backends/utils/idb.d.ts +0 -10
  189. package/dist/backends/utils/idb.d.ts.map +0 -1
  190. package/dist/backends/utils/idb.js +0 -58
  191. package/dist/backends/utils/idb.js.map +0 -1
  192. package/dist/backends/web-worker/index.d.ts +0 -26
  193. package/dist/backends/web-worker/index.d.ts.map +0 -1
  194. package/dist/backends/web-worker/index.js +0 -63
  195. package/dist/backends/web-worker/index.js.map +0 -1
  196. package/dist/backends/web-worker/worker.d.ts +0 -17
  197. package/dist/backends/web-worker/worker.d.ts.map +0 -1
  198. package/dist/backends/web-worker/worker.js +0 -139
  199. package/dist/backends/web-worker/worker.js.map +0 -1
  200. package/dist/storage/base.d.ts +0 -10
  201. package/dist/storage/base.d.ts.map +0 -1
  202. package/dist/storage/base.js +0 -14
  203. package/dist/storage/base.js.map +0 -1
  204. package/src/react/useGlobalQuery.ts +0 -37
  205. package/src/react/useGraphQL.ts +0 -112
package/src/store.ts CHANGED
@@ -1,12 +1,8 @@
1
- import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
2
1
  import { assertNever, makeNoopSpan, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
3
2
  import { identity } from '@livestore/utils/effect'
4
3
  import * as otel from '@opentelemetry/api'
5
4
  import type { GraphQLSchema } from 'graphql'
6
- import * as graphql from 'graphql'
7
- import { uniqueId } from 'lodash-es'
8
- import * as ReactDOM from 'react-dom'
9
- import initSqlite3Wasm from 'sqlite-esm'
5
+ import type * as Sqlite from 'sqlite-esm'
10
6
  import { v4 as uuid } from 'uuid'
11
7
 
12
8
  import type { ComponentKey } from './componentKey.js'
@@ -15,16 +11,17 @@ import type { LiveStoreEvent } from './events.js'
15
11
  import { InMemoryDatabase } from './inMemoryDatabase.js'
16
12
  import { migrateDb } from './migrations.js'
17
13
  import { getDurationMsFromSpan } from './otel.js'
18
- import type { GetAtom, Ref } from './reactive.js'
19
- import { ReactiveGraph } from './reactive.js'
20
- import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
21
- import { LiveStoreJSQuery } from './reactiveQueries/js.js'
22
- import { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
14
+ import type { ReactiveGraph, Ref } from './reactive.js'
15
+ import type { ILiveStoreQuery } from './reactiveQueries/base-class.js'
16
+ import { type DbContext, dbGraph } from './reactiveQueries/graph.js'
17
+ import type { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
18
+ import type { LiveStoreJSQuery } from './reactiveQueries/js.js'
19
+ import type { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
23
20
  import type { ActionDefinition, GetActionArgs, Schema, SQLWriteStatement } from './schema.js'
24
21
  import { componentStateTables } from './schema.js'
25
22
  import type { Storage, StorageInit } from './storage/index.js'
26
- import type { Bindable, ParamsObject } from './util.js'
27
- import { isPromise, sql } from './util.js'
23
+ import type { ParamsObject } from './util.js'
24
+ import { isPromise, prepareBindValues, sql } from './util.js'
28
25
 
29
26
  export type LiveStoreQuery<TResult extends Record<string, any> = any> =
30
27
  | LiveStoreSQLQuery<TResult>
@@ -37,8 +34,6 @@ export type BaseGraphQLContext = {
37
34
  otelContext?: otel.Context
38
35
  }
39
36
 
40
- export const RESET_DB_LOCAL_STORAGE_KEY = 'livestore-reset'
41
-
42
37
  export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
43
38
  ? ReadonlyArray<Readonly<R>>
44
39
  : TQuery extends LiveStoreJSQuery<infer S>
@@ -47,7 +42,7 @@ export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
47
42
  ? Readonly<Result>
48
43
  : never
49
44
 
50
- const globalComponentKey: ComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' }
45
+ export const globalComponentKey: ComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' }
51
46
 
52
47
  export type GraphQLOptions<TContext> = {
53
48
  schema: GraphQLSchema
@@ -56,6 +51,8 @@ export type GraphQLOptions<TContext> = {
56
51
 
57
52
  export type StoreOptions<TGraphQLContext extends BaseGraphQLContext> = {
58
53
  db: InMemoryDatabase
54
+ /** A `Proxy`d version of `db` except that it also mirrors `execute` calls to the storage */
55
+ dbProxy: InMemoryDatabase
59
56
  schema: Schema
60
57
  storage?: Storage
61
58
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
@@ -100,9 +97,11 @@ export type StoreOtel = {
100
97
  queriesSpanContext: otel.Context
101
98
  }
102
99
 
103
- export class Store<TGraphQLContext extends BaseGraphQLContext> {
104
- graph: ReactiveGraph<RefreshReason, QueryDebugInfo>
100
+ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext> {
101
+ graph: ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>
105
102
  inMemoryDB: InMemoryDatabase
103
+ // TODO refactor
104
+ _proxyDb: InMemoryDatabase
106
105
  schema: Schema
107
106
  graphQLSchema?: GraphQLSchema
108
107
  graphQLContext?: TGraphQLContext
@@ -118,6 +117,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
118
117
 
119
118
  private constructor({
120
119
  db,
120
+ dbProxy,
121
121
  schema,
122
122
  storage,
123
123
  graphQLOptions,
@@ -125,12 +125,13 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
125
125
  otelRootSpanContext,
126
126
  }: StoreOptions<TGraphQLContext>) {
127
127
  this.inMemoryDB = db
128
- this.graph = new ReactiveGraph({
129
- // TODO move this into React module
130
- // Do all our updates inside a single React setState batch to avoid multiple UI re-renders
131
- effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
132
- otelTracer,
133
- })
128
+ this._proxyDb = dbProxy
129
+ // this.graph = new ReactiveGraph({
130
+ // // TODO move this into React module
131
+ // // Do all our updates inside a single React setState batch to avoid multiple UI re-renders
132
+ // effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
133
+ // otelTracer,
134
+ // })
134
135
  this.schema = schema
135
136
  // TODO generalize the `tableRefs` concept to allow finer-grained refs
136
137
  this.tableRefs = {}
@@ -143,6 +144,9 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
143
144
  const queriesSpan = otelTracer.startSpan('LiveStore:queries', {}, otelRootSpanContext)
144
145
  const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
145
146
 
147
+ this.graph = dbGraph
148
+ this.graph.context = { store: this, otelTracer, rootOtelContext: otelQueriesSpanContext }
149
+
146
150
  this.otel = {
147
151
  tracer: otelTracer,
148
152
  applyEventsSpanContext: otelApplyEventsSpanContext,
@@ -187,372 +191,340 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
187
191
  *
188
192
  * NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
189
193
  */
190
- querySQL = <TResult>(
191
- genQueryString: (get: GetAtom) => string,
192
- {
193
- queriedTables,
194
- bindValues,
195
- componentKey,
196
- label,
197
- otelContext = otel.context.active(),
198
- }: {
199
- /**
200
- * List of tables that are queried in this query;
201
- * used to determine reactive dependencies.
202
- *
203
- * NOTE In the future we want to auto-generate this via parsing the query
204
- */
205
- queriedTables: string[]
206
- bindValues?: Bindable | undefined
207
- componentKey?: ComponentKey | undefined
208
- label?: string | undefined
209
- otelContext?: otel.Context
210
- },
211
- ): LiveStoreSQLQuery<TResult> =>
212
- this.otel.tracer.startActiveSpan(
213
- 'querySQL', // NOTE span name will be overridden further down
214
- { attributes: { label } },
215
- otelContext,
216
- (span) => {
217
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
218
-
219
- const queryString$ = this.graph.makeThunk(
220
- (get, addDebugInfo) => {
221
- const queryString = genQueryString(get)
222
- addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
223
- return queryString
224
- },
225
- { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
226
- otelContext,
227
- )
228
-
229
- label = label ?? queryString$.result
230
- span.updateName(`querySQL:${label}`)
231
-
232
- const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
233
-
234
- const results$ = this.graph.makeThunk<TResult[]>(
235
- (get, addDebugInfo) =>
236
- this.otel.tracer.startActiveSpan(
237
- 'sql', // NOTE span name will be overridden further down
238
- {},
239
- otelContext,
240
- (span) => {
241
- try {
242
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
243
-
244
- // Establish a reactive dependency on the tables used in the query
245
- for (const tableName of queriedTables) {
246
- const tableRef =
247
- this.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
248
- get(tableRef)
249
- }
250
- const sqlString = get(queryString$)
251
-
252
- span.setAttribute('sql.query', sqlString)
253
- span.updateName(`sql:${sqlString.slice(0, 50)}`)
254
-
255
- const results = this.inMemoryDB.select(sqlString, { queriedTables, bindValues, otelContext })
256
-
257
- span.setAttribute('sql.rowsCount', results.length)
258
- addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
259
-
260
- return results as unknown as TResult[]
261
- } finally {
262
- span.end()
263
- }
264
- },
265
- ),
266
- { label: queryLabel },
267
- otelContext,
268
- )
269
-
270
- const query = new LiveStoreSQLQuery<TResult>({
271
- label,
272
- queryString$,
273
- results$,
274
- componentKey: componentKey ?? globalComponentKey,
275
- store: this,
276
- otelContext,
277
- })
278
-
279
- this.activeQueries.add(query)
280
-
281
- // TODO get rid of temporary query workaround
282
- if (this.temporaryQueries !== undefined) {
283
- this.temporaryQueries.add(query)
284
- }
285
-
286
- // NOTE we are not ending the span here but in the query `destroy` method
287
- return query
288
- },
289
- )
290
-
291
- queryJS = <TResult>(
292
- genResults: (get: GetAtom) => TResult,
293
- {
294
- componentKey = globalComponentKey,
295
- label = `js${uniqueId()}`,
296
- otelContext = otel.context.active(),
297
- }: { componentKey?: ComponentKey; label?: string; otelContext?: otel.Context },
298
- ): LiveStoreJSQuery<TResult> =>
299
- this.otel.tracer.startActiveSpan(`queryJS:${label}`, { attributes: { label } }, otelContext, (span) => {
300
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
301
- const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
302
- const results$ = this.graph.makeThunk(
303
- (get, addDebugInfo) => {
304
- addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
305
- return genResults(get)
306
- },
307
- { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
308
- otelContext,
309
- )
310
-
311
- const query = new LiveStoreJSQuery<TResult>({
312
- label,
313
- results$,
314
- componentKey,
315
- store: this,
316
- otelContext,
317
- })
318
-
319
- this.activeQueries.add(query)
320
-
321
- // TODO get rid of temporary query workaround
322
- if (this.temporaryQueries !== undefined) {
323
- this.temporaryQueries.add(query)
324
- }
325
-
326
- // NOTE we are not ending the span here but in the query `destroy` method
327
- return query
328
- })
329
-
330
- queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
331
- document: DocumentNode<TResult, TVariableValues>,
332
- genVariableValues: (get: GetAtom) => TVariableValues,
333
- {
334
- componentKey,
335
- label,
336
- otelContext = otel.context.active(),
337
- }: {
338
- componentKey: ComponentKey
339
- label?: string
340
- otelContext?: otel.Context
341
- },
342
- ): LiveStoreGraphQLQuery<TResult, TVariableValues, TGraphQLContext> =>
343
- this.otel.tracer.startActiveSpan(
344
- `queryGraphQL:`, // NOTE span name will be overridden further down
345
- {},
346
- otelContext,
347
- (span) => {
348
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
349
-
350
- if (this.graphQLContext === undefined) {
351
- return shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
352
- }
353
-
354
- const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql'
355
-
356
- span.updateName(`queryGraphQL:${labelWithDefault}`)
357
-
358
- const variableValues$ = this.graph.makeThunk(
359
- genVariableValues,
360
- { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
361
- otelContext,
362
- )
363
-
364
- const resultsLabel = `${labelWithDefault}:results` + (this.temporaryQueries ? ':temp' : '')
365
- const results$ = this.graph.makeThunk<TResult>(
366
- (get, addDebugInfo) => {
367
- const variableValues = get(variableValues$)
368
- const { result, queriedTables } = this.queryGraphQLOnce(document, variableValues, otelContext)
369
-
370
- // Add dependencies on any tables that were used
371
- for (const tableName of queriedTables) {
372
- const tableRef = this.tableRefs[tableName]
373
- assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
374
- get(tableRef!)
375
- }
376
-
377
- addDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document) })
378
-
379
- return result
380
- },
381
- { label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } },
382
- otelContext,
383
- )
384
-
385
- const query = new LiveStoreGraphQLQuery({
386
- document,
387
- context: this.graphQLContext,
388
- results$,
389
- componentKey,
390
- label: labelWithDefault,
391
- store: this,
392
- otelContext,
393
- })
394
-
395
- this.activeQueries.add(query)
396
-
397
- // TODO get rid of temporary query workaround
398
- if (this.temporaryQueries !== undefined) {
399
- this.temporaryQueries.add(query)
400
- }
401
-
402
- // NOTE we are not ending the span here but in the query `destroy` method
403
- return query
404
- },
405
- )
406
-
407
- queryGraphQLOnce = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
408
- document: DocumentNode<TResult, TVariableValues>,
409
- variableValues: TVariableValues,
410
- otelContext: otel.Context = this.otel.queriesSpanContext,
411
- ): { result: TResult; queriedTables: string[] } => {
412
- const schema =
413
- this.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
414
- const context =
415
- this.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
416
- const tracer = this.otel.tracer
417
-
418
- const operationName = graphql.getOperationAST(document)?.name?.value
419
-
420
- return tracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
421
- try {
422
- span.setAttribute('graphql.variables', JSON.stringify(variableValues))
423
- span.setAttribute('graphql.query', graphql.print(document))
424
-
425
- context.queriedTables.clear()
426
-
427
- context.otelContext = otel.trace.setSpan(otel.context.active(), span)
428
-
429
- const res = graphql.executeSync({
430
- document,
431
- contextValue: context,
432
- schema: schema,
433
- variableValues,
434
- })
435
-
436
- // TODO track number of nested SQL queries via Otel + debug info
437
-
438
- if (res.errors) {
439
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' })
440
- span.setAttribute('graphql.error', res.errors.join('\n'))
441
- span.setAttribute('graphql.error-detail', JSON.stringify(res.errors))
442
- console.error(`graphql error (${operationName})`, res.errors)
443
- }
444
-
445
- return { result: res.data as unknown as TResult, queriedTables: Array.from(context.queriedTables.values()) }
446
- } finally {
447
- span.end()
448
- }
449
- })
450
- }
194
+ // querySQL = <TResult>(
195
+ // genQueryString: string | ((get: GetAtomResult) => string),
196
+ // {
197
+ // queriedTables,
198
+ // bindValues,
199
+ // componentKey,
200
+ // label,
201
+ // otelContext = otel.context.active(),
202
+ // }: {
203
+ // /**
204
+ // * List of tables that are queried in this query;
205
+ // * used to determine reactive dependencies.
206
+ // *
207
+ // * NOTE In the future we want to auto-generate this via parsing the query
208
+ // */
209
+ // queriedTables: string[]
210
+ // bindValues?: Bindable | undefined
211
+ // componentKey?: ComponentKey | undefined
212
+ // label?: string | undefined
213
+ // otelContext?: otel.Context
214
+ // },
215
+ // ): LiveStoreSQLQuery<TResult> =>
216
+ // this.otel.tracer.startActiveSpan(
217
+ // 'querySQL', // NOTE span name will be overridden further down
218
+ // { attributes: { label } },
219
+ // otelContext,
220
+ // (span) => {
221
+ // const otelContext = otel.trace.setSpan(otel.context.active(), span)
222
+
223
+ // const queryString$ = this.graph.makeThunk(
224
+ // (get, addDebugInfo) => {
225
+ // if (typeof genQueryString === 'function') {
226
+ // const queryString = genQueryString(makeGetAtomResult(get))
227
+ // addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
228
+ // return queryString
229
+ // } else {
230
+ // return genQueryString
231
+ // }
232
+ // },
233
+ // { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
234
+ // otelContext,
235
+ // )
236
+
237
+ // label = label ?? queryString$.result
238
+ // span.updateName(`querySQL:${label}`)
239
+
240
+ // const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
241
+
242
+ // const results$ = this.graph.makeThunk<ReadonlyArray<TResult>>(
243
+ // (get, addDebugInfo) =>
244
+ // this.otel.tracer.startActiveSpan(
245
+ // 'sql:', // NOTE span name will be overridden further down
246
+ // {},
247
+ // otelContext,
248
+ // (span) => {
249
+ // try {
250
+ // const otelContext = otel.trace.setSpan(otel.context.active(), span)
251
+
252
+ // // Establish a reactive dependency on the tables used in the query
253
+ // for (const tableName of queriedTables) {
254
+ // const tableRef =
255
+ // this.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
256
+ // get(tableRef)
257
+ // }
258
+ // const sqlString = get(queryString$)
259
+
260
+ // span.setAttribute('sql.query', sqlString)
261
+ // span.updateName(`sql:${sqlString.slice(0, 50)}`)
262
+
263
+ // const results = this.inMemoryDB.select<TResult>(sqlString, {
264
+ // queriedTables,
265
+ // bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
266
+ // otelContext,
267
+ // })
268
+
269
+ // span.setAttribute('sql.rowsCount', results.length)
270
+ // addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
271
+
272
+ // return results
273
+ // } finally {
274
+ // span.end()
275
+ // }
276
+ // },
277
+ // ),
278
+ // { label: queryLabel },
279
+ // otelContext,
280
+ // )
281
+
282
+ // const query = new LiveStoreSQLQuery<TResult>({
283
+ // label,
284
+ // queryString$,
285
+ // results$,
286
+ // componentKey: componentKey ?? globalComponentKey,
287
+ // store: this,
288
+ // otelContext,
289
+ // })
290
+
291
+ // this.activeQueries.add(query)
292
+
293
+ // // TODO get rid of temporary query workaround
294
+ // if (this.temporaryQueries !== undefined) {
295
+ // this.temporaryQueries.add(query)
296
+ // }
297
+
298
+ // // NOTE we are not ending the span here but in the query `destroy` method
299
+ // return query
300
+ // },
301
+ // )
302
+
303
+ // queryJS = <TResult>(
304
+ // genResults: (get: GetAtomResult) => TResult,
305
+ // {
306
+ // componentKey = globalComponentKey,
307
+ // label = `js${uniqueId()}`,
308
+ // otelContext = otel.context.active(),
309
+ // }: { componentKey?: ComponentKey; label?: string; otelContext?: otel.Context },
310
+ // ): LiveStoreJSQuery<TResult> =>
311
+ // this.otel.tracer.startActiveSpan(`queryJS:${label}`, { attributes: { label } }, otelContext, (span) => {
312
+ // const otelContext = otel.trace.setSpan(otel.context.active(), span)
313
+ // const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
314
+ // const results$ = this.graph.makeThunk(
315
+ // (get, addDebugInfo) => {
316
+ // addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
317
+ // return genResults(makeGetAtomResult(get))
318
+ // },
319
+ // { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
320
+ // otelContext,
321
+ // )
322
+
323
+ // // const query = new LiveStoreJSQuery<TResult>({
324
+ // // label,
325
+ // // results$,
326
+ // // componentKey,
327
+ // // store: this,
328
+ // // otelContext,
329
+ // // })
330
+
331
+ // this.activeQueries.add(query)
332
+
333
+ // // TODO get rid of temporary query workaround
334
+ // if (this.temporaryQueries !== undefined) {
335
+ // this.temporaryQueries.add(query)
336
+ // }
337
+
338
+ // // NOTE we are not ending the span here but in the query `destroy` method
339
+ // return query
340
+ // })
341
+
342
+ // queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
343
+ // document: DocumentNode<TResult, TVariableValues>,
344
+ // genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
345
+ // {
346
+ // componentKey,
347
+ // label,
348
+ // otelContext = otel.context.active(),
349
+ // }: {
350
+ // componentKey: ComponentKey
351
+ // label?: string
352
+ // otelContext?: otel.Context
353
+ // },
354
+ // ): LiveStoreGraphQLQuery<TResult, TVariableValues, TGraphQLContext> =>
355
+ // this.otel.tracer.startActiveSpan(
356
+ // `queryGraphQL:`, // NOTE span name will be overridden further down
357
+ // {},
358
+ // otelContext,
359
+ // (span) => {
360
+ // const otelContext = otel.trace.setSpan(otel.context.active(), span)
361
+
362
+ // if (this.graphQLContext === undefined) {
363
+ // return shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
364
+ // }
365
+
366
+ // const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql'
367
+
368
+ // span.updateName(`queryGraphQL:${labelWithDefault}`)
369
+
370
+ // const variableValues$ = this.graph.makeThunk(
371
+ // (get) => {
372
+ // if (typeof genVariableValues === 'function') {
373
+ // return genVariableValues(makeGetAtomResult(get))
374
+ // } else {
375
+ // return genVariableValues
376
+ // }
377
+ // },
378
+ // { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
379
+ // // otelContext,
380
+ // )
381
+
382
+ // const resultsLabel = `${labelWithDefault}:results` + (this.temporaryQueries ? ':temp' : '')
383
+ // const results$ = this.graph.makeThunk<TResult>(
384
+ // (get, addDebugInfo) => {
385
+ // const variableValues = get(variableValues$)
386
+ // const { result, queriedTables } = this.queryGraphQLOnce(document, variableValues, otelContext)
387
+
388
+ // // Add dependencies on any tables that were used
389
+ // for (const tableName of queriedTables) {
390
+ // const tableRef = this.tableRefs[tableName]
391
+ // assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
392
+ // get(tableRef!)
393
+ // }
394
+
395
+ // addDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document) })
396
+
397
+ // return result
398
+ // },
399
+ // { label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } },
400
+ // // otelContext,
401
+ // )
402
+
403
+ // const query = new LiveStoreGraphQLQuery({
404
+ // document,
405
+ // context: this.graphQLContext,
406
+ // results$,
407
+ // componentKey,
408
+ // label: labelWithDefault,
409
+ // store: this,
410
+ // otelContext,
411
+ // })
412
+
413
+ // this.activeQueries.add(query)
414
+
415
+ // // TODO get rid of temporary query workaround
416
+ // if (this.temporaryQueries !== undefined) {
417
+ // this.temporaryQueries.add(query)
418
+ // }
419
+
420
+ // // NOTE we are not ending the span here but in the query `destroy` method
421
+ // return query
422
+ // },
423
+ // )
424
+
425
+ // queryGraphQLOnce = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
426
+ // document: DocumentNode<TResult, TVariableValues>,
427
+ // variableValues: TVariableValues,
428
+ // otelContext: otel.Context = this.otel.queriesSpanContext,
429
+ // ): { result: TResult; queriedTables: string[] } => {
430
+ // const schema =
431
+ // this.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
432
+ // const context =
433
+ // this.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
434
+ // const tracer = this.otel.tracer
435
+
436
+ // const operationName = graphql.getOperationAST(document)?.name?.value
437
+
438
+ // return tracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
439
+ // try {
440
+ // span.setAttribute('graphql.variables', JSON.stringify(variableValues))
441
+ // span.setAttribute('graphql.query', graphql.print(document))
442
+
443
+ // context.queriedTables.clear()
444
+
445
+ // context.otelContext = otel.trace.setSpan(otel.context.active(), span)
446
+
447
+ // const res = graphql.executeSync({
448
+ // document,
449
+ // contextValue: context,
450
+ // schema: schema,
451
+ // variableValues,
452
+ // })
453
+
454
+ // // TODO track number of nested SQL queries via Otel + debug info
455
+
456
+ // if (res.errors) {
457
+ // span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' })
458
+ // span.setAttribute('graphql.error', res.errors.join('\n'))
459
+ // span.setAttribute('graphql.error-detail', JSON.stringify(res.errors))
460
+ // console.error(`graphql error (${operationName})`, res.errors)
461
+ // }
462
+
463
+ // return { result: res.data as unknown as TResult, queriedTables: Array.from(context.queriedTables.values()) }
464
+ // } finally {
465
+ // span.end()
466
+ // }
467
+ // })
468
+ // }
451
469
 
452
470
  /**
453
471
  * Subscribe to the results of a query
454
472
  * Returns a function to cancel the subscription.
455
473
  */
456
- subscribe = <TQuery extends LiveStoreQuery>(
457
- query: TQuery,
458
- onNewValue: (value: QueryResult<TQuery>) => void,
474
+ subscribe = <TResult>(
475
+ query: ILiveStoreQuery<TResult>,
476
+ onNewValue: (value: TResult) => void,
459
477
  onSubsubscribe?: () => void,
460
- options?: { label?: string } | undefined,
478
+ options?: { label?: string; otelContext?: otel.Context } | undefined,
461
479
  ): (() => void) =>
462
480
  this.otel.tracer.startActiveSpan(
463
481
  `LiveStore.subscribe`,
464
482
  { attributes: { label: options?.label } },
465
- query.otelContext,
483
+ options?.otelContext ?? this.otel.queriesSpanContext,
466
484
  (span) => {
467
485
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
468
486
 
469
- const effect = this.graph.makeEffect(
470
- (get) => {
471
- const result = get(query.results$) as QueryResult<TQuery>
472
- onNewValue(result)
473
- },
474
- { label: `subscribe:${options?.label}` },
475
- otelContext,
476
- )
487
+ const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), {
488
+ label: `subscribe:${options?.label}`,
489
+ })
490
+
491
+ effect.doEffect(otelContext)
477
492
 
478
- const subscriptionKey = uuid()
493
+ // const subscriptionKey = uuid()
479
494
 
480
495
  const unsubscribe = () => {
481
496
  try {
482
497
  this.graph.destroy(effect)
483
- query.activeSubscriptions.delete(subscriptionKey)
498
+ this.activeQueries.delete(query as LiveStoreQuery)
499
+ // query.activeSubscriptions.delete(subscriptionKey)
484
500
  onSubsubscribe?.()
485
501
  } finally {
486
502
  span.end()
487
503
  }
488
504
  }
489
505
 
490
- query.activeSubscriptions.set(subscriptionKey, unsubscribe)
506
+ this.activeQueries.add(query as LiveStoreQuery)
507
+
508
+ // query.activeSubscriptions.set(subscriptionKey, unsubscribe)
491
509
 
492
510
  return unsubscribe
493
511
  },
494
512
  )
495
513
 
496
- /**
497
- * Any queries created in the callback will be destroyed when the callback is complete.
498
- * Useful for temporarily creating reactive queries, which is an idempotent operation
499
- * that can be safely called inside a React useMemo hook.
500
- */
501
- inTempQueryContext = <TResult>(callback: () => TResult): TResult => {
502
- this.temporaryQueries = new Set()
503
- // TODO: consider errors / try/finally here?
504
- const result = callback()
505
- for (const query of this.temporaryQueries) {
506
- this.destroyQuery(query)
507
- }
508
- this.temporaryQueries = undefined
509
- return result
510
- }
511
-
512
514
  /**
513
515
  * Destroys the entire store, including all queries and subscriptions.
514
516
  *
515
517
  * Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
516
518
  */
517
519
  destroy = () => {
518
- for (const query of this.activeQueries) {
519
- this.destroyQuery(query)
520
- }
521
-
522
520
  Object.values(this.tableRefs).forEach((tableRef) => this.graph.destroy(tableRef))
523
521
 
524
- const applyEventsSpan = otel.trace.getSpan(this.otel.applyEventsSpanContext)!
525
- applyEventsSpan.end()
526
-
527
- const queriesSpan = otel.trace.getSpan(this.otel.queriesSpanContext)!
528
- queriesSpan.end()
522
+ otel.trace.getSpan(this.otel.applyEventsSpanContext)!.end()
523
+ otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
529
524
 
530
525
  // TODO destroy active subscriptions
531
526
  }
532
527
 
533
- private destroyQuery = (query: LiveStoreQuery) => {
534
- if (query._tag === 'sql') {
535
- // results are downstream of query string, so will automatically be destroyed together
536
- this.graph.destroy(query.queryString$)
537
- } else {
538
- this.graph.destroy(query.results$)
539
- }
540
- this.activeQueries.delete(query)
541
- query.destroy()
542
- }
543
-
544
- /**
545
- * Clean up queries and downstream subscriptions associated with a component.
546
- * This is critical to avoid memory leaks.
547
- */
548
- unmountComponent = (componentKey: ComponentKey) => {
549
- for (const query of this.activeQueries) {
550
- if (query.componentKey === componentKey) {
551
- this.destroyQuery(query)
552
- }
553
- }
554
- }
555
-
556
528
  /* Apply a single write event to the store, and refresh all queries in response */
557
529
  applyEvent = <TEventType extends string & keyof LiveStoreActionDefinitionsTypes>(
558
530
  eventType: TEventType,
@@ -581,20 +553,25 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
581
553
  tablesToUpdate.push([tableRef!, null])
582
554
  }
583
555
 
556
+ const debugRefreshReason = {
557
+ _tag: 'applyEvent' as const,
558
+ event: { type: eventType, args },
559
+ writeTables: [...writeTables],
560
+ }
561
+
584
562
  // Update all table refs together in a batch, to only trigger one reactive update
585
- this.graph.setRefs(
586
- tablesToUpdate,
587
- {
588
- otelHint: 'applyEvents',
589
- skipRefresh,
590
- debugRefreshReason: {
591
- _tag: 'applyEvent',
592
- event: { type: eventType, args },
593
- writeTables: [...writeTables],
594
- },
595
- },
596
- otelContext,
597
- )
563
+ this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext })
564
+
565
+ if (skipRefresh === false) {
566
+ // TODO update the graph
567
+ // this.graph.refresh(
568
+ // {
569
+ // otelHint: 'applyEvents',
570
+ // debugRefreshReason,
571
+ // },
572
+ // otelContext,
573
+ // )
574
+ }
598
575
  } catch (e: any) {
599
576
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
600
577
 
@@ -677,20 +654,18 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
677
654
  tablesToUpdate.push([tableRef!, null])
678
655
  }
679
656
 
657
+ const debugRefreshReason = {
658
+ _tag: 'applyEvents' as const,
659
+ events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
660
+ writeTables: [...writeTables],
661
+ }
680
662
  // Update all table refs together in a batch, to only trigger one reactive update
681
- this.graph.setRefs(
682
- tablesToUpdate,
683
- {
684
- otelHint: 'applyEvents',
685
- skipRefresh,
686
- debugRefreshReason: {
687
- _tag: 'applyEvents',
688
- events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
689
- writeTables: [...writeTables],
690
- },
691
- },
692
- otelContext,
693
- )
663
+ this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext })
664
+
665
+ if (skipRefresh === false) {
666
+ // TODO update the graph
667
+ // this.graph.refresh({ debugRefreshReason, otelHint: 'applyEvents' }, otelContext)
668
+ }
694
669
  } catch (e: any) {
695
670
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
696
671
  } finally {
@@ -713,8 +688,9 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
713
688
  { attributes: { 'livestore.manualRefreshLabel': label } },
714
689
  this.otel.applyEventsSpanContext,
715
690
  (span) => {
716
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
717
- this.graph.refresh({ otelHint: 'manualRefresh', debugRefreshReason: { _tag: 'manualRefresh' } }, otelContext)
691
+ // const otelContext = otel.trace.setSpan(otel.context.active(), span)
692
+ // TODO update the graph
693
+ // this.graph.refresh({ otelHint: 'manualRefresh', debugRefreshReason: { _tag: 'manualRefresh' } }, otelContext)
718
694
  span.end()
719
695
  },
720
696
  )
@@ -778,14 +754,19 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
778
754
  // Synchronously apply the event to the in-memory database
779
755
  // const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
780
756
  const { statement, bindValues } = eventToSql(eventWithId, actionDefinition)
781
- const { durationMs } = this.inMemoryDB.execute(statement.sql, bindValues, statement.writeTables, {
782
- otelContext,
783
- })
757
+ const { durationMs } = this.inMemoryDB.execute(
758
+ statement.sql,
759
+ prepareBindValues(bindValues, statement.sql),
760
+ statement.writeTables,
761
+ {
762
+ otelContext,
763
+ },
764
+ )
784
765
 
785
766
  // Asynchronously apply the event to a persistent storage (we're not awaiting this promise here)
786
767
  if (this.storage !== undefined) {
787
768
  // this.storage.applyEvent(eventWithId, actionDefinition, span)
788
- this.storage.execute(statement.sql, bindValues, span)
769
+ this.storage.execute(statement.sql, prepareBindValues(bindValues, statement.sql), span)
789
770
  }
790
771
 
791
772
  // Uncomment to print a list of queries currently registered on the store
@@ -808,12 +789,12 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
808
789
  * This should only be used for framework-internal purposes;
809
790
  * all app writes should go through applyEvent.
810
791
  */
811
- execute = async (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
812
- this.inMemoryDB.execute(query, params, writeTables)
792
+ execute = (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
793
+ this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables)
813
794
 
814
795
  if (this.storage !== undefined) {
815
796
  const parentSpan = otel.trace.getSpan(otel.context.active())
816
- this.storage.execute(query, params, parentSpan)
797
+ this.storage.execute(query, prepareBindValues(params, query), parentSpan)
817
798
  }
818
799
  }
819
800
  }
@@ -826,6 +807,7 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
826
807
  otelTracer = makeNoopTracer(),
827
808
  otelRootSpanContext = otel.context.active(),
828
809
  boot,
810
+ sqlite3,
829
811
  }: {
830
812
  schema: Schema
831
813
  loadStorage: () => StorageInit | Promise<StorageInit>
@@ -833,50 +815,36 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
833
815
  otelTracer?: otel.Tracer
834
816
  otelRootSpanContext?: otel.Context
835
817
  boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
818
+ sqlite3: Sqlite.Sqlite3Static
836
819
  }): Promise<Store<TGraphQLContext>> => {
837
820
  return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
838
821
  try {
839
822
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
840
823
 
841
- const loadStorageAndPersistedData = async () => {
842
- const storage = await otelTracer.startActiveSpan('storage:load', {}, otelContext, async (span) => {
824
+ const storage = await otelTracer.startActiveSpan('storage:load', {}, otelContext, async (span) => {
825
+ try {
826
+ const init = await loadStorage()
827
+ const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
828
+ return init({ otelTracer, parentSpan })
829
+ } finally {
830
+ span.end()
831
+ }
832
+ })
833
+
834
+ const persistedData = await otelTracer.startActiveSpan(
835
+ 'storage:getPersistedData',
836
+ {},
837
+ otelContext,
838
+ async (span) => {
843
839
  try {
844
- const init = await loadStorage()
845
- const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
846
- return init({ otelTracer, parentSpan })
840
+ return await storage.getPersistedData(span)
847
841
  } finally {
848
842
  span.end()
849
843
  }
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()])
844
+ },
845
+ )
878
846
 
879
- const db = InMemoryDatabase.load(persistedData, otelTracer, otelRootSpanContext, sqlite3)
847
+ const db = InMemoryDatabase.load({ data: persistedData, otelTracer, otelRootSpanContext, sqlite3 })
880
848
 
881
849
  // Proxy to `db` that also mirrors `execute` calls to `storage`
882
850
  const dbProxy = new Proxy(db, {
@@ -887,6 +855,14 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
887
855
  return db.execute(query, bindValues, writeTables, options)
888
856
  }
889
857
  return execute
858
+ } else if (prop === 'select') {
859
+ // NOTE we're even proxying `select` calls here as some apps (e.g. Overtone) currently rely on this
860
+ // TODO remove this once we've migrated all apps to use `execute` instead of `select`
861
+ const select: InMemoryDatabase['select'] = (query, options = {}) => {
862
+ storage.execute(query, options.bindValues as any)
863
+ return db.select(query, options)
864
+ }
865
+ return select
890
866
  } else {
891
867
  return Reflect.get(db, prop, receiver)
892
868
  }
@@ -914,7 +890,7 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
914
890
  // Think about what to do about this case.
915
891
  // await applySchema(db, schema)
916
892
  return Store.createStore<TGraphQLContext>(
917
- { db, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
893
+ { db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
918
894
  span,
919
895
  )
920
896
  } finally {