@livestore/livestore 0.4.0-dev.16 → 0.4.0-dev.18
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.test.js +2 -1
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/live-queries/client-document-get-query.js +3 -2
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +6 -4
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +64 -4
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/mod.d.ts +1 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +3 -2
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +2 -13
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +34 -13
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +89 -4
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +1 -0
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +20 -26
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +129 -91
- package/dist/store/store.js.map +1 -1
- package/package.json +5 -5
- package/src/SqliteDbWrapper.test.ts +2 -2
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +220 -0
- package/src/live-queries/client-document-get-query.ts +3 -3
- package/src/live-queries/db-query.test.ts +103 -6
- package/src/live-queries/db-query.ts +7 -4
- package/src/mod.ts +8 -8
- package/src/store/create-store.ts +3 -2
- package/src/store/devtools.ts +40 -26
- package/src/store/store-types.ts +107 -4
- package/src/store/store.ts +166 -123
package/src/store/store.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Bindable,
|
|
3
3
|
type ClientSession,
|
|
4
|
-
type ClientSessionSyncProcessor,
|
|
5
4
|
Devtools,
|
|
6
5
|
getExecStatementsFromMaterializer,
|
|
7
6
|
getResultSchema,
|
|
@@ -37,7 +36,7 @@ import {
|
|
|
37
36
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
38
37
|
import * as otel from '@opentelemetry/api'
|
|
39
38
|
|
|
40
|
-
import type { LiveQuery,
|
|
39
|
+
import type { LiveQuery, ReactivityGraphContext, SignalDef } from '../live-queries/base-class.ts'
|
|
41
40
|
import { makeReactivityGraph } from '../live-queries/base-class.ts'
|
|
42
41
|
import { makeExecBeforeFirstRun } from '../live-queries/client-document-get-query.ts'
|
|
43
42
|
import { queryDb } from '../live-queries/db-query.ts'
|
|
@@ -45,18 +44,20 @@ import type { Ref } from '../reactive.ts'
|
|
|
45
44
|
import { SqliteDbWrapper } from '../SqliteDbWrapper.ts'
|
|
46
45
|
import { ReferenceCountedSet } from '../utils/data-structures.ts'
|
|
47
46
|
import { downloadBlob, exposeDebugUtils } from '../utils/dev.ts'
|
|
48
|
-
import
|
|
49
|
-
Queryable,
|
|
50
|
-
RefreshReason,
|
|
51
|
-
StoreCommitOptions,
|
|
52
|
-
StoreEventsOptions,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
import {
|
|
48
|
+
type Queryable,
|
|
49
|
+
type RefreshReason,
|
|
50
|
+
type StoreCommitOptions,
|
|
51
|
+
type StoreEventsOptions,
|
|
52
|
+
type StoreInternals,
|
|
53
|
+
StoreInternalsSymbol,
|
|
54
|
+
type StoreOptions,
|
|
55
|
+
type StoreOtel,
|
|
56
|
+
type SubscribeOptions,
|
|
57
|
+
type Unsubscribe,
|
|
57
58
|
} from './store-types.ts'
|
|
58
59
|
|
|
59
|
-
type SubscribeFn = {
|
|
60
|
+
export type SubscribeFn = {
|
|
60
61
|
<TResult>(
|
|
61
62
|
query: Queryable<TResult>,
|
|
62
63
|
onUpdate: (value: TResult) => void,
|
|
@@ -71,12 +72,8 @@ if (isDevEnv()) {
|
|
|
71
72
|
|
|
72
73
|
export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> extends Inspectable.Class {
|
|
73
74
|
readonly storeId: string
|
|
74
|
-
reactivityGraph: ReactivityGraph
|
|
75
|
-
sqliteDbWrapper: SqliteDbWrapper
|
|
76
|
-
clientSession: ClientSession
|
|
77
75
|
schema: LiveStoreSchema
|
|
78
76
|
context: TContext
|
|
79
|
-
otel: StoreOtel
|
|
80
77
|
/**
|
|
81
78
|
* Reactive connectivity updates emitted by the backing sync backend.
|
|
82
79
|
*
|
|
@@ -94,29 +91,16 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
94
91
|
* )
|
|
95
92
|
* ```
|
|
96
93
|
*/
|
|
97
|
-
readonly networkStatus: ClientSession['leaderThread']['networkStatus']
|
|
98
|
-
/**
|
|
99
|
-
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
|
100
|
-
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
|
101
|
-
*/
|
|
102
|
-
tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> }
|
|
94
|
+
readonly networkStatus: ClientSession['leaderThread']['networkStatus'];
|
|
103
95
|
|
|
104
|
-
/** Tracks whether the store has been shut down */
|
|
105
|
-
private isShutdown = false
|
|
106
|
-
|
|
107
|
-
private effectContext: {
|
|
108
|
-
runtime: Runtime.Runtime<Scope.Scope>
|
|
109
|
-
lifetimeScope: Scope.Scope
|
|
110
|
-
}
|
|
96
|
+
/** Tracks whether the store has been shut down is kept in internals */
|
|
111
97
|
|
|
112
98
|
/** RC-based set to see which queries are currently subscribed to */
|
|
113
|
-
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
114
|
-
|
|
115
|
-
// NOTE this is currently exposed for the Devtools databrowser to commit events
|
|
116
|
-
readonly __eventSchema
|
|
117
|
-
readonly syncProcessor: ClientSessionSyncProcessor
|
|
118
99
|
|
|
119
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Store internals. Shouldn't be used directly in application code.
|
|
102
|
+
*/
|
|
103
|
+
[StoreInternalsSymbol]: StoreInternals
|
|
120
104
|
|
|
121
105
|
// #region constructor
|
|
122
106
|
constructor({
|
|
@@ -134,20 +118,14 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
134
118
|
super()
|
|
135
119
|
|
|
136
120
|
this.storeId = storeId
|
|
137
|
-
|
|
138
|
-
this.sqliteDbWrapper = new SqliteDbWrapper({ otel: otelOptions, db: clientSession.sqliteDb })
|
|
139
|
-
this.clientSession = clientSession
|
|
140
121
|
this.schema = schema
|
|
141
122
|
this.context = context
|
|
142
|
-
this.networkStatus = clientSession.leaderThread.networkStatus
|
|
143
|
-
|
|
144
|
-
this.effectContext = effectContext
|
|
145
123
|
|
|
146
124
|
const reactivityGraph = makeReactivityGraph()
|
|
147
125
|
|
|
148
126
|
const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext)
|
|
149
127
|
|
|
150
|
-
|
|
128
|
+
const syncProcessor = makeClientSessionSyncProcessor({
|
|
151
129
|
schema,
|
|
152
130
|
clientSession,
|
|
153
131
|
runtime: effectContext.runtime,
|
|
@@ -175,7 +153,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
175
153
|
const execArgsArr = getExecStatementsFromMaterializer({
|
|
176
154
|
eventDef,
|
|
177
155
|
materializer,
|
|
178
|
-
dbState: this.sqliteDbWrapper,
|
|
156
|
+
dbState: this[StoreInternalsSymbol].sqliteDbWrapper,
|
|
179
157
|
event: { decoded: undefined, encoded: eventEncoded },
|
|
180
158
|
})
|
|
181
159
|
|
|
@@ -202,10 +180,13 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
202
180
|
for (const {
|
|
203
181
|
statementSql,
|
|
204
182
|
bindValues,
|
|
205
|
-
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
|
183
|
+
writeTables = this[StoreInternalsSymbol].sqliteDbWrapper.getTablesUsed(statementSql),
|
|
206
184
|
} of execArgsArr) {
|
|
207
185
|
try {
|
|
208
|
-
this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, {
|
|
186
|
+
this[StoreInternalsSymbol].sqliteDbWrapper.cachedExecute(statementSql, bindValues, {
|
|
187
|
+
otelContext,
|
|
188
|
+
writeTables,
|
|
189
|
+
})
|
|
209
190
|
} catch (cause) {
|
|
210
191
|
// TOOD refactor with `SqliteError`
|
|
211
192
|
throw UnexpectedError.make({
|
|
@@ -219,7 +200,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
219
200
|
writeTablesForEvent.add(table)
|
|
220
201
|
}
|
|
221
202
|
|
|
222
|
-
this.sqliteDbWrapper.debug.head = eventEncoded.seqNum
|
|
203
|
+
this[StoreInternalsSymbol].sqliteDbWrapper.debug.head = eventEncoded.seqNum
|
|
223
204
|
}
|
|
224
205
|
}
|
|
225
206
|
|
|
@@ -228,7 +209,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
228
209
|
| { _tag: 'no-op' }
|
|
229
210
|
| { _tag: 'unset' } = { _tag: 'unset' }
|
|
230
211
|
if (withChangeset === true) {
|
|
231
|
-
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
|
212
|
+
sessionChangeset = this[StoreInternalsSymbol].sqliteDbWrapper.withChangeset(exec).changeset
|
|
232
213
|
} else {
|
|
233
214
|
exec()
|
|
234
215
|
}
|
|
@@ -237,12 +218,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
237
218
|
}).pipe(Effect.mapError((cause) => MaterializeError.make({ cause }))),
|
|
238
219
|
),
|
|
239
220
|
rollback: (changeset) => {
|
|
240
|
-
this.sqliteDbWrapper.rollback(changeset)
|
|
221
|
+
this[StoreInternalsSymbol].sqliteDbWrapper.rollback(changeset)
|
|
241
222
|
},
|
|
242
223
|
refreshTables: (tables) => {
|
|
243
224
|
const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
|
|
244
225
|
for (const tableName of tables) {
|
|
245
|
-
const tableRef = this.tableRefs[tableName]
|
|
226
|
+
const tableRef = this[StoreInternalsSymbol].tableRefs[tableName]
|
|
246
227
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
247
228
|
tablesToUpdate.push([tableRef!, null])
|
|
248
229
|
}
|
|
@@ -260,11 +241,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
260
241
|
confirmUnsavedChanges,
|
|
261
242
|
})
|
|
262
243
|
|
|
263
|
-
this.__eventSchema = LiveStoreEvent.makeEventDefSchemaMemo(schema)
|
|
264
|
-
|
|
265
244
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
266
|
-
|
|
267
|
-
|
|
245
|
+
const tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> } = {}
|
|
246
|
+
const activeQueries = new ReferenceCountedSet<LiveQuery<any>>()
|
|
268
247
|
|
|
269
248
|
const commitsSpan = otelOptions.tracer.startSpan('LiveStore:commits', {}, otelOptions.rootSpanContext)
|
|
270
249
|
const otelMuationsSpanContext = otel.trace.setSpan(otel.context.active(), commitsSpan)
|
|
@@ -272,8 +251,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
272
251
|
const queriesSpan = otelOptions.tracer.startSpan('LiveStore:queries', {}, otelOptions.rootSpanContext)
|
|
273
252
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
|
|
274
253
|
|
|
275
|
-
|
|
276
|
-
this.reactivityGraph.context = {
|
|
254
|
+
reactivityGraph.context = {
|
|
277
255
|
store: this as unknown as Store<LiveStoreSchema>,
|
|
278
256
|
defRcMap: new Map(),
|
|
279
257
|
reactivityGraph: new WeakRef(reactivityGraph),
|
|
@@ -281,8 +259,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
281
259
|
rootOtelContext: otelQueriesSpanContext,
|
|
282
260
|
effectsWrapper: batchUpdates,
|
|
283
261
|
}
|
|
284
|
-
|
|
285
|
-
this.otel = {
|
|
262
|
+
const otelObj: StoreOtel = {
|
|
286
263
|
tracer: otelOptions.tracer,
|
|
287
264
|
rootSpanContext: otelOptions.rootSpanContext,
|
|
288
265
|
commitsSpanContext: otelMuationsSpanContext,
|
|
@@ -298,27 +275,27 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
298
275
|
: Array.from(this.schema.state.sqlite.tables.keys()).filter((_) => !SystemTables.isStateSystemTable(_)),
|
|
299
276
|
)
|
|
300
277
|
const existingTableRefs = new Map(
|
|
301
|
-
Array.from(
|
|
278
|
+
Array.from(reactivityGraph.atoms.values())
|
|
302
279
|
.filter((_): _ is Ref<any, any, any> => _._tag === 'ref' && _.label?.startsWith('tableRef:') === true)
|
|
303
280
|
.map((_) => [_.label!.slice('tableRef:'.length), _] as const),
|
|
304
281
|
)
|
|
305
282
|
for (const tableName of allTableNames) {
|
|
306
|
-
|
|
283
|
+
tableRefs[tableName] =
|
|
307
284
|
existingTableRefs.get(tableName) ??
|
|
308
|
-
|
|
285
|
+
reactivityGraph.makeRef(null, {
|
|
309
286
|
equal: () => false,
|
|
310
287
|
label: `tableRef:${tableName}`,
|
|
311
288
|
meta: { liveStoreRefType: 'table' },
|
|
312
289
|
})
|
|
313
290
|
}
|
|
314
291
|
|
|
315
|
-
|
|
292
|
+
const boot = Effect.gen(this, function* () {
|
|
316
293
|
yield* Effect.addFinalizer(() =>
|
|
317
294
|
Effect.sync(() => {
|
|
318
295
|
// Remove all table refs from the reactivity graph
|
|
319
|
-
for (const tableRef of Object.values(
|
|
296
|
+
for (const tableRef of Object.values(tableRefs)) {
|
|
320
297
|
for (const superComp of tableRef.super) {
|
|
321
|
-
this.reactivityGraph.removeEdge(superComp, tableRef)
|
|
298
|
+
this[StoreInternalsSymbol].reactivityGraph.removeEdge(superComp, tableRef)
|
|
322
299
|
}
|
|
323
300
|
}
|
|
324
301
|
|
|
@@ -329,21 +306,57 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
329
306
|
}),
|
|
330
307
|
)
|
|
331
308
|
|
|
332
|
-
yield*
|
|
309
|
+
yield* syncProcessor.boot
|
|
333
310
|
})
|
|
311
|
+
|
|
312
|
+
// Build Sqlite wrapper last to avoid using getters before internals are set
|
|
313
|
+
const sqliteDbWrapper = new SqliteDbWrapper({ otel: otelOptions, db: clientSession.sqliteDb })
|
|
314
|
+
|
|
315
|
+
// Initialize internals bag
|
|
316
|
+
this[StoreInternalsSymbol] = {
|
|
317
|
+
eventSchema: LiveStoreEvent.makeEventDefSchemaMemo(schema) as Schema.Schema<
|
|
318
|
+
LiveStoreEvent.AnyDecoded,
|
|
319
|
+
LiveStoreEvent.AnyEncoded
|
|
320
|
+
>,
|
|
321
|
+
clientSession,
|
|
322
|
+
sqliteDbWrapper,
|
|
323
|
+
effectContext,
|
|
324
|
+
otel: otelObj,
|
|
325
|
+
reactivityGraph,
|
|
326
|
+
tableRefs,
|
|
327
|
+
activeQueries,
|
|
328
|
+
syncProcessor,
|
|
329
|
+
boot,
|
|
330
|
+
isShutdown: false,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Initialize stable network status property from client session
|
|
334
|
+
this.networkStatus = clientSession.leaderThread.networkStatus
|
|
334
335
|
}
|
|
335
336
|
// #endregion constructor
|
|
336
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Current session identifier for this Store instance.
|
|
340
|
+
*
|
|
341
|
+
* - Stable for the lifetime of the Store
|
|
342
|
+
* - Useful for correlating events or scoping per-session data
|
|
343
|
+
*/
|
|
337
344
|
get sessionId(): string {
|
|
338
|
-
return this.clientSession.sessionId
|
|
345
|
+
return this[StoreInternalsSymbol].clientSession.sessionId
|
|
339
346
|
}
|
|
340
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Stable client identifier for the process/device using this Store.
|
|
350
|
+
*
|
|
351
|
+
* - Shared across Store instances created by the same client
|
|
352
|
+
* - Useful for diagnostics and multi-client correlation
|
|
353
|
+
*/
|
|
341
354
|
get clientId(): string {
|
|
342
|
-
return this.clientSession.clientId
|
|
355
|
+
return this[StoreInternalsSymbol].clientSession.clientId
|
|
343
356
|
}
|
|
344
357
|
|
|
345
358
|
private checkShutdown = (operation: string): void => {
|
|
346
|
-
if (this.isShutdown) {
|
|
359
|
+
if (this[StoreInternalsSymbol].isShutdown) {
|
|
347
360
|
throw new UnexpectedError({
|
|
348
361
|
cause: `Store has been shut down (while performing "${operation}").`,
|
|
349
362
|
note: `You cannot perform this operation after the store has been shut down.`,
|
|
@@ -388,17 +401,17 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
388
401
|
): Unsubscribe => {
|
|
389
402
|
this.checkShutdown('subscribe')
|
|
390
403
|
|
|
391
|
-
return this.otel.tracer.startActiveSpan(
|
|
404
|
+
return this[StoreInternalsSymbol].otel.tracer.startActiveSpan(
|
|
392
405
|
`LiveStore.subscribe`,
|
|
393
406
|
{ attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
|
|
394
|
-
options?.otelContext ?? this.otel.queriesSpanContext,
|
|
407
|
+
options?.otelContext ?? this[StoreInternalsSymbol].otel.queriesSpanContext,
|
|
395
408
|
(span) => {
|
|
396
409
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
397
410
|
|
|
398
411
|
const queryRcRef = isQueryBuilder(query)
|
|
399
|
-
? queryDb(query).make(this.reactivityGraph.context!)
|
|
412
|
+
? queryDb(query).make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
400
413
|
: query._tag === 'def' || query._tag === 'signal-def'
|
|
401
|
-
? query.make(this.reactivityGraph.context!)
|
|
414
|
+
? query.make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
402
415
|
: {
|
|
403
416
|
value: query as LiveQuery<TResult>,
|
|
404
417
|
deref: () => {},
|
|
@@ -406,10 +419,23 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
406
419
|
const query$ = queryRcRef.value
|
|
407
420
|
|
|
408
421
|
const label = `subscribe:${options?.label}`
|
|
409
|
-
|
|
410
|
-
|
|
422
|
+
let suppressCallback = options?.skipInitialRun === true
|
|
423
|
+
const effect = this[StoreInternalsSymbol].reactivityGraph.makeEffect(
|
|
424
|
+
(get, _otelContext, debugRefreshReason) => {
|
|
425
|
+
const result = get(query$.results$, otelContext, debugRefreshReason)
|
|
426
|
+
if (suppressCallback) {
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
onUpdate(result)
|
|
430
|
+
},
|
|
411
431
|
{ label },
|
|
412
432
|
)
|
|
433
|
+
const runInitialEffect = () => {
|
|
434
|
+
effect.doEffect(otelContext, {
|
|
435
|
+
_tag: 'subscribe.initial',
|
|
436
|
+
label: `subscribe-initial-run:${options?.label}`,
|
|
437
|
+
})
|
|
438
|
+
}
|
|
413
439
|
|
|
414
440
|
if (options?.stackInfo) {
|
|
415
441
|
query$.activeSubscriptions.add(options.stackInfo)
|
|
@@ -417,19 +443,23 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
417
443
|
|
|
418
444
|
options?.onSubscribe?.(query$)
|
|
419
445
|
|
|
420
|
-
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
|
446
|
+
this[StoreInternalsSymbol].activeQueries.add(query$ as LiveQuery<TResult>)
|
|
421
447
|
|
|
422
|
-
if (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
448
|
+
if (!query$.isDestroyed) {
|
|
449
|
+
if (suppressCallback) {
|
|
450
|
+
// We still run once to register dependencies in the reactive graph, but suppress the initial callback so the
|
|
451
|
+
// caller truly skips the first emission; subsequent runs (after commits) will call the callback.
|
|
452
|
+
runInitialEffect()
|
|
453
|
+
suppressCallback = false
|
|
454
|
+
} else {
|
|
455
|
+
runInitialEffect()
|
|
456
|
+
}
|
|
427
457
|
}
|
|
428
458
|
|
|
429
459
|
const unsubscribe = () => {
|
|
430
460
|
try {
|
|
431
|
-
this.reactivityGraph.destroyNode(effect)
|
|
432
|
-
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
461
|
+
this[StoreInternalsSymbol].reactivityGraph.destroyNode(effect)
|
|
462
|
+
this[StoreInternalsSymbol].activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
433
463
|
|
|
434
464
|
if (options?.stackInfo) {
|
|
435
465
|
query$.activeSubscriptions.delete(options.stackInfo)
|
|
@@ -498,9 +528,13 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
498
528
|
this.checkShutdown('query')
|
|
499
529
|
|
|
500
530
|
if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
|
|
501
|
-
const res = this.sqliteDbWrapper.cachedSelect(
|
|
502
|
-
|
|
503
|
-
|
|
531
|
+
const res = this[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(
|
|
532
|
+
query.query,
|
|
533
|
+
prepareBindValues(query.bindValues, query.query),
|
|
534
|
+
{
|
|
535
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
|
536
|
+
},
|
|
537
|
+
) as any
|
|
504
538
|
if (query.schema) {
|
|
505
539
|
return Schema.decodeSync(query.schema)(res)
|
|
506
540
|
}
|
|
@@ -513,7 +547,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
513
547
|
id: ast.id,
|
|
514
548
|
explicitDefaultValues: ast.explicitDefaultValues,
|
|
515
549
|
otelContext: options?.otelContext,
|
|
516
|
-
})(this.reactivityGraph.context!)
|
|
550
|
+
})(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
517
551
|
}
|
|
518
552
|
|
|
519
553
|
const sqlRes = query.asSql()
|
|
@@ -521,13 +555,17 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
521
555
|
|
|
522
556
|
// Replace SessionIdSymbol in bind values before executing the query
|
|
523
557
|
if (sqlRes.bindValues) {
|
|
524
|
-
replaceSessionIdSymbol(sqlRes.bindValues, this.clientSession.sessionId)
|
|
558
|
+
replaceSessionIdSymbol(sqlRes.bindValues, this[StoreInternalsSymbol].clientSession.sessionId)
|
|
525
559
|
}
|
|
526
560
|
|
|
527
|
-
const rawRes = this.sqliteDbWrapper.cachedSelect(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
561
|
+
const rawRes = this[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(
|
|
562
|
+
sqlRes.query,
|
|
563
|
+
sqlRes.bindValues as any as PreparedBindValues,
|
|
564
|
+
{
|
|
565
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
|
566
|
+
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
|
567
|
+
},
|
|
568
|
+
)
|
|
531
569
|
|
|
532
570
|
const decodeResult = Schema.decodeEither(schema)(rawRes)
|
|
533
571
|
if (decodeResult._tag === 'Right') {
|
|
@@ -543,12 +581,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
543
581
|
)
|
|
544
582
|
}
|
|
545
583
|
} else if (query._tag === 'def') {
|
|
546
|
-
const query$ = query.make(this.reactivityGraph.context!)
|
|
584
|
+
const query$ = query.make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
547
585
|
const result = this.query(query$.value, options)
|
|
548
586
|
query$.deref()
|
|
549
587
|
return result
|
|
550
588
|
} else if (query._tag === 'signal-def') {
|
|
551
|
-
const signal$ = query.make(this.reactivityGraph.context!)
|
|
589
|
+
const signal$ = query.make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
552
590
|
return signal$.value.get()
|
|
553
591
|
} else {
|
|
554
592
|
return query.run({
|
|
@@ -575,7 +613,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
575
613
|
setSignal = <T>(signalDef: SignalDef<T>, value: T | ((prev: T) => T)): void => {
|
|
576
614
|
this.checkShutdown('setSignal')
|
|
577
615
|
|
|
578
|
-
const signalRef = signalDef.make(this.reactivityGraph.context!)
|
|
616
|
+
const signalRef = signalDef.make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
579
617
|
const newValue: T = typeof value === 'function' ? (value as any)(signalRef.value.get()) : value
|
|
580
618
|
signalRef.value.set(newValue)
|
|
581
619
|
|
|
@@ -664,13 +702,13 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
664
702
|
const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
|
|
665
703
|
|
|
666
704
|
Effect.gen(this, function* () {
|
|
667
|
-
const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)
|
|
705
|
+
const commitsSpan = otel.trace.getSpan(this[StoreInternalsSymbol].otel.commitsSpanContext)
|
|
668
706
|
commitsSpan?.addEvent('commit')
|
|
669
707
|
const currentSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
670
708
|
commitsSpan?.addLink({ context: currentSpan.spanContext() })
|
|
671
709
|
|
|
672
710
|
for (const event of events) {
|
|
673
|
-
replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
|
|
711
|
+
replaceSessionIdSymbol(event.args, this[StoreInternalsSymbol].clientSession.sessionId)
|
|
674
712
|
}
|
|
675
713
|
|
|
676
714
|
if (events.length === 0) return
|
|
@@ -680,11 +718,11 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
680
718
|
const materializeEventsTx = Effect.try({
|
|
681
719
|
try: () => {
|
|
682
720
|
const runMaterializeEvents = () => {
|
|
683
|
-
return this.syncProcessor.push(events).pipe(Runtime.runSync(localRuntime))
|
|
721
|
+
return this[StoreInternalsSymbol].syncProcessor.push(events).pipe(Runtime.runSync(localRuntime))
|
|
684
722
|
}
|
|
685
723
|
|
|
686
724
|
if (events.length > 1) {
|
|
687
|
-
return this.sqliteDbWrapper.txn(runMaterializeEvents)
|
|
725
|
+
return this[StoreInternalsSymbol].sqliteDbWrapper.txn(runMaterializeEvents)
|
|
688
726
|
} else {
|
|
689
727
|
return runMaterializeEvents()
|
|
690
728
|
}
|
|
@@ -697,7 +735,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
697
735
|
|
|
698
736
|
const tablesToUpdate: [Ref<null, ReactivityGraphContext, RefreshReason>, null][] = []
|
|
699
737
|
for (const tableName of writeTables) {
|
|
700
|
-
const tableRef = this.tableRefs[tableName]
|
|
738
|
+
const tableRef = this[StoreInternalsSymbol].tableRefs[tableName]
|
|
701
739
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
702
740
|
tablesToUpdate.push([tableRef!, null])
|
|
703
741
|
}
|
|
@@ -710,7 +748,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
710
748
|
const skipRefresh = options?.skipRefresh ?? false
|
|
711
749
|
|
|
712
750
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
713
|
-
this.reactivityGraph.setRefs(tablesToUpdate, {
|
|
751
|
+
this[StoreInternalsSymbol].reactivityGraph.setRefs(tablesToUpdate, {
|
|
714
752
|
debugRefreshReason,
|
|
715
753
|
skipRefresh,
|
|
716
754
|
otelContext: otel.trace.setSpan(otel.context.active(), currentSpan),
|
|
@@ -725,14 +763,16 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
725
763
|
},
|
|
726
764
|
links: [
|
|
727
765
|
// Span link to LiveStore:commits
|
|
728
|
-
OtelTracer.makeSpanLink({
|
|
766
|
+
OtelTracer.makeSpanLink({
|
|
767
|
+
context: otel.trace.getSpanContext(this[StoreInternalsSymbol].otel.commitsSpanContext)!,
|
|
768
|
+
}),
|
|
729
769
|
// User-provided span links
|
|
730
770
|
...(options?.spanLinks?.map(OtelTracer.makeSpanLink) ?? []),
|
|
731
771
|
],
|
|
732
772
|
}),
|
|
733
773
|
Effect.tapErrorCause(Effect.logError),
|
|
734
774
|
Effect.catchAllCause((cause) => Effect.fork(this.shutdown(cause))),
|
|
735
|
-
Runtime.runSync(this.effectContext.runtime),
|
|
775
|
+
Runtime.runSync(this[StoreInternalsSymbol].effectContext.runtime),
|
|
736
776
|
)
|
|
737
777
|
}
|
|
738
778
|
// #endregion commit
|
|
@@ -775,13 +815,13 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
775
815
|
this.checkShutdown('manualRefresh')
|
|
776
816
|
|
|
777
817
|
const { label } = options ?? {}
|
|
778
|
-
this.otel.tracer.startActiveSpan(
|
|
818
|
+
this[StoreInternalsSymbol].otel.tracer.startActiveSpan(
|
|
779
819
|
'LiveStore:manualRefresh',
|
|
780
820
|
{ attributes: { 'livestore.manualRefreshLabel': label } },
|
|
781
|
-
this.otel.commitsSpanContext,
|
|
821
|
+
this[StoreInternalsSymbol].otel.commitsSpanContext,
|
|
782
822
|
(span) => {
|
|
783
823
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
784
|
-
this.reactivityGraph.runDeferredEffects({ otelContext })
|
|
824
|
+
this[StoreInternalsSymbol].reactivityGraph.runDeferredEffects({ otelContext })
|
|
785
825
|
span.end()
|
|
786
826
|
},
|
|
787
827
|
)
|
|
@@ -795,7 +835,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
795
835
|
shutdownPromise = async (cause?: UnexpectedError) => {
|
|
796
836
|
this.checkShutdown('shutdownPromise')
|
|
797
837
|
|
|
798
|
-
this.isShutdown = true
|
|
838
|
+
this[StoreInternalsSymbol].isShutdown = true
|
|
799
839
|
await this.shutdown(cause ? Cause.fail(cause) : undefined).pipe(this.runEffectFork, Fiber.join, Effect.runPromise)
|
|
800
840
|
}
|
|
801
841
|
|
|
@@ -805,8 +845,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
805
845
|
* This is called automatically when the store was created using the React or Effect API.
|
|
806
846
|
*/
|
|
807
847
|
shutdown = (cause?: Cause.Cause<UnexpectedError | MaterializeError>): Effect.Effect<void> => {
|
|
808
|
-
this.isShutdown = true
|
|
809
|
-
return this.clientSession.shutdown(
|
|
848
|
+
this[StoreInternalsSymbol].isShutdown = true
|
|
849
|
+
return this[StoreInternalsSymbol].clientSession.shutdown(
|
|
810
850
|
cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
|
|
811
851
|
)
|
|
812
852
|
}
|
|
@@ -819,30 +859,33 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
819
859
|
_dev = {
|
|
820
860
|
downloadDb: (source: 'local' | 'leader' = 'local') => {
|
|
821
861
|
Effect.gen(this, function* () {
|
|
822
|
-
const data =
|
|
862
|
+
const data =
|
|
863
|
+
source === 'local'
|
|
864
|
+
? this[StoreInternalsSymbol].sqliteDbWrapper.export()
|
|
865
|
+
: yield* this[StoreInternalsSymbol].clientSession.leaderThread.export
|
|
823
866
|
downloadBlob(data, `livestore-${Date.now()}.db`)
|
|
824
867
|
}).pipe(this.runEffectFork)
|
|
825
868
|
},
|
|
826
869
|
|
|
827
870
|
downloadEventlogDb: () => {
|
|
828
871
|
Effect.gen(this, function* () {
|
|
829
|
-
const data = yield* this.clientSession.leaderThread.getEventlogData
|
|
872
|
+
const data = yield* this[StoreInternalsSymbol].clientSession.leaderThread.getEventlogData
|
|
830
873
|
downloadBlob(data, `livestore-eventlog-${Date.now()}.db`)
|
|
831
874
|
}).pipe(this.runEffectFork)
|
|
832
875
|
},
|
|
833
876
|
|
|
834
877
|
hardReset: (mode: 'all-data' | 'only-app-db' = 'all-data') => {
|
|
835
878
|
Effect.gen(this, function* () {
|
|
836
|
-
const clientId = this.clientSession.clientId
|
|
837
|
-
yield* this.clientSession.leaderThread.sendDevtoolsMessage(
|
|
879
|
+
const clientId = this[StoreInternalsSymbol].clientSession.clientId
|
|
880
|
+
yield* this[StoreInternalsSymbol].clientSession.leaderThread.sendDevtoolsMessage(
|
|
838
881
|
Devtools.Leader.ResetAllData.Request.make({ liveStoreVersion, mode, requestId: nanoid(), clientId }),
|
|
839
882
|
)
|
|
840
883
|
}).pipe(this.runEffectFork)
|
|
841
884
|
},
|
|
842
885
|
|
|
843
886
|
overrideNetworkStatus: (status: 'online' | 'offline') => {
|
|
844
|
-
const clientId = this.clientSession.clientId
|
|
845
|
-
this.clientSession.leaderThread
|
|
887
|
+
const clientId = this[StoreInternalsSymbol].clientSession.clientId
|
|
888
|
+
this[StoreInternalsSymbol].clientSession.leaderThread
|
|
846
889
|
.sendDevtoolsMessage(
|
|
847
890
|
Devtools.Leader.SetSyncLatch.Request.make({
|
|
848
891
|
clientId,
|
|
@@ -856,19 +899,19 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
856
899
|
|
|
857
900
|
syncStates: () =>
|
|
858
901
|
Effect.gen(this, function* () {
|
|
859
|
-
const session = yield* this.syncProcessor.syncState
|
|
860
|
-
const leader = yield* this.clientSession.leaderThread.syncState
|
|
902
|
+
const session = yield* this[StoreInternalsSymbol].syncProcessor.syncState
|
|
903
|
+
const leader = yield* this[StoreInternalsSymbol].clientSession.leaderThread.syncState
|
|
861
904
|
return { session, leader }
|
|
862
905
|
}).pipe(this.runEffectPromise),
|
|
863
906
|
|
|
864
907
|
printSyncStates: () => {
|
|
865
908
|
Effect.gen(this, function* () {
|
|
866
|
-
const session = yield* this.syncProcessor.syncState
|
|
909
|
+
const session = yield* this[StoreInternalsSymbol].syncProcessor.syncState
|
|
867
910
|
yield* Effect.log(
|
|
868
911
|
`Session sync state: ${session.localHead} (upstream: ${session.upstreamHead})`,
|
|
869
912
|
session.toJSON(),
|
|
870
913
|
)
|
|
871
|
-
const leader = yield* this.clientSession.leaderThread.syncState
|
|
914
|
+
const leader = yield* this[StoreInternalsSymbol].clientSession.leaderThread.syncState
|
|
872
915
|
yield* Effect.log(`Leader sync state: ${leader.localHead} (upstream: ${leader.upstreamHead})`, leader.toJSON())
|
|
873
916
|
}).pipe(this.runEffectFork)
|
|
874
917
|
},
|
|
@@ -876,25 +919,25 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
876
919
|
version: liveStoreVersion,
|
|
877
920
|
|
|
878
921
|
otel: {
|
|
879
|
-
rootSpanContext: () => otel.trace.getSpan(this.otel.rootSpanContext)?.spanContext(),
|
|
922
|
+
rootSpanContext: () => otel.trace.getSpan(this[StoreInternalsSymbol].otel.rootSpanContext)?.spanContext(),
|
|
880
923
|
},
|
|
881
924
|
}
|
|
882
925
|
|
|
883
926
|
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
|
884
927
|
toJSON = () => ({
|
|
885
928
|
_tag: 'livestore.Store',
|
|
886
|
-
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
|
|
929
|
+
reactivityGraph: this[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults: true }),
|
|
887
930
|
})
|
|
888
931
|
|
|
889
932
|
private runEffectFork = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
|
890
933
|
effect.pipe(
|
|
891
|
-
Effect.forkIn(this.effectContext.lifetimeScope),
|
|
934
|
+
Effect.forkIn(this[StoreInternalsSymbol].effectContext.lifetimeScope),
|
|
892
935
|
Effect.tapCauseLogPretty,
|
|
893
|
-
Runtime.runFork(this.effectContext.runtime),
|
|
936
|
+
Runtime.runFork(this[StoreInternalsSymbol].effectContext.runtime),
|
|
894
937
|
)
|
|
895
938
|
|
|
896
939
|
private runEffectPromise = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
|
897
|
-
effect.pipe(Effect.tapCauseLogPretty, Runtime.runPromise(this.effectContext.runtime))
|
|
940
|
+
effect.pipe(Effect.tapCauseLogPretty, Runtime.runPromise(this[StoreInternalsSymbol].effectContext.runtime))
|
|
898
941
|
|
|
899
942
|
private getCommitArgs = (
|
|
900
943
|
firstEventOrTxnFnOrOptions: any,
|
|
@@ -926,7 +969,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
926
969
|
|
|
927
970
|
// for (const event of events) {
|
|
928
971
|
// if (event.args.id === SessionIdSymbol) {
|
|
929
|
-
// event.args.id = this.
|
|
972
|
+
// event.args.id = this.sessionId
|
|
930
973
|
// }
|
|
931
974
|
// }
|
|
932
975
|
|