@livestore/livestore 0.4.0-dev.1 → 0.4.0-dev.10
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/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +2 -4
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +7 -4
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +13 -7
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +10 -10
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +36 -27
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +115 -0
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +3 -3
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/store-types.d.ts +2 -2
- 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 +21 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +134 -88
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.d.ts +3 -0
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +2 -1
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts +15 -14
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +20 -15
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +7 -7
- package/src/ambient.d.ts +3 -3
- package/src/effect/LiveStore.ts +2 -4
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +268 -131
- package/src/live-queries/db-query.test.ts +13 -7
- package/src/live-queries/db-query.ts +7 -4
- package/src/mod.ts +2 -0
- package/src/reactive.test.ts +150 -1
- package/src/reactive.ts +47 -39
- package/src/store/create-store.ts +12 -4
- package/src/store/store-types.ts +5 -2
- package/src/store/store.ts +204 -145
- package/src/utils/dev.ts +5 -0
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +31 -20
- package/dist/store/store-shutdown.test.d.ts +0 -2
- package/dist/store/store-shutdown.test.d.ts.map +0 -1
- package/dist/store/store-shutdown.test.js +0 -103
- package/dist/store/store-shutdown.test.js.map +0 -1
@@ -9,7 +9,7 @@ import {
|
|
9
9
|
SessionIdSymbol,
|
10
10
|
UnexpectedError,
|
11
11
|
} from '@livestore/common'
|
12
|
-
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
12
|
+
import { deepEqual, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
13
13
|
import { Equal, Hash, Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
|
14
14
|
import * as otel from '@opentelemetry/api'
|
15
15
|
|
@@ -124,9 +124,8 @@ export const queryDb: {
|
|
124
124
|
reactivityGraph: ctx.reactivityGraph.deref()!,
|
125
125
|
queryInput,
|
126
126
|
label,
|
127
|
-
map: options?.map,
|
128
|
-
otelContext,
|
129
127
|
def,
|
128
|
+
...omitUndefineds({ map: options?.map, otelContext }),
|
130
129
|
})
|
131
130
|
}),
|
132
131
|
label,
|
@@ -429,7 +428,11 @@ Result:`,
|
|
429
428
|
return result
|
430
429
|
},
|
431
430
|
),
|
432
|
-
{
|
431
|
+
{
|
432
|
+
label: `${label}:results`,
|
433
|
+
meta: { liveStoreThunkType: 'db.result' },
|
434
|
+
...omitUndefineds({ equal: resultsEqual }),
|
435
|
+
},
|
433
436
|
)
|
434
437
|
|
435
438
|
this.results$ = results$
|
package/src/mod.ts
CHANGED
@@ -4,6 +4,7 @@ export {
|
|
4
4
|
type BootStatus,
|
5
5
|
type DebugInfo,
|
6
6
|
IntentionalShutdownCause,
|
7
|
+
liveStoreVersion,
|
7
8
|
type MutableDebugInfo,
|
8
9
|
type PreparedBindValues,
|
9
10
|
prepareBindValues,
|
@@ -14,6 +15,7 @@ export {
|
|
14
15
|
SessionIdSymbol,
|
15
16
|
type SqliteDb,
|
16
17
|
StoreInterrupted,
|
18
|
+
type SyncState,
|
17
19
|
sql,
|
18
20
|
} from '@livestore/common'
|
19
21
|
export * from '@livestore/common/schema'
|
package/src/reactive.test.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
2
|
-
|
2
|
+
import type { DebugRefreshReasonBase, DebugThunkInfo } from './reactive.ts'
|
3
3
|
import { ReactiveGraph } from './reactive.ts'
|
4
4
|
|
5
5
|
describe('a trivial graph', () => {
|
@@ -424,3 +424,152 @@ describe('error handling', () => {
|
|
424
424
|
)
|
425
425
|
})
|
426
426
|
})
|
427
|
+
|
428
|
+
// Bug: When an effect calls setRef during execution, it triggers nested runEffects calls.
|
429
|
+
// The nested call would overwrite and clear the shared currentDebugRefresh state,
|
430
|
+
// causing a TypeError when the outer runEffects tried to access currentDebugRefresh.refreshedAtoms.
|
431
|
+
// Fix: Use local variables to capture debug state instead of relying on shared mutable state.
|
432
|
+
describe('bug fix: currentDebugRefresh race condition', () => {
|
433
|
+
it('keeps the debug refresh context when nested effect runs are triggered', () => {
|
434
|
+
type TestRefreshReason = DebugRefreshReasonBase | { _tag: 'outer' } | { _tag: 'nested' }
|
435
|
+
|
436
|
+
const graph = new ReactiveGraph<TestRefreshReason, DebugThunkInfo>()
|
437
|
+
graph.context = {}
|
438
|
+
|
439
|
+
const triggerRef = graph.makeRef(0, { label: 'trigger' })
|
440
|
+
const responseRef = graph.makeRef(0, { label: 'response' })
|
441
|
+
|
442
|
+
const triggerThunk = graph.makeThunk((get) => get(triggerRef), { label: 'triggerThunk' })
|
443
|
+
const laterThunk = graph.makeThunk((get) => get(triggerRef) + 1, { label: 'laterThunk' })
|
444
|
+
const responseThunk = graph.makeThunk((get) => get(responseRef), { label: 'responseThunk' })
|
445
|
+
|
446
|
+
const nestedEffect = graph.makeEffect((get) => {
|
447
|
+
get(responseThunk)
|
448
|
+
})
|
449
|
+
|
450
|
+
let observedLater: number | undefined
|
451
|
+
const outerEffect = graph.makeEffect((get) => {
|
452
|
+
const current = get(triggerThunk)
|
453
|
+
|
454
|
+
if (current !== 0) {
|
455
|
+
graph.setRef(responseRef, current, {
|
456
|
+
debugRefreshReason: { _tag: 'nested' },
|
457
|
+
})
|
458
|
+
}
|
459
|
+
|
460
|
+
observedLater = get(laterThunk)
|
461
|
+
})
|
462
|
+
|
463
|
+
nestedEffect.doEffect()
|
464
|
+
outerEffect.doEffect()
|
465
|
+
graph.debugRefreshInfos.clear()
|
466
|
+
|
467
|
+
graph.setRef(triggerRef, 1, { debugRefreshReason: { _tag: 'outer' } })
|
468
|
+
|
469
|
+
expect(observedLater).toBe(2)
|
470
|
+
|
471
|
+
const refreshInfos = Array.from(graph.debugRefreshInfos)
|
472
|
+
const outerRefresh = refreshInfos.find((info) => info.reason._tag === 'outer')
|
473
|
+
expect(outerRefresh).toBeDefined()
|
474
|
+
const refreshedLabels = outerRefresh!.refreshedAtoms.map((atomInfo) => atomInfo.atom.label)
|
475
|
+
expect(refreshedLabels).toContain('triggerThunk')
|
476
|
+
expect(refreshedLabels).toContain('laterThunk')
|
477
|
+
|
478
|
+
const makeThunkRefreshes = refreshInfos.filter((info) => info.reason._tag === 'makeThunk')
|
479
|
+
expect(makeThunkRefreshes).toHaveLength(0)
|
480
|
+
})
|
481
|
+
|
482
|
+
it('handles nested runEffects from effect calling setRef', () => {
|
483
|
+
const graph = new ReactiveGraph()
|
484
|
+
graph.context = {}
|
485
|
+
|
486
|
+
const a = graph.makeRef(1)
|
487
|
+
const b = graph.makeRef(2)
|
488
|
+
|
489
|
+
// Effect that calls setRef, triggering nested runEffects
|
490
|
+
graph
|
491
|
+
.makeEffect((get) => {
|
492
|
+
get(a)
|
493
|
+
graph.setRef(b, 3)
|
494
|
+
})
|
495
|
+
.doEffect()
|
496
|
+
|
497
|
+
// Effect observing b
|
498
|
+
graph.makeEffect((get) => get(b)).doEffect()
|
499
|
+
|
500
|
+
// Previously crashed with: Cannot read properties of undefined (reading 'refreshedAtoms')
|
501
|
+
// Now handles nested runEffects correctly
|
502
|
+
expect(() => graph.setRef(a, 2)).not.toThrow()
|
503
|
+
})
|
504
|
+
|
505
|
+
it('handles thunk calling setRef directly', () => {
|
506
|
+
const graph = new ReactiveGraph()
|
507
|
+
graph.context = {}
|
508
|
+
|
509
|
+
const a = graph.makeRef(1)
|
510
|
+
const b = graph.makeRef(2)
|
511
|
+
|
512
|
+
// Effect observing b so setRef(b) triggers runEffects
|
513
|
+
graph.makeEffect((get) => get(b)).doEffect()
|
514
|
+
|
515
|
+
// Thunk that calls setRef during its computation
|
516
|
+
const thunk = graph.makeThunk((get) => {
|
517
|
+
const val = get(a)
|
518
|
+
graph.setRef(b, val * 2) // This triggers nested runEffects
|
519
|
+
return val + get(b)
|
520
|
+
})
|
521
|
+
|
522
|
+
// With our fix, this handles nested currentDebugRefresh correctly
|
523
|
+
expect(() => thunk.computeResult()).not.toThrow()
|
524
|
+
expect(thunk.computeResult()).toBe(3) // 1 + 2 (b was already set to 2)
|
525
|
+
})
|
526
|
+
|
527
|
+
it('handles nested thunk computations', () => {
|
528
|
+
const graph = new ReactiveGraph()
|
529
|
+
graph.context = {}
|
530
|
+
|
531
|
+
const a = graph.makeRef(1)
|
532
|
+
|
533
|
+
// Outer thunk that creates and computes inner thunk
|
534
|
+
const outerThunk = graph.makeThunk((get) => {
|
535
|
+
const val = get(a)
|
536
|
+
|
537
|
+
// Create and compute inner thunk during outer computation
|
538
|
+
const innerThunk = graph.makeThunk((innerGet) => innerGet(a) * 2)
|
539
|
+
|
540
|
+
// Nested thunk computation - previously could corrupt currentDebugRefresh
|
541
|
+
return val + innerThunk.computeResult()
|
542
|
+
})
|
543
|
+
|
544
|
+
// With our fix, nested thunk computations work correctly
|
545
|
+
expect(() => outerThunk.computeResult()).not.toThrow()
|
546
|
+
expect(outerThunk.computeResult()).toBe(3) // 1 + (1 * 2)
|
547
|
+
})
|
548
|
+
})
|
549
|
+
|
550
|
+
// Bug: Nodes could have undefined or deleted super/sub properties in certain edge cases,
|
551
|
+
// causing crashes with "Cannot read properties of undefined" errors.
|
552
|
+
// Fix: Added validation checks in destroyNode to handle corrupted nodes gracefully.
|
553
|
+
// Note: Full addEdge protection was removed, so some scenarios still crash with native errors.
|
554
|
+
describe('bug fix: node corruption protection', () => {
|
555
|
+
it('handles node destruction during effect execution', () => {
|
556
|
+
const graph = new ReactiveGraph()
|
557
|
+
graph.context = {}
|
558
|
+
|
559
|
+
const a = graph.makeRef(1)
|
560
|
+
const thunk1 = graph.makeThunk((get) => get(a) * 2)
|
561
|
+
const thunk2 = graph.makeThunk((get) => get(thunk1))
|
562
|
+
|
563
|
+
let firstRun = true
|
564
|
+
const effect = graph.makeEffect((get) => {
|
565
|
+
if (firstRun) {
|
566
|
+
firstRun = false
|
567
|
+
graph.destroyNode(thunk1) // Destroy dependency mid-execution
|
568
|
+
}
|
569
|
+
get(thunk2)
|
570
|
+
})
|
571
|
+
|
572
|
+
// Should detect the destroyed node
|
573
|
+
expect(() => effect.doEffect()).toThrow('LiveStore Error: Attempted to compute destroyed')
|
574
|
+
})
|
575
|
+
})
|
package/src/reactive.ts
CHANGED
@@ -22,7 +22,7 @@
|
|
22
22
|
// - At every thunk we check value equality with the previous value and cutoff propagation if possible.
|
23
23
|
|
24
24
|
import { BoundArray } from '@livestore/common'
|
25
|
-
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
25
|
+
import { deepEqual, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
26
26
|
import type { Types } from '@livestore/utils/effect'
|
27
27
|
import type * as otel from '@opentelemetry/api'
|
28
28
|
// import { getDurationMsFromSpan } from './otel.ts'
|
@@ -45,9 +45,9 @@ export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
45
45
|
computeResult: () => T
|
46
46
|
sub: Set<Atom<any, TContext, TDebugRefreshReason>> // always empty
|
47
47
|
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
|
48
|
-
label?: string
|
48
|
+
label?: string | undefined
|
49
49
|
/** Container for meta information (e.g. the LiveStore Store) */
|
50
|
-
meta?: any
|
50
|
+
meta?: any | undefined
|
51
51
|
equal: (a: T, b: T) => boolean
|
52
52
|
refreshes: number
|
53
53
|
}
|
@@ -61,9 +61,9 @@ export type Thunk<TResult, TContext, TDebugRefreshReason extends DebugRefreshRea
|
|
61
61
|
previousResult: TResult | NOT_REFRESHED_YET
|
62
62
|
sub: Set<Atom<any, TContext, TDebugRefreshReason>>
|
63
63
|
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
|
64
|
-
label?: string
|
64
|
+
label?: string | undefined
|
65
65
|
/** Container for meta information (e.g. the LiveStore Store) */
|
66
|
-
meta?: any
|
66
|
+
meta?: any | undefined
|
67
67
|
equal: (a: TResult, b: TResult) => boolean
|
68
68
|
recomputations: number
|
69
69
|
|
@@ -80,7 +80,7 @@ export type Effect<TDebugRefreshReason extends DebugRefreshReason> = {
|
|
80
80
|
isDestroyed: boolean
|
81
81
|
doEffect: (otelContext?: otel.Context | undefined, debugRefreshReason?: TDebugRefreshReason | undefined) => void
|
82
82
|
sub: Set<Atom<any, TODO, TODO>>
|
83
|
-
label?: string
|
83
|
+
label?: string | undefined
|
84
84
|
invocations: number
|
85
85
|
}
|
86
86
|
|
@@ -103,10 +103,10 @@ export type DebugRefreshReasonBase =
|
|
103
103
|
/** Usually in response to some `commit` calls with `skipRefresh: true` */
|
104
104
|
| {
|
105
105
|
_tag: 'runDeferredEffects'
|
106
|
-
originalRefreshReasons?: ReadonlyArray<DebugRefreshReasonBase>
|
107
|
-
manualRefreshReason?: DebugRefreshReasonBase
|
106
|
+
originalRefreshReasons?: ReadonlyArray<DebugRefreshReasonBase> | undefined
|
107
|
+
manualRefreshReason?: DebugRefreshReasonBase | undefined
|
108
108
|
}
|
109
|
-
| { _tag: 'makeThunk'; label?: string }
|
109
|
+
| { _tag: 'makeThunk'; label?: string | undefined }
|
110
110
|
| { _tag: 'unknown' }
|
111
111
|
|
112
112
|
export type DebugRefreshReason<T extends string = string> = DebugRefreshReasonBase | { _tag: T }
|
@@ -135,7 +135,7 @@ const unknownRefreshReason = () => {
|
|
135
135
|
return { _tag: 'unknown' as const }
|
136
136
|
}
|
137
137
|
|
138
|
-
export type EncodedOption<A> = { _tag: 'Some'; value?: A } | { _tag: 'None' }
|
138
|
+
export type EncodedOption<A> = { _tag: 'Some'; value?: A | undefined } | { _tag: 'None' }
|
139
139
|
const encodedOptionSome = <A>(value: A): EncodedOption<A> => ({ _tag: 'Some', value })
|
140
140
|
const encodedOptionNone = <A>(): EncodedOption<A> => ({ _tag: 'None' })
|
141
141
|
|
@@ -192,7 +192,7 @@ export const __resetIds = () => {
|
|
192
192
|
export class ReactiveGraph<
|
193
193
|
TDebugRefreshReason extends DebugRefreshReason,
|
194
194
|
TDebugThunkInfo extends DebugThunkInfo,
|
195
|
-
TContext extends { effectsWrapper?: (runEffects: () => void) => void } = {},
|
195
|
+
TContext extends { effectsWrapper?: ((runEffects: () => void) => void) | undefined } = {},
|
196
196
|
> {
|
197
197
|
id = uniqueGraphId()
|
198
198
|
|
@@ -211,10 +211,8 @@ export class ReactiveGraph<
|
|
211
211
|
|
212
212
|
private refreshCallbacks: Set<() => void> = new Set()
|
213
213
|
|
214
|
-
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: for debugging
|
215
214
|
private nodeIdCounter = 0
|
216
215
|
private uniqueNodeId = () => `node-${++this.nodeIdCounter}`
|
217
|
-
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: for debugging
|
218
216
|
private refreshInfoIdCounter = 0
|
219
217
|
private uniqueRefreshInfoId = () => `refresh-info-${++this.refreshInfoIdCounter}`
|
220
218
|
|
@@ -231,8 +229,7 @@ export class ReactiveGraph<
|
|
231
229
|
computeResult: () => ref.previousResult,
|
232
230
|
sub: new Set(),
|
233
231
|
super: new Set(),
|
234
|
-
label: options?.label,
|
235
|
-
meta: options?.meta,
|
232
|
+
...omitUndefineds({ label: options?.label, meta: options?.meta }),
|
236
233
|
equal: options?.equal ?? deepEqual,
|
237
234
|
refreshes: 0,
|
238
235
|
}
|
@@ -267,8 +264,11 @@ export class ReactiveGraph<
|
|
267
264
|
computeResult: (otelContext, debugRefreshReason) => {
|
268
265
|
if (thunk.isDirty) {
|
269
266
|
const neededCurrentRefresh = this.currentDebugRefresh === undefined
|
267
|
+
let localDebugRefresh: { refreshedAtoms: any[]; startMs: number } | undefined
|
270
268
|
if (neededCurrentRefresh) {
|
271
|
-
|
269
|
+
// Use local variable to prevent corruption from nested computations
|
270
|
+
localDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
271
|
+
this.currentDebugRefresh = localDebugRefresh
|
272
272
|
}
|
273
273
|
|
274
274
|
// Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
|
@@ -300,15 +300,20 @@ export class ReactiveGraph<
|
|
300
300
|
debugInfo: debugInfo ?? (unknownRefreshReason() as TDebugThunkInfo),
|
301
301
|
} satisfies AtomDebugInfo<TDebugThunkInfo>
|
302
302
|
|
303
|
-
|
303
|
+
// Use currentDebugRefresh if available (could be from parent or local)
|
304
|
+
const debugRefresh = localDebugRefresh ?? this.currentDebugRefresh
|
305
|
+
if (debugRefresh) {
|
306
|
+
debugRefresh.refreshedAtoms.push(debugInfoForAtom)
|
307
|
+
}
|
304
308
|
|
305
309
|
thunk.isDirty = false
|
306
310
|
thunk.previousResult = result
|
307
311
|
thunk.recomputations++
|
308
312
|
|
309
|
-
if (neededCurrentRefresh) {
|
310
|
-
|
311
|
-
const
|
313
|
+
if (neededCurrentRefresh && localDebugRefresh) {
|
314
|
+
// Use local reference which can't be corrupted by nested calls
|
315
|
+
const refreshedAtoms = localDebugRefresh.refreshedAtoms
|
316
|
+
const durationMs = performance.now() - localDebugRefresh.startMs
|
312
317
|
this.currentDebugRefresh = undefined
|
313
318
|
|
314
319
|
this.debugRefreshInfos.push({
|
@@ -330,8 +335,7 @@ export class ReactiveGraph<
|
|
330
335
|
sub: new Set(),
|
331
336
|
super: new Set(),
|
332
337
|
recomputations: 0,
|
333
|
-
label: options?.label,
|
334
|
-
meta: options?.meta,
|
338
|
+
...omitUndefineds({ label: options?.label, meta: options?.meta }),
|
335
339
|
equal: options?.equal ?? deepEqual,
|
336
340
|
__getResult: getResult,
|
337
341
|
}
|
@@ -407,7 +411,7 @@ export class ReactiveGraph<
|
|
407
411
|
doEffect(getAtom as GetAtom, otelContext, debugRefreshReason)
|
408
412
|
},
|
409
413
|
sub: new Set(),
|
410
|
-
label: options?.label,
|
414
|
+
...omitUndefineds({ label: options?.label }),
|
411
415
|
invocations: 0,
|
412
416
|
}
|
413
417
|
|
@@ -461,7 +465,7 @@ export class ReactiveGraph<
|
|
461
465
|
} else {
|
462
466
|
this.runEffects(effectsToRefresh, {
|
463
467
|
debugRefreshReason: options?.debugRefreshReason ?? (unknownRefreshReason() as TDebugRefreshReason),
|
464
|
-
otelContext: options?.otelContext,
|
468
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
465
469
|
})
|
466
470
|
}
|
467
471
|
}
|
@@ -475,15 +479,21 @@ export class ReactiveGraph<
|
|
475
479
|
) => {
|
476
480
|
const effectsWrapper = this.context?.effectsWrapper ?? ((runEffects: () => void) => runEffects())
|
477
481
|
effectsWrapper(() => {
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
+
// Capture debug state in local variable to prevent corruption from nested runEffects
|
483
|
+
const previousDebugRefresh = this.currentDebugRefresh
|
484
|
+
const localDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
485
|
+
this.currentDebugRefresh = localDebugRefresh
|
486
|
+
|
487
|
+
try {
|
488
|
+
for (const effect of effectsToRefresh) {
|
489
|
+
effect.doEffect(options?.otelContext, options.debugRefreshReason)
|
490
|
+
}
|
491
|
+
} finally {
|
492
|
+
this.currentDebugRefresh = previousDebugRefresh
|
482
493
|
}
|
483
494
|
|
484
|
-
const refreshedAtoms =
|
485
|
-
const durationMs = performance.now() -
|
486
|
-
this.currentDebugRefresh = undefined
|
495
|
+
const refreshedAtoms = localDebugRefresh.refreshedAtoms
|
496
|
+
const durationMs = performance.now() - localDebugRefresh.startMs
|
487
497
|
|
488
498
|
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
489
499
|
id: this.uniqueRefreshInfoId(),
|
@@ -509,9 +519,9 @@ export class ReactiveGraph<
|
|
509
519
|
debugRefreshReason: {
|
510
520
|
_tag: 'runDeferredEffects',
|
511
521
|
originalRefreshReasons: Array.from(debugRefreshReasons) as ReadonlyArray<DebugRefreshReasonBase>,
|
512
|
-
manualRefreshReason: options?.debugRefreshReason,
|
513
|
-
} as TDebugRefreshReason,
|
514
|
-
otelContext: options?.otelContext,
|
522
|
+
...omitUndefineds({ manualRefreshReason: options?.debugRefreshReason }),
|
523
|
+
} as unknown as TDebugRefreshReason,
|
524
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
515
525
|
})
|
516
526
|
}
|
517
527
|
}
|
@@ -641,8 +651,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
|
|
641
651
|
return {
|
642
652
|
_tag: atom._tag,
|
643
653
|
id: atom.id,
|
644
|
-
label: atom.label,
|
645
|
-
meta: atom.meta,
|
654
|
+
...omitUndefineds({ label: atom.label, meta: atom.meta }),
|
646
655
|
isDirty: atom.isDirty,
|
647
656
|
sub,
|
648
657
|
super: super_,
|
@@ -655,8 +664,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
|
|
655
664
|
return {
|
656
665
|
_tag: 'thunk',
|
657
666
|
id: atom.id,
|
658
|
-
label: atom.label,
|
659
|
-
meta: atom.meta,
|
667
|
+
...omitUndefineds({ label: atom.label, meta: atom.meta }),
|
660
668
|
isDirty: atom.isDirty,
|
661
669
|
sub,
|
662
670
|
super: super_,
|
@@ -676,7 +684,7 @@ const serializeEffect = (effect: Effect<any>): SerializedEffect => {
|
|
676
684
|
return {
|
677
685
|
_tag: effect._tag,
|
678
686
|
id: effect.id,
|
679
|
-
label: effect.label,
|
687
|
+
...omitUndefineds({ label: effect.label }),
|
680
688
|
sub,
|
681
689
|
invocations: effect.invocations,
|
682
690
|
isDestroyed: effect.isDestroyed,
|
@@ -5,13 +5,16 @@ import {
|
|
5
5
|
type ClientSessionDevtoolsChannel,
|
6
6
|
type ClientSessionSyncProcessorSimulationParams,
|
7
7
|
type IntentionalShutdownCause,
|
8
|
+
type InvalidPullError,
|
9
|
+
type IsOfflineError,
|
10
|
+
type MaterializeError,
|
8
11
|
type MigrationsReport,
|
9
12
|
provideOtel,
|
10
13
|
type SyncError,
|
11
14
|
UnexpectedError,
|
12
15
|
} from '@livestore/common'
|
13
16
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
14
|
-
import { isDevEnv, LS_DEV } from '@livestore/utils'
|
17
|
+
import { isDevEnv, LS_DEV, omitUndefineds } from '@livestore/utils'
|
15
18
|
import {
|
16
19
|
Context,
|
17
20
|
Deferred,
|
@@ -156,7 +159,7 @@ export const createStorePromise = async <TSchema extends LiveStoreSchema = LiveS
|
|
156
159
|
Effect.withSpan('createStore', {
|
157
160
|
attributes: { storeId: options.storeId, disableDevtools: options.disableDevtools },
|
158
161
|
}),
|
159
|
-
provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
|
162
|
+
provideOtel(omitUndefineds({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer })),
|
160
163
|
Effect.tapCauseLogPretty,
|
161
164
|
Effect.annotateLogs({ thread: 'window' }),
|
162
165
|
Effect.provide(Logger.prettyWithThread('window')),
|
@@ -217,7 +220,12 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
217
220
|
|
218
221
|
const runtime = yield* Effect.runtime<Scope.Scope>()
|
219
222
|
|
220
|
-
const shutdown = (
|
223
|
+
const shutdown = (
|
224
|
+
exit: Exit.Exit<
|
225
|
+
IntentionalShutdownCause,
|
226
|
+
UnexpectedError | MaterializeError | SyncError | InvalidPullError | IsOfflineError
|
227
|
+
>,
|
228
|
+
) =>
|
221
229
|
Effect.gen(function* () {
|
222
230
|
yield* Scope.close(lifetimeScope, exit).pipe(
|
223
231
|
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
|
@@ -280,7 +288,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
280
288
|
storeId,
|
281
289
|
params: {
|
282
290
|
leaderPushBatchSize: params?.leaderPushBatchSize ?? DEFAULT_PARAMS.leaderPushBatchSize,
|
283
|
-
simulation: params?.simulation,
|
291
|
+
...omitUndefineds({ simulation: params?.simulation }),
|
284
292
|
},
|
285
293
|
})
|
286
294
|
|
package/src/store/store-types.ts
CHANGED
@@ -2,6 +2,9 @@ import type {
|
|
2
2
|
ClientSession,
|
3
3
|
ClientSessionSyncProcessorSimulationParams,
|
4
4
|
IntentionalShutdownCause,
|
5
|
+
InvalidPullError,
|
6
|
+
IsOfflineError,
|
7
|
+
MaterializeError,
|
5
8
|
StoreInterrupted,
|
6
9
|
SyncError,
|
7
10
|
UnexpectedError,
|
@@ -28,11 +31,11 @@ export type LiveStoreContext =
|
|
28
31
|
|
29
32
|
export type ShutdownDeferred = Deferred.Deferred<
|
30
33
|
IntentionalShutdownCause,
|
31
|
-
UnexpectedError | SyncError | StoreInterrupted
|
34
|
+
UnexpectedError | SyncError | StoreInterrupted | MaterializeError | InvalidPullError | IsOfflineError
|
32
35
|
>
|
33
36
|
export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
|
34
37
|
IntentionalShutdownCause,
|
35
|
-
UnexpectedError | SyncError | StoreInterrupted
|
38
|
+
UnexpectedError | SyncError | StoreInterrupted | MaterializeError | InvalidPullError | IsOfflineError
|
36
39
|
>()
|
37
40
|
|
38
41
|
export type LiveStoreContextRunning = {
|