@livestore/livestore 0.0.58-dev.5 → 0.0.58-dev.7
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/react/LiveStoreProvider.d.ts +2 -0
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +3 -3
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/useRow.test.js +5 -359
- package/dist/react/useRow.test.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +2 -2
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/store.d.ts +17 -13
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +65 -34
- package/dist/store.js.map +1 -1
- package/package.json +8 -8
- package/src/react/LiveStoreProvider.tsx +5 -3
- package/src/react/__snapshots__/useRow.test.tsx.snap +359 -0
- package/src/react/useRow.test.tsx +5 -359
- package/src/reactiveQueries/sql.ts +2 -2
- package/src/store.ts +105 -45
package/src/store.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BootDb,
|
|
3
3
|
BootStatus,
|
|
4
|
+
EventId,
|
|
4
5
|
IntentionalShutdownCause,
|
|
5
6
|
ParamsObject,
|
|
6
7
|
PreparedBindValues,
|
|
@@ -10,10 +11,16 @@ import type {
|
|
|
10
11
|
} from '@livestore/common'
|
|
11
12
|
import { getExecArgsFromMutation, prepareBindValues, UnexpectedError } from '@livestore/common'
|
|
12
13
|
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
makeMutationEventSchemaMemo,
|
|
16
|
+
SCHEMA_META_TABLE,
|
|
17
|
+
SCHEMA_MUTATIONS_META_TABLE,
|
|
18
|
+
SESSION_CHANGESET_META_TABLE,
|
|
19
|
+
} from '@livestore/common/schema'
|
|
14
20
|
import { assertNever, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
15
21
|
import {
|
|
16
22
|
Cause,
|
|
23
|
+
Data,
|
|
17
24
|
Deferred,
|
|
18
25
|
Duration,
|
|
19
26
|
Effect,
|
|
@@ -23,6 +30,7 @@ import {
|
|
|
23
30
|
Layer,
|
|
24
31
|
Logger,
|
|
25
32
|
LogLevel,
|
|
33
|
+
MutableHashMap,
|
|
26
34
|
OtelTracer,
|
|
27
35
|
Queue,
|
|
28
36
|
Runtime,
|
|
@@ -74,8 +82,8 @@ export type StoreOptions<
|
|
|
74
82
|
fiberSet: FiberSet.FiberSet
|
|
75
83
|
runtime: Runtime.Runtime<Scope.Scope>
|
|
76
84
|
batchUpdates: (runUpdates: () => void) => void
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
currentMutationEventIdRef: { current: EventId }
|
|
86
|
+
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
export type RefreshReason =
|
|
@@ -109,9 +117,6 @@ export type StoreOtel = {
|
|
|
109
117
|
queriesSpanContext: otel.Context
|
|
110
118
|
}
|
|
111
119
|
|
|
112
|
-
let storeCount = 0
|
|
113
|
-
const uniqueStoreId = () => `store-${++storeCount}`
|
|
114
|
-
|
|
115
120
|
export type StoreMutateOptions = {
|
|
116
121
|
label?: string
|
|
117
122
|
skipRefresh?: boolean
|
|
@@ -133,7 +138,7 @@ export class Store<
|
|
|
133
138
|
TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext,
|
|
134
139
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
135
140
|
> extends Inspectable.Class {
|
|
136
|
-
|
|
141
|
+
readonly storeId: string
|
|
137
142
|
reactivityGraph: ReactivityGraph
|
|
138
143
|
syncDbWrapper: SynchronousDatabaseWrapper
|
|
139
144
|
adapter: StoreAdapter
|
|
@@ -150,15 +155,15 @@ export class Store<
|
|
|
150
155
|
private fiberSet: FiberSet.FiberSet
|
|
151
156
|
private runtime: Runtime.Runtime<Scope.Scope>
|
|
152
157
|
|
|
153
|
-
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
154
|
-
private __processedMutationIds
|
|
155
|
-
private __processedMutationWithoutRefreshIds = new Set<string>()
|
|
156
|
-
|
|
157
158
|
/** RC-based set to see which queries are currently subscribed to */
|
|
158
159
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
159
160
|
|
|
161
|
+
// NOTE this is currently exposed for the Devtools databrowser to emit mutation events
|
|
160
162
|
readonly __mutationEventSchema
|
|
161
163
|
|
|
164
|
+
private currentMutationEventIdRef
|
|
165
|
+
private unsyncedMutationEvents
|
|
166
|
+
|
|
162
167
|
// #region constructor
|
|
163
168
|
private constructor({
|
|
164
169
|
adapter,
|
|
@@ -168,12 +173,19 @@ export class Store<
|
|
|
168
173
|
otelOptions,
|
|
169
174
|
disableDevtools,
|
|
170
175
|
batchUpdates,
|
|
171
|
-
|
|
176
|
+
currentMutationEventIdRef,
|
|
177
|
+
unsyncedMutationEvents,
|
|
178
|
+
storeId,
|
|
172
179
|
fiberSet,
|
|
173
180
|
runtime,
|
|
174
181
|
}: StoreOptions<TGraphQLContext, TSchema>) {
|
|
175
182
|
super()
|
|
176
183
|
|
|
184
|
+
this.storeId = storeId
|
|
185
|
+
|
|
186
|
+
this.currentMutationEventIdRef = currentMutationEventIdRef
|
|
187
|
+
this.unsyncedMutationEvents = unsyncedMutationEvents
|
|
188
|
+
|
|
177
189
|
this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: adapter.syncDb })
|
|
178
190
|
this.adapter = adapter
|
|
179
191
|
this.schema = schema
|
|
@@ -184,9 +196,6 @@ export class Store<
|
|
|
184
196
|
// TODO refactor
|
|
185
197
|
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
186
198
|
|
|
187
|
-
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
188
|
-
this.__processedMutationIds = __processedMutationIds
|
|
189
|
-
|
|
190
199
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
191
200
|
this.tableRefs = {}
|
|
192
201
|
this.activeQueries = new ReferenceCountedSet()
|
|
@@ -222,7 +231,7 @@ export class Store<
|
|
|
222
231
|
isRunningInDevtools
|
|
223
232
|
? this.schema.tables.keys()
|
|
224
233
|
: Array.from(this.schema.tables.keys()).filter(
|
|
225
|
-
(_) => _ !== SCHEMA_META_TABLE && _ !== SCHEMA_MUTATIONS_META_TABLE,
|
|
234
|
+
(_) => _ !== SCHEMA_META_TABLE && _ !== SCHEMA_MUTATIONS_META_TABLE && _ !== SESSION_CHANGESET_META_TABLE,
|
|
226
235
|
),
|
|
227
236
|
)
|
|
228
237
|
const existingTableRefs = new Map(
|
|
@@ -241,9 +250,11 @@ export class Store<
|
|
|
241
250
|
|
|
242
251
|
Effect.gen(this, function* () {
|
|
243
252
|
yield* this.adapter.coordinator.syncMutations.pipe(
|
|
244
|
-
Stream.
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
Stream.tapChunk((mutationsEventsDecodedChunk) =>
|
|
254
|
+
Effect.sync(() => {
|
|
255
|
+
this.mutate({ wasSyncMessage: true }, ...mutationsEventsDecodedChunk)
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
247
258
|
Stream.runDrain,
|
|
248
259
|
Effect.interruptible,
|
|
249
260
|
Effect.withSpan('LiveStore:syncMutations'),
|
|
@@ -327,17 +338,21 @@ export class Store<
|
|
|
327
338
|
|
|
328
339
|
// #region mutate
|
|
329
340
|
mutate: {
|
|
330
|
-
<const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
341
|
+
<const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(...list: TMutationArg): void
|
|
331
342
|
(
|
|
332
|
-
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
343
|
+
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(
|
|
344
|
+
...list: TMutationArg
|
|
345
|
+
) => void,
|
|
333
346
|
): void
|
|
334
|
-
<const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
347
|
+
<const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(
|
|
335
348
|
options: StoreMutateOptions,
|
|
336
349
|
...list: TMutationArg
|
|
337
350
|
): void
|
|
338
351
|
(
|
|
339
352
|
options: StoreMutateOptions,
|
|
340
|
-
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
353
|
+
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(
|
|
354
|
+
...list: TMutationArg
|
|
355
|
+
) => void,
|
|
341
356
|
): void
|
|
342
357
|
} = (firstMutationOrTxnFnOrOptions: any, ...restMutations: any[]) => {
|
|
343
358
|
let mutationsEvents: MutationEvent.ForSchema<TSchema>[]
|
|
@@ -361,16 +376,14 @@ export class Store<
|
|
|
361
376
|
mutationsEvents = [firstMutationOrTxnFnOrOptions, ...restMutations]
|
|
362
377
|
}
|
|
363
378
|
|
|
364
|
-
mutationsEvents = mutationsEvents.filter(
|
|
379
|
+
mutationsEvents = mutationsEvents.filter(
|
|
380
|
+
(_) => _.id === undefined || !MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(_.id)),
|
|
381
|
+
)
|
|
365
382
|
|
|
366
383
|
if (mutationsEvents.length === 0) {
|
|
367
384
|
return
|
|
368
385
|
}
|
|
369
386
|
|
|
370
|
-
for (const mutationEvent of mutationsEvents) {
|
|
371
|
-
this.__processedMutationIds.add(mutationEvent.id)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
387
|
const label = options?.label ?? 'mutate'
|
|
375
388
|
const skipRefresh = options?.skipRefresh ?? false
|
|
376
389
|
const wasSyncMessage = options?.wasSyncMessage ?? false
|
|
@@ -385,7 +398,7 @@ export class Store<
|
|
|
385
398
|
|
|
386
399
|
let durationMs: number
|
|
387
400
|
|
|
388
|
-
|
|
401
|
+
const res = this.otel.tracer.startActiveSpan(
|
|
389
402
|
'LiveStore:mutate',
|
|
390
403
|
{ attributes: { 'livestore.mutateLabel': label } },
|
|
391
404
|
this.otel.mutationsSpanContext,
|
|
@@ -465,6 +478,16 @@ export class Store<
|
|
|
465
478
|
return { durationMs }
|
|
466
479
|
},
|
|
467
480
|
)
|
|
481
|
+
|
|
482
|
+
// NOTE we need to add the mutation events to the unsynced mutation events map only after running the code above
|
|
483
|
+
// so the short-circuiting in `mutateWithoutRefresh` doesn't kick in for those events
|
|
484
|
+
for (const mutationEvent of mutationsEvents) {
|
|
485
|
+
if (mutationEvent.id !== undefined) {
|
|
486
|
+
MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEvent.id), mutationEvent)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return res
|
|
468
491
|
}
|
|
469
492
|
|
|
470
493
|
/**
|
|
@@ -492,19 +515,30 @@ export class Store<
|
|
|
492
515
|
* the caller must refresh queries after calling this method.
|
|
493
516
|
*/
|
|
494
517
|
mutateWithoutRefresh = (
|
|
495
|
-
|
|
518
|
+
mutationEventDecoded_: MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>,
|
|
496
519
|
options: {
|
|
497
520
|
otelContext: otel.Context
|
|
498
521
|
coordinatorMode: 'default' | 'skip-coordinator' | 'skip-persist'
|
|
499
522
|
},
|
|
500
523
|
): { writeTables: ReadonlySet<string>; durationMs: number } => {
|
|
524
|
+
const mutationDef =
|
|
525
|
+
this.schema.mutations.get(mutationEventDecoded_.mutation) ??
|
|
526
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
|
|
527
|
+
|
|
528
|
+
const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = mutationEventDecoded_.hasOwnProperty('id')
|
|
529
|
+
? (mutationEventDecoded_ as MutationEvent.ForSchema<TSchema>)
|
|
530
|
+
: {
|
|
531
|
+
...mutationEventDecoded_,
|
|
532
|
+
...this.getNextMutationEventId({ localOnly: mutationDef.options.localOnly }),
|
|
533
|
+
}
|
|
534
|
+
|
|
501
535
|
// NOTE we also need this temporary workaround here since some code-paths use `mutateWithoutRefresh` directly
|
|
502
536
|
// e.g. the row-query functionality
|
|
503
|
-
if (this.
|
|
537
|
+
if (MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id))) {
|
|
504
538
|
// NOTE this data should never be used
|
|
505
539
|
return { writeTables: new Set(), durationMs: 0 }
|
|
506
540
|
} else {
|
|
507
|
-
this.
|
|
541
|
+
MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
|
508
542
|
}
|
|
509
543
|
|
|
510
544
|
const { otelContext, coordinatorMode = 'default' } = options
|
|
@@ -524,10 +558,6 @@ export class Store<
|
|
|
524
558
|
const allWriteTables = new Set<string>()
|
|
525
559
|
let durationMsTotal = 0
|
|
526
560
|
|
|
527
|
-
const mutationDef =
|
|
528
|
-
this.schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
529
|
-
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
530
|
-
|
|
531
561
|
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
532
562
|
|
|
533
563
|
for (const {
|
|
@@ -549,7 +579,7 @@ export class Store<
|
|
|
549
579
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
550
580
|
this.adapter.coordinator
|
|
551
581
|
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
552
|
-
.pipe(
|
|
582
|
+
.pipe(this.runEffectFork)
|
|
553
583
|
}
|
|
554
584
|
|
|
555
585
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -568,7 +598,7 @@ export class Store<
|
|
|
568
598
|
* This should only be used for framework-internal purposes;
|
|
569
599
|
* all app writes should go through mutate.
|
|
570
600
|
*/
|
|
571
|
-
|
|
601
|
+
__execute = (
|
|
572
602
|
query: string,
|
|
573
603
|
params: ParamsObject = {},
|
|
574
604
|
writeTables?: ReadonlySet<string>,
|
|
@@ -579,10 +609,21 @@ export class Store<
|
|
|
579
609
|
this.adapter.coordinator.execute(query, prepareBindValues(params, query)).pipe(this.runEffectFork)
|
|
580
610
|
}
|
|
581
611
|
|
|
582
|
-
|
|
612
|
+
__select = (query: string, params: ParamsObject = {}) => {
|
|
583
613
|
return this.syncDbWrapper.select(query, { bindValues: prepareBindValues(params, query) })
|
|
584
614
|
}
|
|
585
615
|
|
|
616
|
+
private getNextMutationEventId = ({ localOnly }: { localOnly: boolean }): { id: EventId; parentId: EventId } => {
|
|
617
|
+
const id = this.adapter.coordinator.getNextMutationEventId({ localOnly }).pipe(Effect.runSync)
|
|
618
|
+
const parentId = localOnly
|
|
619
|
+
? this.currentMutationEventIdRef.current
|
|
620
|
+
: { global: this.currentMutationEventIdRef.current.global, local: 0 }
|
|
621
|
+
|
|
622
|
+
this.currentMutationEventIdRef.current = id
|
|
623
|
+
|
|
624
|
+
return { id, parentId }
|
|
625
|
+
}
|
|
626
|
+
|
|
586
627
|
private makeTableRef = (tableName: string) =>
|
|
587
628
|
this.reactivityGraph.makeRef(null, {
|
|
588
629
|
equal: () => false,
|
|
@@ -745,7 +786,13 @@ export const createStore = <
|
|
|
745
786
|
|
|
746
787
|
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
747
788
|
|
|
748
|
-
|
|
789
|
+
// TODO get rid of this
|
|
790
|
+
// const __processedMutationIds = new Set<number>()
|
|
791
|
+
|
|
792
|
+
const currentMutationEventIdRef = { current: yield* adapter.coordinator.getCurrentMutationEventId }
|
|
793
|
+
|
|
794
|
+
// TODO fill up with unsynced mutation events from the coordinator
|
|
795
|
+
const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
|
|
749
796
|
|
|
750
797
|
// TODO consider moving booting into the storage backend
|
|
751
798
|
if (boot !== undefined) {
|
|
@@ -765,12 +812,24 @@ export const createStore = <
|
|
|
765
812
|
}
|
|
766
813
|
},
|
|
767
814
|
mutate: (...list) => {
|
|
768
|
-
for (const
|
|
815
|
+
for (const mutationEventDecoded_ of list) {
|
|
769
816
|
const mutationDef =
|
|
770
|
-
schema.mutations.get(
|
|
771
|
-
shouldNeverHappen(`Unknown mutation type: ${
|
|
817
|
+
schema.mutations.get(mutationEventDecoded_.mutation) ??
|
|
818
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
|
|
819
|
+
|
|
820
|
+
const parentId = mutationDef.options.localOnly
|
|
821
|
+
? currentMutationEventIdRef.current
|
|
822
|
+
: { global: currentMutationEventIdRef.current.global, local: 0 }
|
|
823
|
+
|
|
824
|
+
currentMutationEventIdRef.current = adapter.coordinator
|
|
825
|
+
.getNextMutationEventId({ localOnly: mutationDef.options.localOnly })
|
|
826
|
+
.pipe(Effect.runSync)
|
|
827
|
+
|
|
828
|
+
const mutationEventDecoded = { ...mutationEventDecoded_, id: currentMutationEventIdRef.current, parentId }
|
|
829
|
+
|
|
830
|
+
MutableHashMap.set(unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
|
772
831
|
|
|
773
|
-
__processedMutationIds.add(mutationEventDecoded.id)
|
|
832
|
+
// __processedMutationIds.add(mutationEventDecoded.id.global)
|
|
774
833
|
|
|
775
834
|
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
776
835
|
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
@@ -828,7 +887,8 @@ export const createStore = <
|
|
|
828
887
|
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
829
888
|
reactivityGraph,
|
|
830
889
|
disableDevtools,
|
|
831
|
-
|
|
890
|
+
currentMutationEventIdRef,
|
|
891
|
+
unsyncedMutationEvents,
|
|
832
892
|
fiberSet,
|
|
833
893
|
runtime,
|
|
834
894
|
batchUpdates: batchUpdates ?? ((run) => run()),
|