@livestore/livestore 0.0.21 → 0.0.23
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.
- package/README.md +14 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +0 -2
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/useQuery.test.js +1 -1
- package/dist/__tests__/react/useQuery.test.js.map +1 -1
- package/dist/__tests__/react/utils/stack-info.test.d.ts +2 -0
- package/dist/__tests__/react/utils/stack-info.test.d.ts.map +1 -0
- package/dist/__tests__/react/utils/stack-info.test.js +43 -0
- package/dist/__tests__/react/utils/stack-info.test.js.map +1 -0
- package/dist/__tests__/reactive.test.js +13 -1
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/__tests__/reactiveQueries/sql.test.js +3 -3
- package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
- package/dist/inMemoryDatabase.d.ts +2 -1
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/inMemoryDatabase.js +3 -2
- package/dist/inMemoryDatabase.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useComponentState.d.ts.map +1 -1
- package/dist/react/useComponentState.js +19 -27
- package/dist/react/useComponentState.js.map +1 -1
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +46 -26
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts.map +1 -1
- package/dist/react/useTemporaryQuery.js +2 -0
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/react/utils/stack-info.d.ts +11 -0
- package/dist/react/utils/stack-info.d.ts.map +1 -0
- package/dist/react/utils/stack-info.js +49 -0
- package/dist/react/utils/stack-info.js.map +1 -0
- package/dist/reactive.d.ts +33 -43
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +66 -255
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +15 -13
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +5 -8
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +4 -3
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +29 -34
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +2 -1
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +8 -9
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +11 -5
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +31 -34
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/store.d.ts +26 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +41 -255
- package/dist/store.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/react/fixture.tsx +0 -3
- package/src/__tests__/react/useQuery.test.tsx +1 -1
- package/src/__tests__/react/utils/{extractStackInfoFromStackTrace.test.ts → stack-info.test.ts} +25 -20
- package/src/__tests__/reactive.test.ts +20 -1
- package/src/__tests__/reactiveQueries/sql.test.ts +3 -3
- package/src/inMemoryDatabase.ts +9 -6
- package/src/react/index.ts +1 -0
- package/src/react/useComponentState.ts +25 -30
- package/src/react/useQuery.ts +66 -34
- package/src/react/useTemporaryQuery.ts +2 -0
- package/src/react/utils/{extractStackInfoFromStackTrace.ts → stack-info.ts} +21 -5
- package/src/reactive.ts +148 -339
- package/src/reactiveQueries/base-class.ts +23 -22
- package/src/reactiveQueries/graphql.ts +34 -36
- package/src/reactiveQueries/js.ts +14 -10
- package/src/reactiveQueries/sql.ts +55 -48
- package/src/store.ts +70 -305
package/src/store.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { LiveStoreEvent } from './events.js'
|
|
|
11
11
|
import { InMemoryDatabase } from './inMemoryDatabase.js'
|
|
12
12
|
import { migrateDb } from './migrations.js'
|
|
13
13
|
import { getDurationMsFromSpan } from './otel.js'
|
|
14
|
+
import type { StackInfo } from './react/utils/stack-info.js'
|
|
14
15
|
import type { ReactiveGraph, Ref } from './reactive.js'
|
|
15
16
|
import type { ILiveStoreQuery } from './reactiveQueries/base-class.js'
|
|
16
17
|
import { type DbContext, dbGraph } from './reactiveQueries/graph.js'
|
|
@@ -42,8 +43,6 @@ export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
|
|
|
42
43
|
? Readonly<Result>
|
|
43
44
|
: never
|
|
44
45
|
|
|
45
|
-
export const globalComponentKey: ComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' }
|
|
46
|
-
|
|
47
46
|
export type GraphQLOptions<TContext> = {
|
|
48
47
|
schema: GraphQLSchema
|
|
49
48
|
makeContext: (db: InMemoryDatabase, tracer: otel.Tracer) => TContext
|
|
@@ -87,9 +86,21 @@ export type RefreshReason =
|
|
|
87
86
|
_tag: 'makeThunk'
|
|
88
87
|
label?: string
|
|
89
88
|
}
|
|
89
|
+
| {
|
|
90
|
+
_tag: 'react'
|
|
91
|
+
api: string
|
|
92
|
+
label?: string
|
|
93
|
+
stackInfo?: StackInfo
|
|
94
|
+
}
|
|
95
|
+
| { _tag: 'manual'; label?: string }
|
|
90
96
|
| { _tag: 'unknown' }
|
|
91
97
|
|
|
92
|
-
export type QueryDebugInfo = {
|
|
98
|
+
export type QueryDebugInfo = {
|
|
99
|
+
_tag: 'graphql' | 'sql' | 'js' | 'unknown'
|
|
100
|
+
label: string
|
|
101
|
+
query: string
|
|
102
|
+
durationMs: number
|
|
103
|
+
}
|
|
93
104
|
|
|
94
105
|
export type StoreOtel = {
|
|
95
106
|
tracer: otel.Tracer
|
|
@@ -110,10 +121,11 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
110
121
|
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
|
111
122
|
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
|
112
123
|
*/
|
|
113
|
-
tableRefs: { [key: string]: Ref<null> }
|
|
114
|
-
|
|
124
|
+
tableRefs: { [key: string]: Ref<null, DbContext, RefreshReason> }
|
|
125
|
+
|
|
126
|
+
/** RC-based set to see which queries are currently subscribed to */
|
|
127
|
+
activeQueries: ReferenceCountedSet<LiveStoreQuery>
|
|
115
128
|
storage?: Storage
|
|
116
|
-
temporaryQueries: Set<LiveStoreQuery> | undefined
|
|
117
129
|
|
|
118
130
|
private constructor({
|
|
119
131
|
db,
|
|
@@ -126,16 +138,10 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
126
138
|
}: StoreOptions<TGraphQLContext>) {
|
|
127
139
|
this.inMemoryDB = db
|
|
128
140
|
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
|
-
// })
|
|
135
141
|
this.schema = schema
|
|
136
142
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
137
143
|
this.tableRefs = {}
|
|
138
|
-
this.activeQueries = new
|
|
144
|
+
this.activeQueries = new ReferenceCountedSet()
|
|
139
145
|
this.storage = storage
|
|
140
146
|
|
|
141
147
|
const applyEventsSpan = otelTracer.startSpan('LiveStore:applyEvents', {}, otelRootSpanContext)
|
|
@@ -144,6 +150,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
144
150
|
const queriesSpan = otelTracer.startSpan('LiveStore:queries', {}, otelRootSpanContext)
|
|
145
151
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
|
|
146
152
|
|
|
153
|
+
// TODO allow passing in a custom graph
|
|
147
154
|
this.graph = dbGraph
|
|
148
155
|
this.graph.context = { store: this, otelTracer, rootOtelContext: otelQueriesSpanContext }
|
|
149
156
|
|
|
@@ -186,287 +193,6 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
186
193
|
})
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
/**
|
|
190
|
-
* Creates a reactive LiveStore SQL query
|
|
191
|
-
*
|
|
192
|
-
* NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
|
|
193
|
-
*/
|
|
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
|
-
// }
|
|
469
|
-
|
|
470
196
|
/**
|
|
471
197
|
* Subscribe to the results of a query
|
|
472
198
|
* Returns a function to cancel the subscription.
|
|
@@ -474,8 +200,8 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
474
200
|
subscribe = <TResult>(
|
|
475
201
|
query: ILiveStoreQuery<TResult>,
|
|
476
202
|
onNewValue: (value: TResult) => void,
|
|
477
|
-
|
|
478
|
-
options?: { label?: string; otelContext?: otel.Context } | undefined,
|
|
203
|
+
onUnsubsubscribe?: () => void,
|
|
204
|
+
options?: { label?: string; otelContext?: otel.Context; skipInitialRun?: boolean } | undefined,
|
|
479
205
|
): (() => void) =>
|
|
480
206
|
this.otel.tracer.startActiveSpan(
|
|
481
207
|
`LiveStore.subscribe`,
|
|
@@ -487,20 +213,23 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
487
213
|
const label = `subscribe:${options?.label}`
|
|
488
214
|
const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), { label })
|
|
489
215
|
|
|
490
|
-
|
|
216
|
+
this.activeQueries.add(query as LiveStoreQuery)
|
|
217
|
+
|
|
218
|
+
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
|
219
|
+
if (options?.skipInitialRun !== true) {
|
|
220
|
+
effect.doEffect(otelContext)
|
|
221
|
+
}
|
|
491
222
|
|
|
492
223
|
const unsubscribe = () => {
|
|
493
224
|
try {
|
|
494
225
|
this.graph.destroy(effect)
|
|
495
|
-
this.activeQueries.
|
|
496
|
-
|
|
226
|
+
this.activeQueries.remove(query as LiveStoreQuery)
|
|
227
|
+
onUnsubsubscribe?.()
|
|
497
228
|
} finally {
|
|
498
229
|
span.end()
|
|
499
230
|
}
|
|
500
231
|
}
|
|
501
232
|
|
|
502
|
-
this.activeQueries.add(query as LiveStoreQuery)
|
|
503
|
-
|
|
504
233
|
return unsubscribe
|
|
505
234
|
},
|
|
506
235
|
)
|
|
@@ -540,7 +269,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
540
269
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
541
270
|
const writeTables = this.applyEventWithoutRefresh(eventType, args, otelContext).writeTables
|
|
542
271
|
|
|
543
|
-
const tablesToUpdate = [] as [Ref<null>, null][]
|
|
272
|
+
const tablesToUpdate = [] as [Ref<null, DbContext, RefreshReason>, null][]
|
|
544
273
|
for (const tableName of writeTables) {
|
|
545
274
|
const tableRef = this.tableRefs[tableName]
|
|
546
275
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
@@ -641,7 +370,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
641
370
|
},
|
|
642
371
|
)
|
|
643
372
|
|
|
644
|
-
const tablesToUpdate = [] as [Ref<null>, null][]
|
|
373
|
+
const tablesToUpdate = [] as [Ref<null, DbContext, RefreshReason>, null][]
|
|
645
374
|
for (const tableName of writeTables) {
|
|
646
375
|
const tableRef = this.tableRefs[tableName]
|
|
647
376
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
@@ -783,8 +512,8 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
783
512
|
* This should only be used for framework-internal purposes;
|
|
784
513
|
* all app writes should go through applyEvent.
|
|
785
514
|
*/
|
|
786
|
-
execute = (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
|
|
787
|
-
this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables)
|
|
515
|
+
execute = (query: string, params: ParamsObject = {}, writeTables?: string[], otelContext?: otel.Context) => {
|
|
516
|
+
this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
788
517
|
|
|
789
518
|
if (this.storage !== undefined) {
|
|
790
519
|
const parentSpan = otel.trace.getSpan(otel.context.active())
|
|
@@ -907,3 +636,39 @@ const eventToSql = (
|
|
|
907
636
|
|
|
908
637
|
return { statement, bindValues }
|
|
909
638
|
}
|
|
639
|
+
|
|
640
|
+
class ReferenceCountedSet<T> {
|
|
641
|
+
private map: Map<T, number>
|
|
642
|
+
|
|
643
|
+
constructor() {
|
|
644
|
+
this.map = new Map<T, number>()
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
add = (key: T) => {
|
|
648
|
+
const count = this.map.get(key) ?? 0
|
|
649
|
+
this.map.set(key, count + 1)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
remove = (key: T) => {
|
|
653
|
+
const count = this.map.get(key) ?? 0
|
|
654
|
+
if (count === 1) {
|
|
655
|
+
this.map.delete(key)
|
|
656
|
+
} else {
|
|
657
|
+
this.map.set(key, count - 1)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
has = (key: T) => {
|
|
662
|
+
return this.map.has(key)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
get size() {
|
|
666
|
+
return this.map.size
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
*[Symbol.iterator]() {
|
|
670
|
+
for (const key of this.map.keys()) {
|
|
671
|
+
yield key
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|