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

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 (114) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SqliteDbWrapper.d.ts +54 -0
  3. package/dist/SqliteDbWrapper.d.ts.map +1 -0
  4. package/dist/SqliteDbWrapper.js +212 -0
  5. package/dist/SqliteDbWrapper.js.map +1 -0
  6. package/dist/SynchronousDatabaseWrapper.d.ts +14 -5
  7. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  8. package/dist/SynchronousDatabaseWrapper.js +24 -4
  9. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  10. package/dist/effect/LiveStore.d.ts +12 -8
  11. package/dist/effect/LiveStore.d.ts.map +1 -1
  12. package/dist/effect/LiveStore.js +9 -2
  13. package/dist/effect/LiveStore.js.map +1 -1
  14. package/dist/index.d.ts +6 -7
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +4 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/live-queries/base-class.d.ts +64 -21
  19. package/dist/live-queries/base-class.d.ts.map +1 -1
  20. package/dist/live-queries/base-class.js +56 -13
  21. package/dist/live-queries/base-class.js.map +1 -1
  22. package/dist/live-queries/computed.d.ts +7 -7
  23. package/dist/live-queries/computed.d.ts.map +1 -1
  24. package/dist/live-queries/computed.js +35 -11
  25. package/dist/live-queries/computed.js.map +1 -1
  26. package/dist/live-queries/db-query.d.ts +67 -0
  27. package/dist/live-queries/db-query.d.ts.map +1 -0
  28. package/dist/live-queries/db-query.js +244 -0
  29. package/dist/live-queries/db-query.js.map +1 -0
  30. package/dist/live-queries/db-query.test.d.ts +2 -0
  31. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  32. package/dist/live-queries/db-query.test.js +123 -0
  33. package/dist/live-queries/db-query.test.js.map +1 -0
  34. package/dist/live-queries/db.d.ts +12 -15
  35. package/dist/live-queries/db.d.ts.map +1 -1
  36. package/dist/live-queries/db.js +44 -25
  37. package/dist/live-queries/db.js.map +1 -1
  38. package/dist/live-queries/db.test.js +16 -14
  39. package/dist/live-queries/db.test.js.map +1 -1
  40. package/dist/live-queries/graphql.d.ts +8 -8
  41. package/dist/live-queries/graphql.d.ts.map +1 -1
  42. package/dist/live-queries/graphql.js +35 -9
  43. package/dist/live-queries/graphql.js.map +1 -1
  44. package/dist/live-queries/make-ref.d.ts +20 -0
  45. package/dist/live-queries/make-ref.d.ts.map +1 -0
  46. package/dist/live-queries/make-ref.js +33 -0
  47. package/dist/live-queries/make-ref.js.map +1 -0
  48. package/dist/reactive.d.ts +15 -13
  49. package/dist/reactive.d.ts.map +1 -1
  50. package/dist/reactive.js +15 -9
  51. package/dist/reactive.js.map +1 -1
  52. package/dist/row-query-utils.d.ts +4 -4
  53. package/dist/row-query-utils.d.ts.map +1 -1
  54. package/dist/row-query-utils.js +14 -10
  55. package/dist/row-query-utils.js.map +1 -1
  56. package/dist/store/create-store.d.ts +3 -4
  57. package/dist/store/create-store.d.ts.map +1 -1
  58. package/dist/store/create-store.js +7 -7
  59. package/dist/store/create-store.js.map +1 -1
  60. package/dist/store/devtools.d.ts +2 -2
  61. package/dist/store/devtools.d.ts.map +1 -1
  62. package/dist/store/devtools.js +15 -15
  63. package/dist/store/devtools.js.map +1 -1
  64. package/dist/store/store-types.d.ts +9 -4
  65. package/dist/store/store-types.d.ts.map +1 -1
  66. package/dist/store/store-types.js.map +1 -1
  67. package/dist/store/store.d.ts +34 -16
  68. package/dist/store/store.d.ts.map +1 -1
  69. package/dist/store/store.js +125 -75
  70. package/dist/store/store.js.map +1 -1
  71. package/dist/utils/expo.d.ts +2 -0
  72. package/dist/utils/expo.d.ts.map +1 -0
  73. package/dist/utils/expo.js +8 -0
  74. package/dist/utils/expo.js.map +1 -0
  75. package/dist/utils/function-string.d.ts +7 -0
  76. package/dist/utils/function-string.d.ts.map +1 -0
  77. package/dist/utils/function-string.js +9 -0
  78. package/dist/utils/function-string.js.map +1 -0
  79. package/dist/utils/stack-info.d.ts.map +1 -1
  80. package/dist/utils/stack-info.js +6 -1
  81. package/dist/utils/stack-info.js.map +1 -1
  82. package/dist/utils/stack-info.test.js +54 -1
  83. package/dist/utils/stack-info.test.js.map +1 -1
  84. package/dist/utils/tests/fixture.d.ts +2 -6
  85. package/dist/utils/tests/fixture.d.ts.map +1 -1
  86. package/dist/utils/tests/fixture.js +3 -5
  87. package/dist/utils/tests/fixture.js.map +1 -1
  88. package/dist/utils/tests/mod.d.ts +1 -0
  89. package/dist/utils/tests/mod.d.ts.map +1 -1
  90. package/dist/utils/tests/mod.js +1 -0
  91. package/dist/utils/tests/mod.js.map +1 -1
  92. package/package.json +5 -5
  93. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -11
  94. package/src/effect/LiveStore.ts +22 -14
  95. package/src/index.ts +14 -7
  96. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -42
  97. package/src/live-queries/base-class.ts +160 -40
  98. package/src/live-queries/computed.ts +45 -19
  99. package/src/live-queries/{db.test.ts → db-query.test.ts} +21 -11
  100. package/src/live-queries/{db.ts → db-query.ts} +97 -39
  101. package/src/live-queries/graphql.ts +47 -21
  102. package/src/live-queries/make-ref.ts +47 -0
  103. package/src/reactive.ts +52 -27
  104. package/src/row-query-utils.ts +29 -18
  105. package/src/store/create-store.ts +20 -23
  106. package/src/store/devtools.ts +17 -17
  107. package/src/store/store-types.ts +6 -4
  108. package/src/store/store.ts +227 -120
  109. package/src/utils/function-string.ts +12 -0
  110. package/src/utils/stack-info.test.ts +58 -1
  111. package/src/utils/stack-info.ts +6 -1
  112. package/src/utils/tests/fixture.ts +2 -7
  113. package/src/utils/tests/mod.ts +1 -0
  114. package/src/global-state.ts +0 -20
package/src/reactive.ts CHANGED
@@ -32,7 +32,11 @@ import type * as otel from '@opentelemetry/api'
32
32
  export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
33
33
  export type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
34
34
 
35
- export type GetAtom = <T>(atom: Atom<T, any, any>, otelContext?: otel.Context) => T
35
+ export type GetAtom = <T>(
36
+ atom: Atom<T, any, any>,
37
+ otelContext?: otel.Context | undefined,
38
+ debugRefreshReason?: TODO | undefined,
39
+ ) => T
36
40
 
37
41
  export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
38
42
  _tag: 'ref'
@@ -42,7 +46,7 @@ export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
42
46
  previousResult: T
43
47
  computeResult: () => T
44
48
  sub: Set<Atom<any, TContext, TDebugRefreshReason>> // always empty
45
- super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect>
49
+ super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
46
50
  label?: string
47
51
  /** Container for meta information (e.g. the LiveStore Store) */
48
52
  meta?: any
@@ -58,7 +62,7 @@ export type Thunk<TResult, TContext, TDebugRefreshReason extends DebugRefreshRea
58
62
  computeResult: (otelContext?: otel.Context, debugRefreshReason?: TDebugRefreshReason) => TResult
59
63
  previousResult: TResult | NOT_REFRESHED_YET
60
64
  sub: Set<Atom<any, TContext, TDebugRefreshReason>>
61
- super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect>
65
+ super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
62
66
  label?: string
63
67
  /** Container for meta information (e.g. the LiveStore Store) */
64
68
  meta?: any
@@ -72,11 +76,11 @@ export type Atom<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
72
76
  | Ref<T, TContext, TDebugRefreshReason>
73
77
  | Thunk<T, TContext, TDebugRefreshReason>
74
78
 
75
- export type Effect = {
79
+ export type Effect<TDebugRefreshReason extends DebugRefreshReason> = {
76
80
  _tag: 'effect'
77
81
  id: string
78
82
  isDestroyed: boolean
79
- doEffect: (otelContext?: otel.Context) => void
83
+ doEffect: (otelContext?: otel.Context | undefined, debugRefreshReason?: TDebugRefreshReason | undefined) => void
80
84
  sub: Set<Atom<any, TODO, TODO>>
81
85
  label?: string
82
86
  invocations: number
@@ -84,7 +88,7 @@ export type Effect = {
84
88
 
85
89
  export type Node<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
86
90
  | Atom<T, TContext, TDebugRefreshReason>
87
- | Effect
91
+ | Effect<TDebugRefreshReason>
88
92
 
89
93
  export const isThunk = <T, TContext, TDebugRefreshReason extends DebugRefreshReason>(
90
94
  obj: unknown,
@@ -166,7 +170,7 @@ export type SerializedThunk = Readonly<
166
170
 
167
171
  export type SerializedEffect = Readonly<
168
172
  PrettifyFlat<
169
- Pick<Effect, '_tag' | 'id' | 'label' | 'invocations' | 'isDestroyed'> & {
173
+ Pick<Effect<any>, '_tag' | 'id' | 'label' | 'invocations' | 'isDestroyed'> & {
170
174
  sub: ReadonlyArray<string>
171
175
  }
172
176
  >
@@ -187,6 +191,13 @@ const uniqueRefreshInfoId = () => `refresh-info-${++refreshInfoIdCounter}`
187
191
  let globalGraphIdCounter = 0
188
192
  const uniqueGraphId = () => `graph-${++globalGraphIdCounter}`
189
193
 
194
+ /** Used for testing */
195
+ export const __resetIds = () => {
196
+ nodeIdCounter = 0
197
+ refreshInfoIdCounter = 0
198
+ globalGraphIdCounter = 0
199
+ }
200
+
190
201
  export class ReactiveGraph<
191
202
  TDebugRefreshReason extends DebugRefreshReason,
192
203
  TDebugThunkInfo extends DebugThunkInfo,
@@ -195,7 +206,7 @@ export class ReactiveGraph<
195
206
  id = uniqueGraphId()
196
207
 
197
208
  readonly atoms: Set<Atom<any, TContext, TDebugRefreshReason>> = new Set()
198
- readonly effects: Set<Effect> = new Set()
209
+ readonly effects: Set<Effect<TDebugRefreshReason>> = new Set()
199
210
 
200
211
  context: TContext | undefined
201
212
 
@@ -205,7 +216,7 @@ export class ReactiveGraph<
205
216
  | { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
206
217
  | undefined
207
218
 
208
- private deferredEffects: Map<Effect, Set<TDebugRefreshReason>> = new Map()
219
+ private deferredEffects: Map<Effect<TDebugRefreshReason>, Set<TDebugRefreshReason>> = new Map()
209
220
 
210
221
  private refreshCallbacks: Set<() => void> = new Set()
211
222
 
@@ -239,6 +250,7 @@ export class ReactiveGraph<
239
250
  setDebugInfo: (debugInfo: TDebugThunkInfo) => void,
240
251
  ctx: TContext,
241
252
  otelContext: otel.Context | undefined,
253
+ debugRefreshReason: TDebugRefreshReason | undefined,
242
254
  ) => T,
243
255
  options?:
244
256
  | {
@@ -266,7 +278,7 @@ export class ReactiveGraph<
266
278
 
267
279
  const getAtom = (atom: Atom<T, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
268
280
  this.addEdge(thunk, atom)
269
- return compute(atom, otelContext)
281
+ return compute(atom, otelContext, debugRefreshReason)
270
282
  }
271
283
 
272
284
  let debugInfo: TDebugThunkInfo | undefined = undefined
@@ -279,6 +291,7 @@ export class ReactiveGraph<
279
291
  setDebugInfo,
280
292
  this.context ?? throwContextNotSetError(this),
281
293
  otelContext,
294
+ debugRefreshReason,
282
295
  )
283
296
 
284
297
  const resultChanged = thunk.equal(thunk.previousResult as T, result) === false
@@ -365,14 +378,18 @@ export class ReactiveGraph<
365
378
  }
366
379
 
367
380
  makeEffect(
368
- doEffect: (get: GetAtom, otelContext?: otel.Context) => void,
381
+ doEffect: (
382
+ get: GetAtom,
383
+ otelContext: otel.Context | undefined,
384
+ debugRefreshReason: DebugRefreshReason | undefined,
385
+ ) => void,
369
386
  options?: { label?: string } | undefined,
370
- ): Effect {
371
- const effect: Effect = {
387
+ ): Effect<TDebugRefreshReason> {
388
+ const effect: Effect<TDebugRefreshReason> = {
372
389
  _tag: 'effect',
373
390
  id: uniqueNodeId(),
374
391
  isDestroyed: false,
375
- doEffect: (otelContext) => {
392
+ doEffect: (otelContext, debugRefreshReason) => {
376
393
  effect.invocations++
377
394
 
378
395
  // NOTE we're not tracking any debug refresh info for effects as they're tracked by the thunks they depend on
@@ -380,12 +397,16 @@ export class ReactiveGraph<
380
397
  // Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
381
398
  effect.sub = new Set()
382
399
 
383
- const getAtom = (atom: Atom<any, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
400
+ const getAtom = (
401
+ atom: Atom<any, TContext, TDebugRefreshReason>,
402
+ otelContext: otel.Context,
403
+ debugRefreshReason: DebugRefreshReason | undefined,
404
+ ) => {
384
405
  this.addEdge(effect, atom)
385
- return compute(atom, otelContext)
406
+ return compute(atom, otelContext, debugRefreshReason)
386
407
  }
387
408
 
388
- doEffect(getAtom as GetAtom, otelContext)
409
+ doEffect(getAtom as GetAtom, otelContext, debugRefreshReason)
389
410
  },
390
411
  sub: new Set(),
391
412
  label: options?.label,
@@ -421,7 +442,7 @@ export class ReactiveGraph<
421
442
  }
422
443
  | undefined,
423
444
  ) {
424
- const effectsToRefresh = new Set<Effect>()
445
+ const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
425
446
  for (const [ref, val] of refs) {
426
447
  ref.previousResult = val
427
448
  ref.refreshes++
@@ -448,7 +469,7 @@ export class ReactiveGraph<
448
469
  }
449
470
 
450
471
  private runEffects = (
451
- effectsToRefresh: Set<Effect>,
472
+ effectsToRefresh: Set<Effect<TDebugRefreshReason>>,
452
473
  options: {
453
474
  debugRefreshReason: TDebugRefreshReason
454
475
  otelContext?: otel.Context
@@ -459,7 +480,7 @@ export class ReactiveGraph<
459
480
  this.currentDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
460
481
 
461
482
  for (const effect of effectsToRefresh) {
462
- effect.doEffect(options?.otelContext)
483
+ effect.doEffect(options?.otelContext, options.debugRefreshReason)
463
484
  }
464
485
 
465
486
  const refreshedAtoms = this.currentDebugRefresh.refreshedAtoms
@@ -504,7 +525,7 @@ export class ReactiveGraph<
504
525
  }
505
526
 
506
527
  addEdge(
507
- superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect,
528
+ superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
508
529
  subComp: Atom<any, TContext, TDebugRefreshReason>,
509
530
  ) {
510
531
  superComp.sub.add(subComp)
@@ -516,11 +537,11 @@ export class ReactiveGraph<
516
537
  }
517
538
 
518
539
  removeEdge(
519
- superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect,
540
+ superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
520
541
  subComp: Atom<any, TContext, TDebugRefreshReason>,
521
542
  ) {
522
543
  superComp.sub.delete(subComp)
523
- const effectsToRefresh = new Set<Effect>()
544
+ const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
524
545
  markSuperCompDirtyRec(subComp, effectsToRefresh)
525
546
 
526
547
  for (const effect of effectsToRefresh) {
@@ -563,7 +584,11 @@ export class ReactiveGraph<
563
584
  }
564
585
  }
565
586
 
566
- const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T => {
587
+ const compute = <T>(
588
+ atom: Atom<T, unknown, any>,
589
+ otelContext: otel.Context,
590
+ debugRefreshReason: DebugRefreshReason | undefined,
591
+ ): T => {
567
592
  // const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
568
593
  if (atom.isDestroyed) {
569
594
  shouldNeverHappen(`LiveStore Error: Attempted to compute destroyed ${atom._tag} (${atom.id}): ${atom.label ?? ''}`)
@@ -571,7 +596,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
571
596
 
572
597
  if (atom.isDirty) {
573
598
  // console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
574
- const result = atom.computeResult(otelContext)
599
+ const result = atom.computeResult(otelContext, debugRefreshReason)
575
600
  atom.isDirty = false
576
601
  atom.previousResult = result
577
602
  return result
@@ -581,7 +606,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
581
606
  }
582
607
  }
583
608
 
584
- const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect>) => {
609
+ const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect<any>>) => {
585
610
  for (const superComp of atom.super) {
586
611
  if (superComp._tag === 'thunk') {
587
612
  superComp.isDirty = true
@@ -644,7 +669,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
644
669
  }
645
670
 
646
671
  // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
647
- const serializeEffect = (effect: Effect): SerializedEffect => {
672
+ const serializeEffect = (effect: Effect<any>): SerializedEffect => {
648
673
  const sub: string[] = []
649
674
  for (const a of effect.sub) {
650
675
  sub.push(a.id)
@@ -4,28 +4,33 @@ import { DbSchema } from '@livestore/common/schema'
4
4
  import { shouldNeverHappen } from '@livestore/utils'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
 
7
- import type { LiveQuery, LiveQueryAny, QueryContext } from './live-queries/base-class.js'
7
+ import type { GetResult, LiveQueryDef, ReactivityGraphContext } from './live-queries/base-class.js'
8
8
  import { computed } from './live-queries/computed.js'
9
9
 
10
10
  export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | undefined) =>
11
11
  `row:${table.sqliteDef.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`
12
12
 
13
13
  export const deriveColQuery: {
14
- <TQuery extends LiveQuery<any, QueryInfo.None>, TCol extends keyof TQuery['__result!'] & string>(
15
- query$: TQuery,
14
+ <TQueryDef extends LiveQueryDef<any, QueryInfo.None>, TCol extends keyof GetResult<TQueryDef> & string>(
15
+ queryDef: TQueryDef,
16
16
  colName: TCol,
17
- ): LiveQuery<TQuery['__result!'][TCol], QueryInfo.None>
18
- <TQuery extends LiveQuery<any, QueryInfo.Row>, TCol extends keyof TQuery['__result!'] & string>(
19
- query$: TQuery,
17
+ ): LiveQueryDef<GetResult<TQueryDef>[TCol], QueryInfo.None>
18
+ <TQueryDef extends LiveQueryDef<any, QueryInfo.Row>, TCol extends keyof GetResult<TQueryDef> & string>(
19
+ queryDef: TQueryDef,
20
20
  colName: TCol,
21
- ): LiveQuery<TQuery['__result!'][TCol], QueryInfo.Col>
22
- } = (query$: LiveQueryAny, colName: string) => {
23
- return computed((get) => get(query$)[colName], {
24
- label: `deriveColQuery:${query$.label}:${colName}`,
21
+ ): LiveQueryDef<GetResult<TQueryDef>[TCol], QueryInfo.Col>
22
+ } = (queryDef: LiveQueryDef<any, QueryInfo.Row | QueryInfo.Col>, colName: string) => {
23
+ return computed((get) => get(queryDef)[colName], {
24
+ label: `deriveColQuery:${queryDef.label}:${colName}`,
25
25
  queryInfo:
26
- query$.queryInfo._tag === 'Row'
27
- ? { _tag: 'Col', table: query$.queryInfo.table, column: colName, id: query$.queryInfo.id }
26
+ queryDef.queryInfo._tag === 'Row'
27
+ ? { _tag: 'Col', table: queryDef.queryInfo.table, column: colName, id: queryDef.queryInfo.id }
28
28
  : undefined,
29
+ deps: [
30
+ queryDef.queryInfo.table.sqliteDef.name,
31
+ queryDef.queryInfo.id === SessionIdSymbol ? 'sessionId' : queryDef.queryInfo.id,
32
+ queryDef.queryInfo._tag === 'Col' ? queryDef.queryInfo.column : undefined,
33
+ ],
29
34
  }) as any
30
35
  }
31
36
 
@@ -41,15 +46,17 @@ export const makeExecBeforeFirstRun =
41
46
  table: DbSchema.TableDefBase
42
47
  otelContext: otel.Context | undefined
43
48
  }) =>
44
- ({ store }: QueryContext) => {
49
+ ({ store }: ReactivityGraphContext) => {
45
50
  const otelContext = otelContext_ ?? store.otel.queriesSpanContext
46
51
 
47
52
  if (table.options.isSingleton === false) {
48
53
  const idStr = id === SessionIdSymbol ? store.sessionId : id!
49
54
  const rowExists =
50
- store.syncDbWrapper.select(`SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`, {
51
- bindValues: [idStr] as any as PreparedBindValues,
52
- }).length === 1
55
+ store.sqliteDbWrapper.select(
56
+ `SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
57
+ [idStr] as any as PreparedBindValues,
58
+ { otelContext },
59
+ ).length === 1
53
60
 
54
61
  if (rowExists) return
55
62
 
@@ -59,7 +66,11 @@ export const makeExecBeforeFirstRun =
59
66
  )
60
67
  }
61
68
 
62
- // NOTE It's important that we only mutate and don't refresh here, as this function is called during a render
63
- store.mutate({ otelContext, skipRefresh: true }, table.insert({ id, ...insertValues }))
69
+ // It's important that we only mutate and don't refresh here, as this function might be called during a render
70
+ // and otherwise we might end up in a "reactive loop"
71
+ store.mutate(
72
+ { otelContext, skipRefresh: true, label: `rowQuery:${table.sqliteDef.name}:${idStr}` },
73
+ table.insert({ id, ...insertValues }),
74
+ )
64
75
  }
65
76
  }
@@ -14,6 +14,7 @@ import {
14
14
  Effect,
15
15
  Exit,
16
16
  identity,
17
+ Layer,
17
18
  Logger,
18
19
  LogLevel,
19
20
  MutableHashMap,
@@ -26,8 +27,7 @@ import {
26
27
  import { nanoid } from '@livestore/utils/nanoid'
27
28
  import * as otel from '@opentelemetry/api'
28
29
 
29
- import { globalReactivityGraph } from '../global-state.js'
30
- import type { ReactivityGraph } from '../live-queries/base-class.js'
30
+ import { LiveStoreContextRunning } from '../effect/index.js'
31
31
  import { connectDevtoolsToStore } from './devtools.js'
32
32
  import { Store } from './store.js'
33
33
  import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, ShutdownDeferred } from './store-types.js'
@@ -36,12 +36,11 @@ export interface CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext,
36
36
  schema: TSchema
37
37
  adapter: Adapter
38
38
  storeId: string
39
- reactivityGraph?: ReactivityGraph
40
39
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
41
40
  boot?: (
42
41
  store: Store<TGraphQLContext, TSchema>,
43
42
  parentSpan: otel.Span,
44
- ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>
43
+ ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
45
44
  batchUpdates?: (run: () => void) => void
46
45
  disableDevtools?: boolean
47
46
  onBootStatus?: (status: BootStatus) => void
@@ -95,7 +94,6 @@ export const createStore = <
95
94
  storeId,
96
95
  graphQLOptions,
97
96
  boot,
98
- reactivityGraph = globalReactivityGraph,
99
97
  batchUpdates,
100
98
  disableDevtools,
101
99
  onBootStatus,
@@ -163,29 +161,28 @@ export const createStore = <
163
161
  // TODO fill up with unsynced mutation events from the client session
164
162
  const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
165
163
 
166
- const store = Store.createStore<TGraphQLContext, TSchema>(
167
- {
168
- clientSession,
169
- schema,
170
- graphQLOptions,
171
- otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
172
- reactivityGraph,
173
- disableDevtools,
174
- unsyncedMutationEvents,
175
- lifetimeScope,
176
- runtime,
177
- // NOTE during boot we're not yet executing mutations in a batched context
178
- // but only set the provided `batchUpdates` function after boot
179
- batchUpdates: (run) => run(),
180
- storeId,
181
- },
182
- span,
183
- )
164
+ const store = new Store<TGraphQLContext, TSchema>({
165
+ clientSession,
166
+ schema,
167
+ graphQLOptions,
168
+ otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
169
+ disableDevtools,
170
+ unsyncedMutationEvents,
171
+ lifetimeScope,
172
+ runtime,
173
+ // NOTE during boot we're not yet executing mutations in a batched context
174
+ // but only set the provided `batchUpdates` function after boot
175
+ batchUpdates: (run) => run(),
176
+ storeId,
177
+ })
178
+
179
+ yield* store.boot
184
180
 
185
181
  if (boot !== undefined) {
186
182
  // TODO also incorporate `boot` function progress into `bootStatusQueue`
187
183
  yield* Effect.tryAll(() => boot(store, span)).pipe(
188
184
  UnexpectedError.mapToUnexpectedError,
185
+ Effect.provide(Layer.succeed(LiveStoreContextRunning, { stage: 'running', store: store as any as Store })),
189
186
  Effect.withSpan('createStore:boot'),
190
187
  )
191
188
  }
@@ -6,14 +6,14 @@ import { Effect, Stream } from '@livestore/utils/effect'
6
6
 
7
7
  import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.js'
8
8
  import { NOT_REFRESHED_YET } from '../reactive.js'
9
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
10
- import { emptyDebugInfo as makeEmptyDebugInfo } from '../SynchronousDatabaseWrapper.js'
9
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
10
+ import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.js'
11
11
  import type { ReferenceCountedSet } from '../utils/data-structures.js'
12
12
 
13
13
  type IStore = {
14
14
  clientSession: ClientSession
15
15
  reactivityGraph: ReactivityGraph
16
- syncDbWrapper: SynchronousDatabaseWrapper
16
+ sqliteDbWrapper: SqliteDbWrapper
17
17
  activeQueries: ReferenceCountedSet<LiveQuery<any>>
18
18
  }
19
19
 
@@ -73,7 +73,7 @@ export const connectDevtoolsToStore = ({
73
73
  const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
74
74
 
75
75
  switch (decodedMessage._tag) {
76
- case 'LSD.ReactivityGraphSubscribe': {
76
+ case 'LSD.ClientSession.ReactivityGraphSubscribe': {
77
77
  const includeResults = decodedMessage.includeResults
78
78
 
79
79
  const send = () =>
@@ -104,10 +104,10 @@ export const connectDevtoolsToStore = ({
104
104
 
105
105
  break
106
106
  }
107
- case 'LSD.DebugInfoReq': {
107
+ case 'LSD.ClientSession.DebugInfoReq': {
108
108
  sendToDevtools(
109
109
  Devtools.DebugInfoRes.make({
110
- debugInfo: store.syncDbWrapper.debugInfo,
110
+ debugInfo: store.sqliteDbWrapper.debugInfo,
111
111
  requestId,
112
112
  clientId,
113
113
  sessionId,
@@ -116,19 +116,19 @@ export const connectDevtoolsToStore = ({
116
116
  )
117
117
  break
118
118
  }
119
- case 'LSD.DebugInfoHistorySubscribe': {
119
+ case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
120
120
  const buffer: DebugInfo[] = []
121
121
  let hasStopped = false
122
122
  let tickHandle: number | undefined
123
123
 
124
124
  const tick = () => {
125
- buffer.push(store.syncDbWrapper.debugInfo)
125
+ buffer.push(store.sqliteDbWrapper.debugInfo)
126
126
 
127
127
  // NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
128
128
  // will get the empty debug info
129
129
  // TODO We need to come up with a more graceful way to do store. Probably via a single global
130
130
  // `requestAnimationFrame` loop that is passed in somehow.
131
- store.syncDbWrapper.debugInfo = makeEmptyDebugInfo()
131
+ store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
132
132
 
133
133
  if (buffer.length > 10) {
134
134
  sendToDevtools(
@@ -162,31 +162,31 @@ export const connectDevtoolsToStore = ({
162
162
 
163
163
  break
164
164
  }
165
- case 'LSD.DebugInfoHistoryUnsubscribe': {
165
+ case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
166
166
  // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
167
167
  // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
168
168
  debugInfoHistorySubscriptions.get(requestId)?.()
169
169
  debugInfoHistorySubscriptions.delete(requestId)
170
170
  break
171
171
  }
172
- case 'LSD.DebugInfoResetReq': {
173
- store.syncDbWrapper.debugInfo.slowQueries.clear()
172
+ case 'LSD.ClientSession.DebugInfoResetReq': {
173
+ store.sqliteDbWrapper.debugInfo.slowQueries.clear()
174
174
  sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
175
175
  break
176
176
  }
177
- case 'LSD.DebugInfoRerunQueryReq': {
177
+ case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
178
178
  const { queryStr, bindValues, queriedTables } = decodedMessage
179
- store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
179
+ store.sqliteDbWrapper.select(queryStr, bindValues, { queriedTables, skipCache: true })
180
180
  sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
181
181
  break
182
182
  }
183
- case 'LSD.ReactivityGraphUnsubscribe': {
183
+ case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
184
184
  // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
185
185
  // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
186
186
  reactivityGraphSubcriptions.get(requestId)?.()
187
187
  break
188
188
  }
189
- case 'LSD.LiveQueriesSubscribe': {
189
+ case 'LSD.ClientSession.LiveQueriesSubscribe': {
190
190
  const send = () =>
191
191
  requestIdleCallback(
192
192
  () =>
@@ -222,7 +222,7 @@ export const connectDevtoolsToStore = ({
222
222
 
223
223
  break
224
224
  }
225
- case 'LSD.LiveQueriesUnsubscribe': {
225
+ case 'LSD.ClientSession.LiveQueriesUnsubscribe': {
226
226
  // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
227
227
  // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
228
228
  liveQueriesSubscriptions.get(requestId)?.()
@@ -5,9 +5,8 @@ import { Schema } from '@livestore/utils/effect'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
  import type { GraphQLSchema } from 'graphql'
7
7
 
8
- import type { ReactivityGraph } from '../live-queries/base-class.js'
9
8
  import type { DebugRefreshReasonBase } from '../reactive.js'
10
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
9
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
11
10
  import type { StackInfo } from '../utils/stack-info.js'
12
11
  import type { Store } from './store.js'
13
12
 
@@ -43,7 +42,7 @@ export type BaseGraphQLContext = {
43
42
 
44
43
  export type GraphQLOptions<TContext> = {
45
44
  schema: GraphQLSchema
46
- makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
45
+ makeContext: (db: SqliteDbWrapper, tracer: otel.Tracer, sessionId: string) => TContext
47
46
  }
48
47
 
49
48
  export type OtelOptions = {
@@ -61,7 +60,6 @@ export type StoreOptions<
61
60
  // TODO remove graphql-related stuff from store and move to GraphQL query directly
62
61
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
63
62
  otelOptions: OtelOptions
64
- reactivityGraph: ReactivityGraph
65
63
  disableDevtools?: boolean
66
64
  lifetimeScope: Scope.Scope
67
65
  runtime: Runtime.Runtime<Scope.Scope>
@@ -87,6 +85,8 @@ export type RefreshReason =
87
85
  label?: string
88
86
  stackInfo?: StackInfo
89
87
  }
88
+ | { _tag: 'subscribe.initial'; label?: string }
89
+ | { _tag: 'subscribe.update'; label?: string }
90
90
  | { _tag: 'manual'; label?: string }
91
91
 
92
92
  export type QueryDebugInfo = {
@@ -108,3 +108,5 @@ export type StoreMutateOptions = {
108
108
  spanLinks?: otel.Link[]
109
109
  otelContext?: otel.Context
110
110
  }
111
+
112
+ export type Unsubscribe = () => void