@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/SqliteDbWrapper.d.ts +54 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/SqliteDbWrapper.js +212 -0
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +14 -5
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +24 -4
- package/dist/SynchronousDatabaseWrapper.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +12 -8
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +9 -2
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +64 -21
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +56 -13
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts +7 -7
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +35 -11
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +67 -0
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/db-query.js +244 -0
- package/dist/live-queries/db-query.js.map +1 -0
- package/dist/live-queries/db-query.test.d.ts +2 -0
- package/dist/live-queries/db-query.test.d.ts.map +1 -0
- package/dist/live-queries/db-query.test.js +123 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/db.d.ts +12 -15
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +44 -25
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +16 -14
- package/dist/live-queries/db.test.js.map +1 -1
- package/dist/live-queries/graphql.d.ts +8 -8
- package/dist/live-queries/graphql.d.ts.map +1 -1
- package/dist/live-queries/graphql.js +35 -9
- package/dist/live-queries/graphql.js.map +1 -1
- package/dist/live-queries/make-ref.d.ts +20 -0
- package/dist/live-queries/make-ref.d.ts.map +1 -0
- package/dist/live-queries/make-ref.js +33 -0
- package/dist/live-queries/make-ref.js.map +1 -0
- package/dist/reactive.d.ts +15 -13
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +15 -9
- package/dist/reactive.js.map +1 -1
- package/dist/row-query-utils.d.ts +4 -4
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +14 -10
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +3 -4
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +7 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +2 -2
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +15 -15
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +9 -4
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +34 -16
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +125 -75
- package/dist/store/store.js.map +1 -1
- package/dist/utils/expo.d.ts +2 -0
- package/dist/utils/expo.d.ts.map +1 -0
- package/dist/utils/expo.js +8 -0
- package/dist/utils/expo.js.map +1 -0
- package/dist/utils/function-string.d.ts +7 -0
- package/dist/utils/function-string.d.ts.map +1 -0
- package/dist/utils/function-string.js +9 -0
- package/dist/utils/function-string.js.map +1 -0
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +6 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +54 -1
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +2 -6
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +3 -5
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +1 -0
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +1 -0
- package/dist/utils/tests/mod.js.map +1 -1
- package/package.json +5 -5
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -11
- package/src/effect/LiveStore.ts +22 -14
- package/src/index.ts +14 -7
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -42
- package/src/live-queries/base-class.ts +160 -40
- package/src/live-queries/computed.ts +45 -19
- package/src/live-queries/{db.test.ts → db-query.test.ts} +21 -11
- package/src/live-queries/{db.ts → db-query.ts} +97 -39
- package/src/live-queries/graphql.ts +47 -21
- package/src/live-queries/make-ref.ts +47 -0
- package/src/reactive.ts +52 -27
- package/src/row-query-utils.ts +29 -18
- package/src/store/create-store.ts +20 -23
- package/src/store/devtools.ts +17 -17
- package/src/store/store-types.ts +6 -4
- package/src/store/store.ts +227 -120
- package/src/utils/function-string.ts +12 -0
- package/src/utils/stack-info.test.ts +58 -1
- package/src/utils/stack-info.ts +6 -1
- package/src/utils/tests/fixture.ts +2 -7
- package/src/utils/tests/mod.ts +1 -0
- 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>(
|
|
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
|
|
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
|
|
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
|
|
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: (
|
|
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 = (
|
|
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>(
|
|
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)
|
package/src/row-query-utils.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
<
|
|
15
|
-
|
|
14
|
+
<TQueryDef extends LiveQueryDef<any, QueryInfo.None>, TCol extends keyof GetResult<TQueryDef> & string>(
|
|
15
|
+
queryDef: TQueryDef,
|
|
16
16
|
colName: TCol,
|
|
17
|
-
):
|
|
18
|
-
<
|
|
19
|
-
|
|
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
|
-
):
|
|
22
|
-
} = (
|
|
23
|
-
return computed((get) => get(
|
|
24
|
-
label: `deriveColQuery:${
|
|
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
|
-
|
|
27
|
-
? { _tag: 'Col', table:
|
|
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 }:
|
|
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.
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
//
|
|
63
|
-
|
|
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 {
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
}
|
package/src/store/devtools.ts
CHANGED
|
@@ -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 {
|
|
10
|
-
import { emptyDebugInfo as makeEmptyDebugInfo } from '../
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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)?.()
|
package/src/store/store-types.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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
|