@livestore/livestore 0.3.0-dev.11 → 0.3.0-dev.5

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 (122) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SynchronousDatabaseWrapper.d.ts +5 -14
  3. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  4. package/dist/SynchronousDatabaseWrapper.js +4 -24
  5. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  6. package/dist/effect/LiveStore.d.ts +8 -12
  7. package/dist/effect/LiveStore.d.ts.map +1 -1
  8. package/dist/effect/LiveStore.js +3 -13
  9. package/dist/effect/LiveStore.js.map +1 -1
  10. package/dist/index.d.ts +7 -6
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/live-queries/base-class.d.ts +21 -64
  15. package/dist/live-queries/base-class.d.ts.map +1 -1
  16. package/dist/live-queries/base-class.js +13 -56
  17. package/dist/live-queries/base-class.js.map +1 -1
  18. package/dist/live-queries/computed.d.ts +7 -7
  19. package/dist/live-queries/computed.d.ts.map +1 -1
  20. package/dist/live-queries/computed.js +11 -35
  21. package/dist/live-queries/computed.js.map +1 -1
  22. package/dist/live-queries/db.d.ts +15 -12
  23. package/dist/live-queries/db.d.ts.map +1 -1
  24. package/dist/live-queries/db.js +25 -44
  25. package/dist/live-queries/db.js.map +1 -1
  26. package/dist/live-queries/db.test.js +14 -16
  27. package/dist/live-queries/db.test.js.map +1 -1
  28. package/dist/live-queries/graphql.d.ts +8 -8
  29. package/dist/live-queries/graphql.d.ts.map +1 -1
  30. package/dist/live-queries/graphql.js +9 -35
  31. package/dist/live-queries/graphql.js.map +1 -1
  32. package/dist/reactive.d.ts +13 -15
  33. package/dist/reactive.d.ts.map +1 -1
  34. package/dist/reactive.js +9 -15
  35. package/dist/reactive.js.map +1 -1
  36. package/dist/row-query-utils.d.ts +4 -4
  37. package/dist/row-query-utils.d.ts.map +1 -1
  38. package/dist/row-query-utils.js +10 -14
  39. package/dist/row-query-utils.js.map +1 -1
  40. package/dist/store/create-store.d.ts +4 -3
  41. package/dist/store/create-store.d.ts.map +1 -1
  42. package/dist/store/create-store.js +7 -7
  43. package/dist/store/create-store.js.map +1 -1
  44. package/dist/store/devtools.d.ts +2 -2
  45. package/dist/store/devtools.d.ts.map +1 -1
  46. package/dist/store/devtools.js +15 -15
  47. package/dist/store/devtools.js.map +1 -1
  48. package/dist/store/store-types.d.ts +5 -10
  49. package/dist/store/store-types.d.ts.map +1 -1
  50. package/dist/store/store-types.js.map +1 -1
  51. package/dist/store/store.d.ts +16 -34
  52. package/dist/store/store.d.ts.map +1 -1
  53. package/dist/store/store.js +77 -129
  54. package/dist/store/store.js.map +1 -1
  55. package/dist/utils/stack-info.d.ts.map +1 -1
  56. package/dist/utils/stack-info.js +1 -6
  57. package/dist/utils/stack-info.js.map +1 -1
  58. package/dist/utils/stack-info.test.js +1 -54
  59. package/dist/utils/stack-info.test.js.map +1 -1
  60. package/dist/utils/tests/fixture.d.ts +6 -2
  61. package/dist/utils/tests/fixture.d.ts.map +1 -1
  62. package/dist/utils/tests/fixture.js +5 -3
  63. package/dist/utils/tests/fixture.js.map +1 -1
  64. package/dist/utils/tests/mod.d.ts +0 -1
  65. package/dist/utils/tests/mod.d.ts.map +1 -1
  66. package/dist/utils/tests/mod.js +0 -1
  67. package/dist/utils/tests/mod.js.map +1 -1
  68. package/package.json +12 -12
  69. package/src/{SqliteDbWrapper.ts → SynchronousDatabaseWrapper.ts} +11 -41
  70. package/src/effect/LiveStore.ts +15 -26
  71. package/src/global-state.ts +20 -0
  72. package/src/index.ts +7 -14
  73. package/src/live-queries/__snapshots__/{db-query.test.ts.snap → db.test.ts.snap} +42 -196
  74. package/src/live-queries/base-class.ts +40 -160
  75. package/src/live-queries/computed.ts +19 -45
  76. package/src/live-queries/{db-query.test.ts → db.test.ts} +11 -21
  77. package/src/live-queries/{db-query.ts → db.ts} +39 -97
  78. package/src/live-queries/graphql.ts +21 -47
  79. package/src/reactive.ts +27 -52
  80. package/src/row-query-utils.ts +18 -29
  81. package/src/store/create-store.ts +23 -20
  82. package/src/store/devtools.ts +17 -17
  83. package/src/store/store-types.ts +5 -7
  84. package/src/store/store.ts +122 -231
  85. package/src/utils/stack-info.test.ts +1 -58
  86. package/src/utils/stack-info.ts +1 -6
  87. package/src/utils/tests/fixture.ts +7 -2
  88. package/src/utils/tests/mod.ts +0 -1
  89. package/dist/SqliteDbWrapper.d.ts +0 -54
  90. package/dist/SqliteDbWrapper.d.ts.map +0 -1
  91. package/dist/SqliteDbWrapper.js +0 -212
  92. package/dist/SqliteDbWrapper.js.map +0 -1
  93. package/dist/__tests__/fixture.d.ts +0 -252
  94. package/dist/__tests__/fixture.d.ts.map +0 -1
  95. package/dist/__tests__/fixture.js +0 -18
  96. package/dist/__tests__/fixture.js.map +0 -1
  97. package/dist/live-queries/db-query.d.ts +0 -67
  98. package/dist/live-queries/db-query.d.ts.map +0 -1
  99. package/dist/live-queries/db-query.js +0 -244
  100. package/dist/live-queries/db-query.js.map +0 -1
  101. package/dist/live-queries/db-query.test.d.ts +0 -2
  102. package/dist/live-queries/db-query.test.d.ts.map +0 -1
  103. package/dist/live-queries/db-query.test.js +0 -123
  104. package/dist/live-queries/db-query.test.js.map +0 -1
  105. package/dist/live-queries/make-ref.d.ts +0 -20
  106. package/dist/live-queries/make-ref.d.ts.map +0 -1
  107. package/dist/live-queries/make-ref.js +0 -33
  108. package/dist/live-queries/make-ref.js.map +0 -1
  109. package/dist/store/store.test.d.ts +0 -2
  110. package/dist/store/store.test.d.ts.map +0 -1
  111. package/dist/store/store.test.js +0 -27
  112. package/dist/store/store.test.js.map +0 -1
  113. package/dist/utils/expo.d.ts +0 -2
  114. package/dist/utils/expo.d.ts.map +0 -1
  115. package/dist/utils/expo.js +0 -8
  116. package/dist/utils/expo.js.map +0 -1
  117. package/dist/utils/function-string.d.ts +0 -7
  118. package/dist/utils/function-string.d.ts.map +0 -1
  119. package/dist/utils/function-string.js +0 -9
  120. package/dist/utils/function-string.js.map +0 -1
  121. package/src/live-queries/make-ref.ts +0 -47
  122. package/src/utils/function-string.ts +0 -12
@@ -27,44 +27,19 @@ import {
27
27
  } from '@livestore/common/schema'
28
28
  import { assertNever, isDevEnv } from '@livestore/utils'
29
29
  import type { Scope } from '@livestore/utils/effect'
30
- import {
31
- Cause,
32
- Data,
33
- Effect,
34
- Inspectable,
35
- MutableHashMap,
36
- OtelTracer,
37
- Runtime,
38
- Schema,
39
- Stream,
40
- } from '@livestore/utils/effect'
30
+ import { Cause, Data, Effect, Inspectable, MutableHashMap, Runtime, Schema } from '@livestore/utils/effect'
41
31
  import { nanoid } from '@livestore/utils/nanoid'
42
32
  import * as otel from '@opentelemetry/api'
43
33
  import { type GraphQLSchema } from 'graphql'
44
34
 
45
- import type {
46
- ILiveQueryRefDef,
47
- LiveQuery,
48
- LiveQueryDef,
49
- ReactivityGraph,
50
- ReactivityGraphContext,
51
- } from '../live-queries/base-class.js'
52
- import { makeReactivityGraph } from '../live-queries/base-class.js'
35
+ import type { LiveQuery, QueryContext, ReactivityGraph } from '../live-queries/base-class.js'
53
36
  import type { Ref } from '../reactive.js'
54
37
  import { makeExecBeforeFirstRun } from '../row-query-utils.js'
55
- import { SqliteDbWrapper } from '../SqliteDbWrapper.js'
38
+ import { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
56
39
  import { ReferenceCountedSet } from '../utils/data-structures.js'
57
40
  import { downloadBlob, exposeDebugUtils } from '../utils/dev.js'
58
41
  import { getDurationMsFromSpan } from '../utils/otel.js'
59
- import type { StackInfo } from '../utils/stack-info.js'
60
- import type {
61
- BaseGraphQLContext,
62
- RefreshReason,
63
- StoreMutateOptions,
64
- StoreOptions,
65
- StoreOtel,
66
- Unsubscribe,
67
- } from './store-types.js'
42
+ import type { BaseGraphQLContext, RefreshReason, StoreMutateOptions, StoreOptions, StoreOtel } from './store-types.js'
68
43
 
69
44
  if (isDevEnv()) {
70
45
  exposeDebugUtils()
@@ -76,7 +51,7 @@ export class Store<
76
51
  > extends Inspectable.Class {
77
52
  readonly storeId: string
78
53
  reactivityGraph: ReactivityGraph
79
- sqliteDbWrapper: SqliteDbWrapper
54
+ syncDbWrapper: SynchronousDatabaseWrapper
80
55
  clientSession: ClientSession
81
56
  schema: LiveStoreSchema
82
57
  graphQLSchema?: GraphQLSchema
@@ -86,7 +61,7 @@ export class Store<
86
61
  * Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
87
62
  * This only works in combination with `equal: () => false` which will always trigger a refresh.
88
63
  */
89
- tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> }
64
+ tableRefs: { [key: string]: Ref<null, QueryContext, RefreshReason> }
90
65
 
91
66
  private runtime: Runtime.Runtime<Scope.Scope>
92
67
 
@@ -99,13 +74,12 @@ export class Store<
99
74
  private syncProcessor: ClientSessionSyncProcessor
100
75
  readonly lifetimeScope: Scope.Scope
101
76
 
102
- readonly boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
103
-
104
77
  // #region constructor
105
- constructor({
78
+ private constructor({
106
79
  clientSession,
107
80
  schema,
108
81
  graphQLOptions,
82
+ reactivityGraph,
109
83
  otelOptions,
110
84
  disableDevtools,
111
85
  batchUpdates,
@@ -119,27 +93,29 @@ export class Store<
119
93
  this.storeId = storeId
120
94
  this.unsyncedMutationEvents = unsyncedMutationEvents
121
95
 
122
- this.sqliteDbWrapper = new SqliteDbWrapper({ otel: otelOptions, db: clientSession.sqliteDb })
96
+ this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: clientSession.syncDb })
123
97
  this.clientSession = clientSession
124
98
  this.schema = schema
125
99
 
126
100
  this.lifetimeScope = lifetimeScope
127
101
  this.runtime = runtime
128
102
 
129
- const reactivityGraph = makeReactivityGraph()
130
-
131
103
  const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext)
132
104
 
133
105
  this.syncProcessor = makeClientSessionSyncProcessor({
134
106
  schema,
135
- clientSession,
136
- runtime,
107
+ initialLeaderHead: clientSession.leaderThread.mutations.initialMutationEventId,
108
+ // rebaseBehaviour: 'auto-rebase',
109
+ pushToLeader: (batch) =>
110
+ clientSession.leaderThread.mutations.push(batch).pipe(
111
+ // NOTE we don't want to shutdown in case of an invalid push error, since it will be retried
112
+ Effect.catchTag('InvalidPushError', Effect.ignoreLogged),
113
+ this.runEffectFork,
114
+ ),
115
+ pullFromLeader: clientSession.leaderThread.mutations.pull,
137
116
  applyMutation: (mutationEventDecoded, { otelContext, withChangeset }) => {
138
117
  const mutationDef = schema.mutations.get(mutationEventDecoded.mutation)!
139
- const execArgsArr = getExecArgsFromMutation({
140
- mutationDef,
141
- mutationEvent: { decoded: mutationEventDecoded, encoded: undefined },
142
- })
118
+ const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
143
119
 
144
120
  const writeTablesForEvent = new Set<string>()
145
121
 
@@ -147,9 +123,9 @@ export class Store<
147
123
  for (const {
148
124
  statementSql,
149
125
  bindValues,
150
- writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
126
+ writeTables = this.syncDbWrapper.getTablesUsed(statementSql),
151
127
  } of execArgsArr) {
152
- this.sqliteDbWrapper.execute(statementSql, bindValues, { otelContext, writeTables })
128
+ this.syncDbWrapper.execute(statementSql, bindValues, writeTables, { otelContext })
153
129
 
154
130
  // durationMsTotal += durationMs
155
131
  writeTables.forEach((table) => writeTablesForEvent.add(table))
@@ -158,7 +134,7 @@ export class Store<
158
134
 
159
135
  let sessionChangeset: Uint8Array | undefined
160
136
  if (withChangeset === true) {
161
- sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
137
+ sessionChangeset = this.syncDbWrapper.withChangeset(exec).changeset
162
138
  } else {
163
139
  exec()
164
140
  }
@@ -166,16 +142,16 @@ export class Store<
166
142
  return { writeTables: writeTablesForEvent, sessionChangeset }
167
143
  },
168
144
  rollback: (changeset) => {
169
- this.sqliteDbWrapper.rollback(changeset)
145
+ this.syncDbWrapper.rollback(changeset)
170
146
  },
171
147
  refreshTables: (tables) => {
172
- const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
148
+ const tablesToUpdate = [] as [Ref<null, QueryContext, RefreshReason>, null][]
173
149
  for (const tableName of tables) {
174
150
  const tableRef = this.tableRefs[tableName]
175
151
  assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
176
152
  tablesToUpdate.push([tableRef!, null])
177
153
  }
178
- reactivityGraph.setRefs(tablesToUpdate)
154
+ this.reactivityGraph.setRefs(tablesToUpdate)
179
155
  },
180
156
  span: syncSpan,
181
157
  })
@@ -195,8 +171,6 @@ export class Store<
195
171
  this.reactivityGraph = reactivityGraph
196
172
  this.reactivityGraph.context = {
197
173
  store: this as unknown as Store<BaseGraphQLContext, LiveStoreSchema>,
198
- liveQueryRCMap: new Map(),
199
- reactivityGraph: new WeakRef(reactivityGraph),
200
174
  otelTracer: otelOptions.tracer,
201
175
  rootOtelContext: otelQueriesSpanContext,
202
176
  effectsWrapper: batchUpdates,
@@ -233,10 +207,10 @@ export class Store<
233
207
 
234
208
  if (graphQLOptions) {
235
209
  this.graphQLSchema = graphQLOptions.schema
236
- this.graphQLContext = graphQLOptions.makeContext(this.sqliteDbWrapper, this.otel.tracer, clientSession.sessionId)
210
+ this.graphQLContext = graphQLOptions.makeContext(this.syncDbWrapper, this.otel.tracer, clientSession.sessionId)
237
211
  }
238
212
 
239
- this.boot = Effect.gen(this, function* () {
213
+ Effect.gen(this, function* () {
240
214
  yield* Effect.addFinalizer(() =>
241
215
  Effect.sync(() => {
242
216
  // Remove all table refs from the reactivity graph
@@ -254,10 +228,24 @@ export class Store<
254
228
  )
255
229
 
256
230
  yield* this.syncProcessor.boot
257
- })
231
+ }).pipe(this.runEffectFork)
258
232
  }
259
233
  // #endregion constructor
260
234
 
235
+ static createStore = <TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema = LiveStoreSchema>(
236
+ storeOptions: StoreOptions<TGraphQLContext, TSchema>,
237
+ parentSpan: otel.Span,
238
+ ): Store<TGraphQLContext, TSchema> => {
239
+ const ctx = otel.trace.setSpan(otel.context.active(), parentSpan)
240
+ return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:createStore', {}, ctx, (span) => {
241
+ try {
242
+ return new Store(storeOptions)
243
+ } finally {
244
+ span.end()
245
+ }
246
+ })
247
+ }
248
+
261
249
  get sessionId(): string {
262
250
  return this.clientSession.sessionId
263
251
  }
@@ -265,66 +253,29 @@ export class Store<
265
253
  /**
266
254
  * Subscribe to the results of a query
267
255
  * Returns a function to cancel the subscription.
268
- *
269
- * @example
270
- * ```ts
271
- * const unsubscribe = store.subscribe(query$, { onUpdate: (result) => console.log(result) })
272
- * ```
273
256
  */
274
257
  subscribe = <TResult>(
275
- query: LiveQueryDef<TResult, any> | LiveQuery<TResult, any>,
276
- options: {
277
- /** Called when the query result has changed */
278
- onUpdate: (value: TResult) => void
279
- onSubscribe?: (query$: LiveQuery<TResult, any>) => void
280
- /** Gets called after the query subscription has been removed */
281
- onUnsubsubscribe?: () => void
282
- label?: string
283
- /**
284
- * Skips the initial `onUpdate` callback
285
- * @default false
286
- */
287
- skipInitialRun?: boolean
288
- otelContext?: otel.Context
289
- /** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
290
- stackInfo?: StackInfo
291
- },
292
- ): Unsubscribe =>
258
+ query$: LiveQuery<TResult, any>,
259
+ onNewValue: (value: TResult) => void,
260
+ onUnsubsubscribe?: () => void,
261
+ options?: { label?: string; otelContext?: otel.Context; skipInitialRun?: boolean } | undefined,
262
+ ): (() => void) =>
293
263
  this.otel.tracer.startActiveSpan(
294
264
  `LiveStore.subscribe`,
295
- { attributes: { label: options?.label, queryLabel: query.label } },
265
+ { attributes: { label: options?.label, queryLabel: query$.label } },
296
266
  options?.otelContext ?? this.otel.queriesSpanContext,
297
267
  (span) => {
298
268
  // console.debug('store sub', query$.id, query$.label)
299
269
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
300
270
 
301
- const queryRcRef =
302
- query._tag === 'def'
303
- ? query.make(this.reactivityGraph.context!)
304
- : {
305
- value: query,
306
- deref: () => {},
307
- }
308
- const query$ = queryRcRef.value
309
-
310
271
  const label = `subscribe:${options?.label}`
311
- const effect = this.reactivityGraph.makeEffect(
312
- (get, _otelContext, debugRefreshReason) =>
313
- options.onUpdate(get(query$.results$, otelContext, debugRefreshReason)),
314
- { label },
315
- )
316
-
317
- if (options?.stackInfo) {
318
- query$.activeSubscriptions.add(options.stackInfo)
319
- }
320
-
321
- options?.onSubscribe?.(query$)
272
+ const effect = this.reactivityGraph.makeEffect((get) => onNewValue(get(query$.results$)), { label })
322
273
 
323
274
  this.activeQueries.add(query$ as LiveQuery<TResult>)
324
275
 
325
276
  // Running effect right away to get initial value (unless `skipInitialRun` is set)
326
- if (options?.skipInitialRun !== true && !query$.isDestroyed) {
327
- effect.doEffect(otelContext, { _tag: 'subscribe.initial', label: `subscribe-initial-run:${options?.label}` })
277
+ if (options?.skipInitialRun !== true) {
278
+ effect.doEffect(otelContext)
328
279
  }
329
280
 
330
281
  const unsubscribe = () => {
@@ -332,14 +283,7 @@ export class Store<
332
283
  try {
333
284
  this.reactivityGraph.destroyNode(effect)
334
285
  this.activeQueries.remove(query$ as LiveQuery<TResult>)
335
-
336
- if (options?.stackInfo) {
337
- query$.activeSubscriptions.delete(options.stackInfo)
338
- }
339
-
340
- queryRcRef.deref()
341
-
342
- options?.onUnsubsubscribe?.()
286
+ onUnsubsubscribe?.()
343
287
  } finally {
344
288
  span.end()
345
289
  }
@@ -349,30 +293,6 @@ export class Store<
349
293
  },
350
294
  )
351
295
 
352
- subscribeStream = <TResult>(
353
- query$: LiveQueryDef<TResult, any>,
354
- options?: { label?: string; skipInitialRun?: boolean } | undefined,
355
- ): Stream.Stream<TResult> =>
356
- Stream.asyncPush<TResult>((emit) =>
357
- Effect.gen(this, function* () {
358
- const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(
359
- Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)),
360
- )
361
- const otelContext = otelSpan ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active()
362
-
363
- yield* Effect.acquireRelease(
364
- Effect.sync(() =>
365
- this.subscribe(query$, {
366
- onUpdate: (result) => emit.single(result),
367
- otelContext,
368
- label: options?.label,
369
- }),
370
- ),
371
- (unsub) => Effect.sync(() => unsub()),
372
- )
373
- }),
374
- )
375
-
376
296
  /**
377
297
  * Synchronously queries the database without creating a LiveQuery.
378
298
  * This is useful for queries that don't need to be reactive.
@@ -388,15 +308,12 @@ export class Store<
388
308
  * ```
389
309
  */
390
310
  query = <TResult>(
391
- query:
392
- | QueryBuilder<TResult, any, any>
393
- | LiveQuery<TResult, any>
394
- | LiveQueryDef<TResult, any>
395
- | { query: string; bindValues: ParamsObject },
396
- options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
311
+ query: QueryBuilder<TResult, any, any> | LiveQuery<TResult, any> | { query: string; bindValues: ParamsObject },
312
+ options?: { otelContext?: otel.Context },
397
313
  ): TResult => {
398
314
  if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
399
- return this.sqliteDbWrapper.select(query.query, prepareBindValues(query.bindValues, query.query), {
315
+ return this.syncDbWrapper.select(query.query, {
316
+ bindValues: prepareBindValues(query.bindValues, query.query),
400
317
  otelContext: options?.otelContext,
401
318
  }) as any
402
319
  } else if (isQueryBuilder(query)) {
@@ -412,38 +329,17 @@ export class Store<
412
329
 
413
330
  const sqlRes = query.asSql()
414
331
  const schema = getResultSchema(query)
415
- const rawRes = this.sqliteDbWrapper.select(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
332
+ const rawRes = this.syncDbWrapper.select(sqlRes.query, {
333
+ bindValues: sqlRes.bindValues as any as PreparedBindValues,
416
334
  otelContext: options?.otelContext,
417
335
  queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
418
336
  })
419
337
  return Schema.decodeSync(schema)(rawRes)
420
- } else if (query._tag === 'def') {
421
- const query$ = query.make(this.reactivityGraph.context!)
422
- const result = this.query(query$.value, options)
423
- query$.deref()
424
- return result
425
338
  } else {
426
- return query.run({ otelContext: options?.otelContext, debugRefreshReason: options?.debugRefreshReason })
339
+ return query.run(options?.otelContext)
427
340
  }
428
341
  }
429
342
 
430
- // makeLive: {
431
- // <T>(def: LiveQueryDef<T, any>): LiveQuery<T, any>
432
- // <T>(def: ILiveQueryRefDef<T>): ILiveQueryRef<T>
433
- // } = (def: any) => {
434
- // if (def._tag === 'live-ref-def') {
435
- // return (def as ILiveQueryRefDef<any>).make(this.reactivityGraph.context!)
436
- // } else {
437
- // return (def as LiveQueryDef<any, any>).make(this.reactivityGraph.context!) as any
438
- // }
439
- // }
440
-
441
- setRef = <T>(refDef: ILiveQueryRefDef<T>, value: T): void => {
442
- const ref = refDef.make(this.reactivityGraph.context!)
443
- ref.value.set(value)
444
- ref.deref()
445
- }
446
-
447
343
  // #region mutate
448
344
  mutate: {
449
345
  <const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(...list: TMutationArg): void
@@ -471,6 +367,7 @@ export class Store<
471
367
 
472
368
  if (mutationsEvents.length === 0) return
473
369
 
370
+ const label = options?.label ?? 'mutate'
474
371
  const skipRefresh = options?.skipRefresh ?? false
475
372
 
476
373
  const mutationsSpan = otel.trace.getSpan(this.otel.mutationsSpanContext)!
@@ -484,39 +381,40 @@ export class Store<
484
381
 
485
382
  return this.otel.tracer.startActiveSpan(
486
383
  'LiveStore:mutate',
487
- {
488
- attributes: {
489
- 'livestore.mutationEventsCount': mutationsEvents.length,
490
- 'livestore.mutationEventTags': mutationsEvents.map((_) => _.mutation),
491
- 'livestore.mutateLabel': options?.label,
492
- },
493
- links: options?.spanLinks,
494
- },
384
+ { attributes: { 'livestore.mutateLabel': label }, links: options?.spanLinks },
495
385
  options?.otelContext ?? this.otel.mutationsSpanContext,
496
386
  (span) => {
497
387
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
498
388
 
499
389
  try {
500
- const { writeTables } = (() => {
501
- try {
502
- const applyMutations = () => this.syncProcessor.push(mutationsEvents, { otelContext })
503
-
504
- if (mutationsEvents.length > 1) {
505
- // TODO: what to do about leader transaction here?
506
- return this.sqliteDbWrapper.txn(applyMutations)
507
- } else {
508
- return applyMutations()
390
+ const { writeTables } = this.otel.tracer.startActiveSpan(
391
+ 'LiveStore:mutate:applyMutations',
392
+ { attributes: { 'livestore.mutateLabel': label } },
393
+ otel.trace.setSpan(otel.context.active(), span),
394
+ (span) => {
395
+ try {
396
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
397
+ // 5
398
+
399
+ const applyMutations = () => this.syncProcessor.push(mutationsEvents, { otelContext })
400
+
401
+ if (mutationsEvents.length > 1) {
402
+ // TODO: what to do about leader transaction here?
403
+ return this.syncDbWrapper.txn(applyMutations)
404
+ } else {
405
+ return applyMutations()
406
+ }
407
+ } catch (e: any) {
408
+ console.error(e)
409
+ span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
410
+ throw e
411
+ } finally {
412
+ span.end()
509
413
  }
510
- } catch (e: any) {
511
- console.error(e)
512
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
513
- throw e
514
- } finally {
515
- span.end()
516
- }
517
- })()
414
+ },
415
+ )
518
416
 
519
- const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
417
+ const tablesToUpdate = [] as [Ref<null, QueryContext, RefreshReason>, null][]
520
418
  for (const tableName of writeTables) {
521
419
  const tableRef = this.tableRefs[tableName]
522
420
  assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
@@ -572,48 +470,41 @@ export class Store<
572
470
  meta: { liveStoreRefType: 'table' },
573
471
  })
574
472
 
575
- /**
576
- * Helper methods useful during development
577
- *
578
- * @internal
579
- */
580
- _dev = {
581
- downloadDb: (source: 'local' | 'leader' = 'local') => {
582
- Effect.gen(this, function* () {
583
- const data = source === 'local' ? this.sqliteDbWrapper.export() : yield* this.clientSession.leaderThread.export
584
- downloadBlob(data, `livestore-${Date.now()}.db`)
585
- }).pipe(this.runEffectFork)
586
- },
587
-
588
- downloadMutationLogDb: () => {
589
- Effect.gen(this, function* () {
590
- const data = yield* this.clientSession.leaderThread.getMutationLogData
591
- downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
592
- }).pipe(this.runEffectFork)
593
- },
594
-
595
- hardReset: (mode: 'all-data' | 'only-app-db' = 'all-data') => {
596
- Effect.gen(this, function* () {
597
- yield* this.clientSession.leaderThread.sendDevtoolsMessage(
598
- Devtools.ResetAllDataReq.make({ liveStoreVersion, mode, requestId: nanoid() }),
599
- )
600
- }).pipe(this.runEffectFork)
601
- },
602
-
603
- syncStates: () => {
604
- Effect.gen(this, function* () {
605
- const session = this.syncProcessor.syncStateRef.current
606
- console.log('Session sync state:', session.toJSON())
607
- const leader = yield* this.clientSession.leaderThread.getSyncState
608
- console.log('Leader sync state:', leader.toJSON())
609
- }).pipe(this.runEffectFork)
610
- },
611
-
612
- shutdown: (cause?: Cause.Cause<UnexpectedError>) => {
613
- this.clientSession
614
- .shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
615
- .pipe(Effect.tapCauseLogPretty, Effect.provide(this.runtime), Effect.runFork)
616
- },
473
+ __devDownloadDb = (source: 'local' | 'leader' = 'local') => {
474
+ Effect.gen(this, function* () {
475
+ const data = source === 'local' ? this.syncDbWrapper.export() : yield* this.clientSession.leaderThread.export
476
+ downloadBlob(data, `livestore-${Date.now()}.db`)
477
+ }).pipe(this.runEffectFork)
478
+ }
479
+
480
+ __devDownloadMutationLogDb = () => {
481
+ Effect.gen(this, function* () {
482
+ const data = yield* this.clientSession.leaderThread.getMutationLogData
483
+ downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
484
+ }).pipe(this.runEffectFork)
485
+ }
486
+
487
+ __devHardReset = (mode: 'all-data' | 'only-app-db' = 'all-data') => {
488
+ Effect.gen(this, function* () {
489
+ yield* this.clientSession.leaderThread.sendDevtoolsMessage(
490
+ Devtools.ResetAllDataReq.make({ liveStoreVersion, mode, requestId: nanoid() }),
491
+ )
492
+ }).pipe(this.runEffectFork)
493
+ }
494
+
495
+ __devSyncStates = () => {
496
+ Effect.gen(this, function* () {
497
+ const session = this.syncProcessor.syncStateRef.current
498
+ console.log('Session sync state:', session)
499
+ const leader = yield* this.clientSession.leaderThread.getSyncState
500
+ console.log('Leader sync state:', leader)
501
+ }).pipe(this.runEffectFork)
502
+ }
503
+
504
+ __devShutdown = (cause?: Cause.Cause<UnexpectedError>) => {
505
+ this.clientSession
506
+ .shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
507
+ .pipe(Effect.tapCauseLogPretty, Effect.provide(this.runtime), Effect.runFork)
617
508
  }
618
509
 
619
510
  // NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
@@ -46,7 +46,7 @@ Error
46
46
 
47
47
  it('Tracklist_ stacktrace', async () => {
48
48
  const stackTrace = `\
49
- Error
49
+ stack Error
50
50
  at https://localhost:8081/@fs/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/livestore/dist/react/useQuery.js?t=1701368568351:19:23
51
51
  at mountMemo (https://localhost:8081/node_modules/.vite-web/deps/chunk-YKTDXTVC.js?v=86daed82:12817:27)
52
52
  at Object.useMemo (https://localhost:8081/node_modules/.vite-web/deps/chunk-YKTDXTVC.js?v=86daed82:13141:24)
@@ -77,60 +77,3 @@ Error
77
77
  }
78
78
  `)
79
79
  })
80
-
81
- it('React 19', async () => {
82
- const stackTrace = `\
83
- Error:
84
- at /Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:57:19
85
- at mountMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:6816:23)
86
- at Object.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22757:18)
87
- at Object.process.env.NODE_ENV.exports.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react@19.0.0/node_modules/react/cjs/react.development.js:1488:34)
88
- at Module.useQueryRef (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:54:27)
89
- at Module.useRow (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20)
90
- at TestComponent (/Users/schickling/Code/overtone/node_modules/.pnpm/@testing-library+react@16.1.0_@testing-library+dom@10.4.0_@types+react-dom@19.0.3_@types+reac_2jaiibiag2sxou3wtzbuqx3r5a/node_modules/@testing-library/react/dist/pure.js:309:27)
91
- at Object.react-stack-bottom-frame (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22428:20)
92
- at renderWithHooks (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:5757:22)
93
- `
94
-
95
- const stackInfo = extractStackInfoFromStackTrace(stackTrace)
96
- expect(stackInfo).toMatchInlineSnapshot(`
97
- {
98
- "frames": [
99
- {
100
- "filePath": "/Users/schickling/Code/overtone/node_modules/.pnpm/@testing-library+react@16.1.0_@testing-library+dom@10.4.0_@types+react-dom@19.0.3_@types+reac_2jaiibiag2sxou3wtzbuqx3r5a/node_modules/@testing-library/react/dist/pure.js:309:27",
101
- "name": "TestComponent",
102
- },
103
- {
104
- "filePath": "/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20",
105
- "name": "useRow",
106
- },
107
- ],
108
- }
109
- `)
110
- })
111
-
112
- it('React 19 - skip react-stack-bottom-frame', async () => {
113
- const stackTrace = `\
114
- Error:
115
- at /Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:57:19
116
- at mountMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:6816:23)
117
- at Object.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22757:18)
118
- at Object.process.env.NODE_ENV.exports.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react@19.0.0/node_modules/react/cjs/react.development.js:1488:34)
119
- at Module.useQueryRef (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:54:27)
120
- at Module.useRow (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20)
121
- at Object.react-stack-bottom-frame (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22428:20)
122
- at renderWithHooks (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:5757:22)
123
- `
124
-
125
- const stackInfo = extractStackInfoFromStackTrace(stackTrace)
126
- expect(stackInfo).toMatchInlineSnapshot(`
127
- {
128
- "frames": [
129
- {
130
- "filePath": "/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20",
131
- "name": "useRow",
132
- },
133
- ],
134
- }
135
- `)
136
- })
@@ -34,20 +34,15 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
34
34
 
35
35
  while ((match = namePattern.exec(stackTrace)) !== null) {
36
36
  const [, name, filePath] = match as any as [string, string, string]
37
- // console.debug(name, filePath)
38
37
 
39
38
  // NOTE No idea where this `Module.` comes from - possibly a Vite thing?
40
39
  if ((name.startsWith('use') || name.startsWith('Module.use')) && name.endsWith('QueryRef') === false) {
41
40
  hasReachedStart = true
42
- // console.debug('hasReachedStart. adding one more frame.')
43
41
 
44
42
  frames.unshift({ name: name.replace(/^Module\./, ''), filePath })
45
43
  } else if (hasReachedStart) {
46
44
  // We've reached the end of the `use*` functions, so we're adding the component name and stop
47
- // Unless it's `react-stack-bottom-frame`, which we skip
48
- if (name !== 'Object.react-stack-bottom-frame') {
49
- frames.unshift({ name, filePath })
50
- }
45
+ frames.unshift({ name, filePath })
51
46
  break
52
47
  }
53
48
  }