@livestore/livestore 0.4.0-dev.3 → 0.4.0-dev.6
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 +29 -24
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +80 -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 +3 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +94 -88
- package/dist/store/store.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 +6 -6
- 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 +100 -0
- package/src/reactive.ts +40 -35
- package/src/store/create-store.ts +12 -4
- package/src/store/store-types.ts +5 -2
- package/src/store/store.ts +160 -147
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +31 -20
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,14 +479,17 @@ export class ReactiveGraph<
|
|
475
479
|
) => {
|
476
480
|
const effectsWrapper = this.context?.effectsWrapper ?? ((runEffects: () => void) => runEffects())
|
477
481
|
effectsWrapper(() => {
|
478
|
-
|
482
|
+
// Capture debug state in local variable to prevent corruption from nested runEffects
|
483
|
+
const localDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
484
|
+
this.currentDebugRefresh = localDebugRefresh
|
479
485
|
|
480
486
|
for (const effect of effectsToRefresh) {
|
481
487
|
effect.doEffect(options?.otelContext, options.debugRefreshReason)
|
482
488
|
}
|
483
489
|
|
484
|
-
|
485
|
-
const
|
490
|
+
// Use local reference which can't be corrupted by nested calls
|
491
|
+
const refreshedAtoms = localDebugRefresh.refreshedAtoms
|
492
|
+
const durationMs = performance.now() - localDebugRefresh.startMs
|
486
493
|
this.currentDebugRefresh = undefined
|
487
494
|
|
488
495
|
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
@@ -509,9 +516,9 @@ export class ReactiveGraph<
|
|
509
516
|
debugRefreshReason: {
|
510
517
|
_tag: 'runDeferredEffects',
|
511
518
|
originalRefreshReasons: Array.from(debugRefreshReasons) as ReadonlyArray<DebugRefreshReasonBase>,
|
512
|
-
manualRefreshReason: options?.debugRefreshReason,
|
513
|
-
} as TDebugRefreshReason,
|
514
|
-
otelContext: options?.otelContext,
|
519
|
+
...omitUndefineds({ manualRefreshReason: options?.debugRefreshReason }),
|
520
|
+
} as unknown as TDebugRefreshReason,
|
521
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
515
522
|
})
|
516
523
|
}
|
517
524
|
}
|
@@ -641,8 +648,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
|
|
641
648
|
return {
|
642
649
|
_tag: atom._tag,
|
643
650
|
id: atom.id,
|
644
|
-
label: atom.label,
|
645
|
-
meta: atom.meta,
|
651
|
+
...omitUndefineds({ label: atom.label, meta: atom.meta }),
|
646
652
|
isDirty: atom.isDirty,
|
647
653
|
sub,
|
648
654
|
super: super_,
|
@@ -655,8 +661,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
|
|
655
661
|
return {
|
656
662
|
_tag: 'thunk',
|
657
663
|
id: atom.id,
|
658
|
-
label: atom.label,
|
659
|
-
meta: atom.meta,
|
664
|
+
...omitUndefineds({ label: atom.label, meta: atom.meta }),
|
660
665
|
isDirty: atom.isDirty,
|
661
666
|
sub,
|
662
667
|
super: super_,
|
@@ -676,7 +681,7 @@ const serializeEffect = (effect: Effect<any>): SerializedEffect => {
|
|
676
681
|
return {
|
677
682
|
_tag: effect._tag,
|
678
683
|
id: effect.id,
|
679
|
-
label: effect.label,
|
684
|
+
...omitUndefineds({ label: effect.label }),
|
680
685
|
sub,
|
681
686
|
invocations: effect.invocations,
|
682
687
|
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 = {
|