@livestore/livestore 0.3.0-dev.11 → 0.3.0-dev.5
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/SynchronousDatabaseWrapper.d.ts +5 -14
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +4 -24
- package/dist/SynchronousDatabaseWrapper.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +8 -12
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +3 -13
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +21 -64
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +13 -56
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts +7 -7
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +11 -35
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db.d.ts +15 -12
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +25 -44
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +14 -16
- package/dist/live-queries/db.test.js.map +1 -1
- package/dist/live-queries/graphql.d.ts +8 -8
- package/dist/live-queries/graphql.d.ts.map +1 -1
- package/dist/live-queries/graphql.js +9 -35
- package/dist/live-queries/graphql.js.map +1 -1
- package/dist/reactive.d.ts +13 -15
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +9 -15
- package/dist/reactive.js.map +1 -1
- package/dist/row-query-utils.d.ts +4 -4
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +10 -14
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +4 -3
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +7 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +2 -2
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +15 -15
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +5 -10
- 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 +16 -34
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +77 -129
- package/dist/store/store.js.map +1 -1
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +1 -6
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +1 -54
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +6 -2
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +5 -3
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +0 -1
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +0 -1
- package/dist/utils/tests/mod.js.map +1 -1
- package/package.json +12 -12
- package/src/{SqliteDbWrapper.ts → SynchronousDatabaseWrapper.ts} +11 -41
- package/src/effect/LiveStore.ts +15 -26
- package/src/global-state.ts +20 -0
- package/src/index.ts +7 -14
- package/src/live-queries/__snapshots__/{db-query.test.ts.snap → db.test.ts.snap} +42 -196
- package/src/live-queries/base-class.ts +40 -160
- package/src/live-queries/computed.ts +19 -45
- package/src/live-queries/{db-query.test.ts → db.test.ts} +11 -21
- package/src/live-queries/{db-query.ts → db.ts} +39 -97
- package/src/live-queries/graphql.ts +21 -47
- package/src/reactive.ts +27 -52
- package/src/row-query-utils.ts +18 -29
- package/src/store/create-store.ts +23 -20
- package/src/store/devtools.ts +17 -17
- package/src/store/store-types.ts +5 -7
- package/src/store/store.ts +122 -231
- package/src/utils/stack-info.test.ts +1 -58
- package/src/utils/stack-info.ts +1 -6
- package/src/utils/tests/fixture.ts +7 -2
- package/src/utils/tests/mod.ts +0 -1
- package/dist/SqliteDbWrapper.d.ts +0 -54
- package/dist/SqliteDbWrapper.d.ts.map +0 -1
- package/dist/SqliteDbWrapper.js +0 -212
- package/dist/SqliteDbWrapper.js.map +0 -1
- package/dist/__tests__/fixture.d.ts +0 -252
- package/dist/__tests__/fixture.d.ts.map +0 -1
- package/dist/__tests__/fixture.js +0 -18
- package/dist/__tests__/fixture.js.map +0 -1
- package/dist/live-queries/db-query.d.ts +0 -67
- package/dist/live-queries/db-query.d.ts.map +0 -1
- package/dist/live-queries/db-query.js +0 -244
- package/dist/live-queries/db-query.js.map +0 -1
- package/dist/live-queries/db-query.test.d.ts +0 -2
- package/dist/live-queries/db-query.test.d.ts.map +0 -1
- package/dist/live-queries/db-query.test.js +0 -123
- package/dist/live-queries/db-query.test.js.map +0 -1
- package/dist/live-queries/make-ref.d.ts +0 -20
- package/dist/live-queries/make-ref.d.ts.map +0 -1
- package/dist/live-queries/make-ref.js +0 -33
- package/dist/live-queries/make-ref.js.map +0 -1
- package/dist/store/store.test.d.ts +0 -2
- package/dist/store/store.test.d.ts.map +0 -1
- package/dist/store/store.test.js +0 -27
- package/dist/store/store.test.js.map +0 -1
- package/dist/utils/expo.d.ts +0 -2
- package/dist/utils/expo.d.ts.map +0 -1
- package/dist/utils/expo.js +0 -8
- package/dist/utils/expo.js.map +0 -1
- package/dist/utils/function-string.d.ts +0 -7
- package/dist/utils/function-string.d.ts.map +0 -1
- package/dist/utils/function-string.js +0 -9
- package/dist/utils/function-string.js.map +0 -1
- package/src/live-queries/make-ref.ts +0 -47
- package/src/utils/function-string.ts +0 -12
package/src/store/store.ts
CHANGED
@@ -27,44 +27,19 @@ import {
|
|
27
27
|
} from '@livestore/common/schema'
|
28
28
|
import { assertNever, isDevEnv } from '@livestore/utils'
|
29
29
|
import type { Scope } from '@livestore/utils/effect'
|
30
|
-
import {
|
31
|
-
Cause,
|
32
|
-
Data,
|
33
|
-
Effect,
|
34
|
-
Inspectable,
|
35
|
-
MutableHashMap,
|
36
|
-
OtelTracer,
|
37
|
-
Runtime,
|
38
|
-
Schema,
|
39
|
-
Stream,
|
40
|
-
} from '@livestore/utils/effect'
|
30
|
+
import { Cause, Data, Effect, Inspectable, MutableHashMap, Runtime, Schema } from '@livestore/utils/effect'
|
41
31
|
import { nanoid } from '@livestore/utils/nanoid'
|
42
32
|
import * as otel from '@opentelemetry/api'
|
43
33
|
import { type GraphQLSchema } from 'graphql'
|
44
34
|
|
45
|
-
import type {
|
46
|
-
ILiveQueryRefDef,
|
47
|
-
LiveQuery,
|
48
|
-
LiveQueryDef,
|
49
|
-
ReactivityGraph,
|
50
|
-
ReactivityGraphContext,
|
51
|
-
} from '../live-queries/base-class.js'
|
52
|
-
import { makeReactivityGraph } from '../live-queries/base-class.js'
|
35
|
+
import type { LiveQuery, QueryContext, ReactivityGraph } from '../live-queries/base-class.js'
|
53
36
|
import type { Ref } from '../reactive.js'
|
54
37
|
import { makeExecBeforeFirstRun } from '../row-query-utils.js'
|
55
|
-
import {
|
38
|
+
import { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
56
39
|
import { ReferenceCountedSet } from '../utils/data-structures.js'
|
57
40
|
import { downloadBlob, exposeDebugUtils } from '../utils/dev.js'
|
58
41
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
59
|
-
import type {
|
60
|
-
import type {
|
61
|
-
BaseGraphQLContext,
|
62
|
-
RefreshReason,
|
63
|
-
StoreMutateOptions,
|
64
|
-
StoreOptions,
|
65
|
-
StoreOtel,
|
66
|
-
Unsubscribe,
|
67
|
-
} from './store-types.js'
|
42
|
+
import type { BaseGraphQLContext, RefreshReason, StoreMutateOptions, StoreOptions, StoreOtel } from './store-types.js'
|
68
43
|
|
69
44
|
if (isDevEnv()) {
|
70
45
|
exposeDebugUtils()
|
@@ -76,7 +51,7 @@ export class Store<
|
|
76
51
|
> extends Inspectable.Class {
|
77
52
|
readonly storeId: string
|
78
53
|
reactivityGraph: ReactivityGraph
|
79
|
-
|
54
|
+
syncDbWrapper: SynchronousDatabaseWrapper
|
80
55
|
clientSession: ClientSession
|
81
56
|
schema: LiveStoreSchema
|
82
57
|
graphQLSchema?: GraphQLSchema
|
@@ -86,7 +61,7 @@ export class Store<
|
|
86
61
|
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
87
62
|
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
88
63
|
*/
|
89
|
-
tableRefs: { [key: string]: Ref<null,
|
64
|
+
tableRefs: { [key: string]: Ref<null, QueryContext, RefreshReason> }
|
90
65
|
|
91
66
|
private runtime: Runtime.Runtime<Scope.Scope>
|
92
67
|
|
@@ -99,13 +74,12 @@ export class Store<
|
|
99
74
|
private syncProcessor: ClientSessionSyncProcessor
|
100
75
|
readonly lifetimeScope: Scope.Scope
|
101
76
|
|
102
|
-
readonly boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
|
103
|
-
|
104
77
|
// #region constructor
|
105
|
-
constructor({
|
78
|
+
private constructor({
|
106
79
|
clientSession,
|
107
80
|
schema,
|
108
81
|
graphQLOptions,
|
82
|
+
reactivityGraph,
|
109
83
|
otelOptions,
|
110
84
|
disableDevtools,
|
111
85
|
batchUpdates,
|
@@ -119,27 +93,29 @@ export class Store<
|
|
119
93
|
this.storeId = storeId
|
120
94
|
this.unsyncedMutationEvents = unsyncedMutationEvents
|
121
95
|
|
122
|
-
this.
|
96
|
+
this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: clientSession.syncDb })
|
123
97
|
this.clientSession = clientSession
|
124
98
|
this.schema = schema
|
125
99
|
|
126
100
|
this.lifetimeScope = lifetimeScope
|
127
101
|
this.runtime = runtime
|
128
102
|
|
129
|
-
const reactivityGraph = makeReactivityGraph()
|
130
|
-
|
131
103
|
const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext)
|
132
104
|
|
133
105
|
this.syncProcessor = makeClientSessionSyncProcessor({
|
134
106
|
schema,
|
135
|
-
clientSession,
|
136
|
-
|
107
|
+
initialLeaderHead: clientSession.leaderThread.mutations.initialMutationEventId,
|
108
|
+
// rebaseBehaviour: 'auto-rebase',
|
109
|
+
pushToLeader: (batch) =>
|
110
|
+
clientSession.leaderThread.mutations.push(batch).pipe(
|
111
|
+
// NOTE we don't want to shutdown in case of an invalid push error, since it will be retried
|
112
|
+
Effect.catchTag('InvalidPushError', Effect.ignoreLogged),
|
113
|
+
this.runEffectFork,
|
114
|
+
),
|
115
|
+
pullFromLeader: clientSession.leaderThread.mutations.pull,
|
137
116
|
applyMutation: (mutationEventDecoded, { otelContext, withChangeset }) => {
|
138
117
|
const mutationDef = schema.mutations.get(mutationEventDecoded.mutation)!
|
139
|
-
const execArgsArr = getExecArgsFromMutation({
|
140
|
-
mutationDef,
|
141
|
-
mutationEvent: { decoded: mutationEventDecoded, encoded: undefined },
|
142
|
-
})
|
118
|
+
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
143
119
|
|
144
120
|
const writeTablesForEvent = new Set<string>()
|
145
121
|
|
@@ -147,9 +123,9 @@ export class Store<
|
|
147
123
|
for (const {
|
148
124
|
statementSql,
|
149
125
|
bindValues,
|
150
|
-
writeTables = this.
|
126
|
+
writeTables = this.syncDbWrapper.getTablesUsed(statementSql),
|
151
127
|
} of execArgsArr) {
|
152
|
-
this.
|
128
|
+
this.syncDbWrapper.execute(statementSql, bindValues, writeTables, { otelContext })
|
153
129
|
|
154
130
|
// durationMsTotal += durationMs
|
155
131
|
writeTables.forEach((table) => writeTablesForEvent.add(table))
|
@@ -158,7 +134,7 @@ export class Store<
|
|
158
134
|
|
159
135
|
let sessionChangeset: Uint8Array | undefined
|
160
136
|
if (withChangeset === true) {
|
161
|
-
sessionChangeset = this.
|
137
|
+
sessionChangeset = this.syncDbWrapper.withChangeset(exec).changeset
|
162
138
|
} else {
|
163
139
|
exec()
|
164
140
|
}
|
@@ -166,16 +142,16 @@ export class Store<
|
|
166
142
|
return { writeTables: writeTablesForEvent, sessionChangeset }
|
167
143
|
},
|
168
144
|
rollback: (changeset) => {
|
169
|
-
this.
|
145
|
+
this.syncDbWrapper.rollback(changeset)
|
170
146
|
},
|
171
147
|
refreshTables: (tables) => {
|
172
|
-
const tablesToUpdate = [] as [Ref<null,
|
148
|
+
const tablesToUpdate = [] as [Ref<null, QueryContext, RefreshReason>, null][]
|
173
149
|
for (const tableName of tables) {
|
174
150
|
const tableRef = this.tableRefs[tableName]
|
175
151
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
176
152
|
tablesToUpdate.push([tableRef!, null])
|
177
153
|
}
|
178
|
-
reactivityGraph.setRefs(tablesToUpdate)
|
154
|
+
this.reactivityGraph.setRefs(tablesToUpdate)
|
179
155
|
},
|
180
156
|
span: syncSpan,
|
181
157
|
})
|
@@ -195,8 +171,6 @@ export class Store<
|
|
195
171
|
this.reactivityGraph = reactivityGraph
|
196
172
|
this.reactivityGraph.context = {
|
197
173
|
store: this as unknown as Store<BaseGraphQLContext, LiveStoreSchema>,
|
198
|
-
liveQueryRCMap: new Map(),
|
199
|
-
reactivityGraph: new WeakRef(reactivityGraph),
|
200
174
|
otelTracer: otelOptions.tracer,
|
201
175
|
rootOtelContext: otelQueriesSpanContext,
|
202
176
|
effectsWrapper: batchUpdates,
|
@@ -233,10 +207,10 @@ export class Store<
|
|
233
207
|
|
234
208
|
if (graphQLOptions) {
|
235
209
|
this.graphQLSchema = graphQLOptions.schema
|
236
|
-
this.graphQLContext = graphQLOptions.makeContext(this.
|
210
|
+
this.graphQLContext = graphQLOptions.makeContext(this.syncDbWrapper, this.otel.tracer, clientSession.sessionId)
|
237
211
|
}
|
238
212
|
|
239
|
-
|
213
|
+
Effect.gen(this, function* () {
|
240
214
|
yield* Effect.addFinalizer(() =>
|
241
215
|
Effect.sync(() => {
|
242
216
|
// Remove all table refs from the reactivity graph
|
@@ -254,10 +228,24 @@ export class Store<
|
|
254
228
|
)
|
255
229
|
|
256
230
|
yield* this.syncProcessor.boot
|
257
|
-
})
|
231
|
+
}).pipe(this.runEffectFork)
|
258
232
|
}
|
259
233
|
// #endregion constructor
|
260
234
|
|
235
|
+
static createStore = <TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema = LiveStoreSchema>(
|
236
|
+
storeOptions: StoreOptions<TGraphQLContext, TSchema>,
|
237
|
+
parentSpan: otel.Span,
|
238
|
+
): Store<TGraphQLContext, TSchema> => {
|
239
|
+
const ctx = otel.trace.setSpan(otel.context.active(), parentSpan)
|
240
|
+
return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:createStore', {}, ctx, (span) => {
|
241
|
+
try {
|
242
|
+
return new Store(storeOptions)
|
243
|
+
} finally {
|
244
|
+
span.end()
|
245
|
+
}
|
246
|
+
})
|
247
|
+
}
|
248
|
+
|
261
249
|
get sessionId(): string {
|
262
250
|
return this.clientSession.sessionId
|
263
251
|
}
|
@@ -265,66 +253,29 @@ export class Store<
|
|
265
253
|
/**
|
266
254
|
* Subscribe to the results of a query
|
267
255
|
* Returns a function to cancel the subscription.
|
268
|
-
*
|
269
|
-
* @example
|
270
|
-
* ```ts
|
271
|
-
* const unsubscribe = store.subscribe(query$, { onUpdate: (result) => console.log(result) })
|
272
|
-
* ```
|
273
256
|
*/
|
274
257
|
subscribe = <TResult>(
|
275
|
-
query
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
/** Gets called after the query subscription has been removed */
|
281
|
-
onUnsubsubscribe?: () => void
|
282
|
-
label?: string
|
283
|
-
/**
|
284
|
-
* Skips the initial `onUpdate` callback
|
285
|
-
* @default false
|
286
|
-
*/
|
287
|
-
skipInitialRun?: boolean
|
288
|
-
otelContext?: otel.Context
|
289
|
-
/** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
|
290
|
-
stackInfo?: StackInfo
|
291
|
-
},
|
292
|
-
): Unsubscribe =>
|
258
|
+
query$: LiveQuery<TResult, any>,
|
259
|
+
onNewValue: (value: TResult) => void,
|
260
|
+
onUnsubsubscribe?: () => void,
|
261
|
+
options?: { label?: string; otelContext?: otel.Context; skipInitialRun?: boolean } | undefined,
|
262
|
+
): (() => void) =>
|
293
263
|
this.otel.tracer.startActiveSpan(
|
294
264
|
`LiveStore.subscribe`,
|
295
|
-
{ attributes: { label: options?.label, queryLabel: query
|
265
|
+
{ attributes: { label: options?.label, queryLabel: query$.label } },
|
296
266
|
options?.otelContext ?? this.otel.queriesSpanContext,
|
297
267
|
(span) => {
|
298
268
|
// console.debug('store sub', query$.id, query$.label)
|
299
269
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
300
270
|
|
301
|
-
const queryRcRef =
|
302
|
-
query._tag === 'def'
|
303
|
-
? query.make(this.reactivityGraph.context!)
|
304
|
-
: {
|
305
|
-
value: query,
|
306
|
-
deref: () => {},
|
307
|
-
}
|
308
|
-
const query$ = queryRcRef.value
|
309
|
-
|
310
271
|
const label = `subscribe:${options?.label}`
|
311
|
-
const effect = this.reactivityGraph.makeEffect(
|
312
|
-
(get, _otelContext, debugRefreshReason) =>
|
313
|
-
options.onUpdate(get(query$.results$, otelContext, debugRefreshReason)),
|
314
|
-
{ label },
|
315
|
-
)
|
316
|
-
|
317
|
-
if (options?.stackInfo) {
|
318
|
-
query$.activeSubscriptions.add(options.stackInfo)
|
319
|
-
}
|
320
|
-
|
321
|
-
options?.onSubscribe?.(query$)
|
272
|
+
const effect = this.reactivityGraph.makeEffect((get) => onNewValue(get(query$.results$)), { label })
|
322
273
|
|
323
274
|
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
324
275
|
|
325
276
|
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
326
|
-
if (options?.skipInitialRun !== true
|
327
|
-
effect.doEffect(otelContext
|
277
|
+
if (options?.skipInitialRun !== true) {
|
278
|
+
effect.doEffect(otelContext)
|
328
279
|
}
|
329
280
|
|
330
281
|
const unsubscribe = () => {
|
@@ -332,14 +283,7 @@ export class Store<
|
|
332
283
|
try {
|
333
284
|
this.reactivityGraph.destroyNode(effect)
|
334
285
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
335
|
-
|
336
|
-
if (options?.stackInfo) {
|
337
|
-
query$.activeSubscriptions.delete(options.stackInfo)
|
338
|
-
}
|
339
|
-
|
340
|
-
queryRcRef.deref()
|
341
|
-
|
342
|
-
options?.onUnsubsubscribe?.()
|
286
|
+
onUnsubsubscribe?.()
|
343
287
|
} finally {
|
344
288
|
span.end()
|
345
289
|
}
|
@@ -349,30 +293,6 @@ export class Store<
|
|
349
293
|
},
|
350
294
|
)
|
351
295
|
|
352
|
-
subscribeStream = <TResult>(
|
353
|
-
query$: LiveQueryDef<TResult, any>,
|
354
|
-
options?: { label?: string; skipInitialRun?: boolean } | undefined,
|
355
|
-
): Stream.Stream<TResult> =>
|
356
|
-
Stream.asyncPush<TResult>((emit) =>
|
357
|
-
Effect.gen(this, function* () {
|
358
|
-
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(
|
359
|
-
Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)),
|
360
|
-
)
|
361
|
-
const otelContext = otelSpan ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active()
|
362
|
-
|
363
|
-
yield* Effect.acquireRelease(
|
364
|
-
Effect.sync(() =>
|
365
|
-
this.subscribe(query$, {
|
366
|
-
onUpdate: (result) => emit.single(result),
|
367
|
-
otelContext,
|
368
|
-
label: options?.label,
|
369
|
-
}),
|
370
|
-
),
|
371
|
-
(unsub) => Effect.sync(() => unsub()),
|
372
|
-
)
|
373
|
-
}),
|
374
|
-
)
|
375
|
-
|
376
296
|
/**
|
377
297
|
* Synchronously queries the database without creating a LiveQuery.
|
378
298
|
* This is useful for queries that don't need to be reactive.
|
@@ -388,15 +308,12 @@ export class Store<
|
|
388
308
|
* ```
|
389
309
|
*/
|
390
310
|
query = <TResult>(
|
391
|
-
query:
|
392
|
-
|
393
|
-
| LiveQuery<TResult, any>
|
394
|
-
| LiveQueryDef<TResult, any>
|
395
|
-
| { query: string; bindValues: ParamsObject },
|
396
|
-
options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
|
311
|
+
query: QueryBuilder<TResult, any, any> | LiveQuery<TResult, any> | { query: string; bindValues: ParamsObject },
|
312
|
+
options?: { otelContext?: otel.Context },
|
397
313
|
): TResult => {
|
398
314
|
if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
|
399
|
-
return this.
|
315
|
+
return this.syncDbWrapper.select(query.query, {
|
316
|
+
bindValues: prepareBindValues(query.bindValues, query.query),
|
400
317
|
otelContext: options?.otelContext,
|
401
318
|
}) as any
|
402
319
|
} else if (isQueryBuilder(query)) {
|
@@ -412,38 +329,17 @@ export class Store<
|
|
412
329
|
|
413
330
|
const sqlRes = query.asSql()
|
414
331
|
const schema = getResultSchema(query)
|
415
|
-
const rawRes = this.
|
332
|
+
const rawRes = this.syncDbWrapper.select(sqlRes.query, {
|
333
|
+
bindValues: sqlRes.bindValues as any as PreparedBindValues,
|
416
334
|
otelContext: options?.otelContext,
|
417
335
|
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
418
336
|
})
|
419
337
|
return Schema.decodeSync(schema)(rawRes)
|
420
|
-
} else if (query._tag === 'def') {
|
421
|
-
const query$ = query.make(this.reactivityGraph.context!)
|
422
|
-
const result = this.query(query$.value, options)
|
423
|
-
query$.deref()
|
424
|
-
return result
|
425
338
|
} else {
|
426
|
-
return query.run(
|
339
|
+
return query.run(options?.otelContext)
|
427
340
|
}
|
428
341
|
}
|
429
342
|
|
430
|
-
// makeLive: {
|
431
|
-
// <T>(def: LiveQueryDef<T, any>): LiveQuery<T, any>
|
432
|
-
// <T>(def: ILiveQueryRefDef<T>): ILiveQueryRef<T>
|
433
|
-
// } = (def: any) => {
|
434
|
-
// if (def._tag === 'live-ref-def') {
|
435
|
-
// return (def as ILiveQueryRefDef<any>).make(this.reactivityGraph.context!)
|
436
|
-
// } else {
|
437
|
-
// return (def as LiveQueryDef<any, any>).make(this.reactivityGraph.context!) as any
|
438
|
-
// }
|
439
|
-
// }
|
440
|
-
|
441
|
-
setRef = <T>(refDef: ILiveQueryRefDef<T>, value: T): void => {
|
442
|
-
const ref = refDef.make(this.reactivityGraph.context!)
|
443
|
-
ref.value.set(value)
|
444
|
-
ref.deref()
|
445
|
-
}
|
446
|
-
|
447
343
|
// #region mutate
|
448
344
|
mutate: {
|
449
345
|
<const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(...list: TMutationArg): void
|
@@ -471,6 +367,7 @@ export class Store<
|
|
471
367
|
|
472
368
|
if (mutationsEvents.length === 0) return
|
473
369
|
|
370
|
+
const label = options?.label ?? 'mutate'
|
474
371
|
const skipRefresh = options?.skipRefresh ?? false
|
475
372
|
|
476
373
|
const mutationsSpan = otel.trace.getSpan(this.otel.mutationsSpanContext)!
|
@@ -484,39 +381,40 @@ export class Store<
|
|
484
381
|
|
485
382
|
return this.otel.tracer.startActiveSpan(
|
486
383
|
'LiveStore:mutate',
|
487
|
-
{
|
488
|
-
attributes: {
|
489
|
-
'livestore.mutationEventsCount': mutationsEvents.length,
|
490
|
-
'livestore.mutationEventTags': mutationsEvents.map((_) => _.mutation),
|
491
|
-
'livestore.mutateLabel': options?.label,
|
492
|
-
},
|
493
|
-
links: options?.spanLinks,
|
494
|
-
},
|
384
|
+
{ attributes: { 'livestore.mutateLabel': label }, links: options?.spanLinks },
|
495
385
|
options?.otelContext ?? this.otel.mutationsSpanContext,
|
496
386
|
(span) => {
|
497
387
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
498
388
|
|
499
389
|
try {
|
500
|
-
const { writeTables } = (
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
390
|
+
const { writeTables } = this.otel.tracer.startActiveSpan(
|
391
|
+
'LiveStore:mutate:applyMutations',
|
392
|
+
{ attributes: { 'livestore.mutateLabel': label } },
|
393
|
+
otel.trace.setSpan(otel.context.active(), span),
|
394
|
+
(span) => {
|
395
|
+
try {
|
396
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
397
|
+
// 5
|
398
|
+
|
399
|
+
const applyMutations = () => this.syncProcessor.push(mutationsEvents, { otelContext })
|
400
|
+
|
401
|
+
if (mutationsEvents.length > 1) {
|
402
|
+
// TODO: what to do about leader transaction here?
|
403
|
+
return this.syncDbWrapper.txn(applyMutations)
|
404
|
+
} else {
|
405
|
+
return applyMutations()
|
406
|
+
}
|
407
|
+
} catch (e: any) {
|
408
|
+
console.error(e)
|
409
|
+
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
410
|
+
throw e
|
411
|
+
} finally {
|
412
|
+
span.end()
|
509
413
|
}
|
510
|
-
}
|
511
|
-
|
512
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
513
|
-
throw e
|
514
|
-
} finally {
|
515
|
-
span.end()
|
516
|
-
}
|
517
|
-
})()
|
414
|
+
},
|
415
|
+
)
|
518
416
|
|
519
|
-
const tablesToUpdate = [] as [Ref<null,
|
417
|
+
const tablesToUpdate = [] as [Ref<null, QueryContext, RefreshReason>, null][]
|
520
418
|
for (const tableName of writeTables) {
|
521
419
|
const tableRef = this.tableRefs[tableName]
|
522
420
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
@@ -572,48 +470,41 @@ export class Store<
|
|
572
470
|
meta: { liveStoreRefType: 'table' },
|
573
471
|
})
|
574
472
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
}
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
},
|
611
|
-
|
612
|
-
shutdown: (cause?: Cause.Cause<UnexpectedError>) => {
|
613
|
-
this.clientSession
|
614
|
-
.shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
|
615
|
-
.pipe(Effect.tapCauseLogPretty, Effect.provide(this.runtime), Effect.runFork)
|
616
|
-
},
|
473
|
+
__devDownloadDb = (source: 'local' | 'leader' = 'local') => {
|
474
|
+
Effect.gen(this, function* () {
|
475
|
+
const data = source === 'local' ? this.syncDbWrapper.export() : yield* this.clientSession.leaderThread.export
|
476
|
+
downloadBlob(data, `livestore-${Date.now()}.db`)
|
477
|
+
}).pipe(this.runEffectFork)
|
478
|
+
}
|
479
|
+
|
480
|
+
__devDownloadMutationLogDb = () => {
|
481
|
+
Effect.gen(this, function* () {
|
482
|
+
const data = yield* this.clientSession.leaderThread.getMutationLogData
|
483
|
+
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
484
|
+
}).pipe(this.runEffectFork)
|
485
|
+
}
|
486
|
+
|
487
|
+
__devHardReset = (mode: 'all-data' | 'only-app-db' = 'all-data') => {
|
488
|
+
Effect.gen(this, function* () {
|
489
|
+
yield* this.clientSession.leaderThread.sendDevtoolsMessage(
|
490
|
+
Devtools.ResetAllDataReq.make({ liveStoreVersion, mode, requestId: nanoid() }),
|
491
|
+
)
|
492
|
+
}).pipe(this.runEffectFork)
|
493
|
+
}
|
494
|
+
|
495
|
+
__devSyncStates = () => {
|
496
|
+
Effect.gen(this, function* () {
|
497
|
+
const session = this.syncProcessor.syncStateRef.current
|
498
|
+
console.log('Session sync state:', session)
|
499
|
+
const leader = yield* this.clientSession.leaderThread.getSyncState
|
500
|
+
console.log('Leader sync state:', leader)
|
501
|
+
}).pipe(this.runEffectFork)
|
502
|
+
}
|
503
|
+
|
504
|
+
__devShutdown = (cause?: Cause.Cause<UnexpectedError>) => {
|
505
|
+
this.clientSession
|
506
|
+
.shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
|
507
|
+
.pipe(Effect.tapCauseLogPretty, Effect.provide(this.runtime), Effect.runFork)
|
617
508
|
}
|
618
509
|
|
619
510
|
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
@@ -46,7 +46,7 @@ Error
|
|
46
46
|
|
47
47
|
it('Tracklist_ stacktrace', async () => {
|
48
48
|
const stackTrace = `\
|
49
|
-
Error
|
49
|
+
stack Error
|
50
50
|
at https://localhost:8081/@fs/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/livestore/dist/react/useQuery.js?t=1701368568351:19:23
|
51
51
|
at mountMemo (https://localhost:8081/node_modules/.vite-web/deps/chunk-YKTDXTVC.js?v=86daed82:12817:27)
|
52
52
|
at Object.useMemo (https://localhost:8081/node_modules/.vite-web/deps/chunk-YKTDXTVC.js?v=86daed82:13141:24)
|
@@ -77,60 +77,3 @@ Error
|
|
77
77
|
}
|
78
78
|
`)
|
79
79
|
})
|
80
|
-
|
81
|
-
it('React 19', async () => {
|
82
|
-
const stackTrace = `\
|
83
|
-
Error:
|
84
|
-
at /Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:57:19
|
85
|
-
at mountMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:6816:23)
|
86
|
-
at Object.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22757:18)
|
87
|
-
at Object.process.env.NODE_ENV.exports.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react@19.0.0/node_modules/react/cjs/react.development.js:1488:34)
|
88
|
-
at Module.useQueryRef (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:54:27)
|
89
|
-
at Module.useRow (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20)
|
90
|
-
at TestComponent (/Users/schickling/Code/overtone/node_modules/.pnpm/@testing-library+react@16.1.0_@testing-library+dom@10.4.0_@types+react-dom@19.0.3_@types+reac_2jaiibiag2sxou3wtzbuqx3r5a/node_modules/@testing-library/react/dist/pure.js:309:27)
|
91
|
-
at Object.react-stack-bottom-frame (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22428:20)
|
92
|
-
at renderWithHooks (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:5757:22)
|
93
|
-
`
|
94
|
-
|
95
|
-
const stackInfo = extractStackInfoFromStackTrace(stackTrace)
|
96
|
-
expect(stackInfo).toMatchInlineSnapshot(`
|
97
|
-
{
|
98
|
-
"frames": [
|
99
|
-
{
|
100
|
-
"filePath": "/Users/schickling/Code/overtone/node_modules/.pnpm/@testing-library+react@16.1.0_@testing-library+dom@10.4.0_@types+react-dom@19.0.3_@types+reac_2jaiibiag2sxou3wtzbuqx3r5a/node_modules/@testing-library/react/dist/pure.js:309:27",
|
101
|
-
"name": "TestComponent",
|
102
|
-
},
|
103
|
-
{
|
104
|
-
"filePath": "/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20",
|
105
|
-
"name": "useRow",
|
106
|
-
},
|
107
|
-
],
|
108
|
-
}
|
109
|
-
`)
|
110
|
-
})
|
111
|
-
|
112
|
-
it('React 19 - skip react-stack-bottom-frame', async () => {
|
113
|
-
const stackTrace = `\
|
114
|
-
Error:
|
115
|
-
at /Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:57:19
|
116
|
-
at mountMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:6816:23)
|
117
|
-
at Object.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22757:18)
|
118
|
-
at Object.process.env.NODE_ENV.exports.useMemo (/Users/schickling/Code/overtone/node_modules/.pnpm/react@19.0.0/node_modules/react/cjs/react.development.js:1488:34)
|
119
|
-
at Module.useQueryRef (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useQuery.ts:54:27)
|
120
|
-
at Module.useRow (/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20)
|
121
|
-
at Object.react-stack-bottom-frame (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:22428:20)
|
122
|
-
at renderWithHooks (/Users/schickling/Code/overtone/node_modules/.pnpm/react-dom@19.0.0_react@19.0.0/node_modules/react-dom/cjs/react-dom-client.development.js:5757:22)
|
123
|
-
`
|
124
|
-
|
125
|
-
const stackInfo = extractStackInfoFromStackTrace(stackTrace)
|
126
|
-
expect(stackInfo).toMatchInlineSnapshot(`
|
127
|
-
{
|
128
|
-
"frames": [
|
129
|
-
{
|
130
|
-
"filePath": "/Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/react/src/useRow.ts:111:20",
|
131
|
-
"name": "useRow",
|
132
|
-
},
|
133
|
-
],
|
134
|
-
}
|
135
|
-
`)
|
136
|
-
})
|
package/src/utils/stack-info.ts
CHANGED
@@ -34,20 +34,15 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
|
|
34
34
|
|
35
35
|
while ((match = namePattern.exec(stackTrace)) !== null) {
|
36
36
|
const [, name, filePath] = match as any as [string, string, string]
|
37
|
-
// console.debug(name, filePath)
|
38
37
|
|
39
38
|
// NOTE No idea where this `Module.` comes from - possibly a Vite thing?
|
40
39
|
if ((name.startsWith('use') || name.startsWith('Module.use')) && name.endsWith('QueryRef') === false) {
|
41
40
|
hasReachedStart = true
|
42
|
-
// console.debug('hasReachedStart. adding one more frame.')
|
43
41
|
|
44
42
|
frames.unshift({ name: name.replace(/^Module\./, ''), filePath })
|
45
43
|
} else if (hasReachedStart) {
|
46
44
|
// We've reached the end of the `use*` functions, so we're adding the component name and stop
|
47
|
-
|
48
|
-
if (name !== 'Object.react-stack-bottom-frame') {
|
49
|
-
frames.unshift({ name, filePath })
|
50
|
-
}
|
45
|
+
frames.unshift({ name, filePath })
|
51
46
|
break
|
52
47
|
}
|
53
48
|
}
|