@livestore/livestore 0.3.1-dev.0 → 0.3.2-dev.0
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/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js +8 -3
- package/dist/QueryCache.js.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +17 -4
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +14 -6
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.d.ts +2 -0
- package/dist/SqliteDbWrapper.test.d.ts.map +1 -0
- package/dist/SqliteDbWrapper.test.js +25 -0
- package/dist/SqliteDbWrapper.test.js.map +1 -0
- package/dist/effect/LiveStore.js +1 -1
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/live-queries/client-document-get-query.js +1 -1
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/db-query.js +1 -1
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +91 -1
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/reactive.js +1 -1
- package/dist/reactive.js.map +1 -1
- package/dist/store/create-store.js +1 -1
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.js +9 -5
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store.d.ts +5 -4
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +42 -17
- package/dist/store/store.js.map +1 -1
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +5 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +6 -2
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +44 -54
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +1 -1
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +7 -7
- package/src/QueryCache.ts +9 -4
- package/src/SqliteDbWrapper.test.ts +38 -0
- package/src/SqliteDbWrapper.ts +24 -15
- package/src/effect/LiveStore.ts +1 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +389 -0
- package/src/live-queries/client-document-get-query.ts +1 -1
- package/src/live-queries/db-query.test.ts +144 -1
- package/src/live-queries/db-query.ts +1 -1
- package/src/reactive.ts +1 -1
- package/src/store/create-store.ts +1 -1
- package/src/store/devtools.ts +5 -5
- package/src/store/store.ts +55 -23
- package/src/utils/stack-info.test.ts +6 -2
- package/src/utils/stack-info.ts +5 -1
- package/src/utils/tests/otel.ts +1 -1
package/src/reactive.ts
CHANGED
@@ -279,7 +279,7 @@ export class ReactiveGraph<
|
|
279
279
|
return compute(atom, otelContext, debugRefreshReason)
|
280
280
|
}
|
281
281
|
|
282
|
-
let debugInfo: TDebugThunkInfo | undefined
|
282
|
+
let debugInfo: TDebugThunkInfo | undefined
|
283
283
|
const setDebugInfo = (debugInfo_: TDebugThunkInfo) => {
|
284
284
|
debugInfo = debugInfo_
|
285
285
|
}
|
@@ -38,7 +38,7 @@ import type {
|
|
38
38
|
} from './store-types.js'
|
39
39
|
|
40
40
|
export const DEFAULT_PARAMS = {
|
41
|
-
leaderPushBatchSize:
|
41
|
+
leaderPushBatchSize: 100,
|
42
42
|
}
|
43
43
|
|
44
44
|
export class LiveStoreContextRunning extends Context.Tag('@livestore/livestore/effect/LiveStoreContextRunning')<
|
package/src/store/devtools.ts
CHANGED
@@ -52,10 +52,10 @@ export const connectDevtoolsToStore = ({
|
|
52
52
|
|
53
53
|
yield* Effect.addFinalizer(() =>
|
54
54
|
Effect.sync(() => {
|
55
|
-
reactivityGraphSubcriptions.
|
56
|
-
liveQueriesSubscriptions.
|
57
|
-
debugInfoHistorySubscriptions.
|
58
|
-
syncHeadClientSessionSubscriptions.
|
55
|
+
for (const unsub of reactivityGraphSubcriptions.values()) unsub()
|
56
|
+
for (const unsub of liveQueriesSubscriptions.values()) unsub()
|
57
|
+
for (const unsub of debugInfoHistorySubscriptions.values()) unsub()
|
58
|
+
for (const unsub of syncHeadClientSessionSubscriptions.values()) unsub()
|
59
59
|
}),
|
60
60
|
)
|
61
61
|
|
@@ -202,7 +202,7 @@ export const connectDevtoolsToStore = ({
|
|
202
202
|
}
|
203
203
|
case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
|
204
204
|
const { queryStr, bindValues, queriedTables } = decodedMessage
|
205
|
-
store.sqliteDbWrapper.
|
205
|
+
store.sqliteDbWrapper.cachedSelect(queryStr, bindValues, { queriedTables, skipCache: true })
|
206
206
|
sendToDevtools(
|
207
207
|
Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
208
208
|
)
|
package/src/store/store.ts
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
-
import type {
|
2
|
-
ClientSession,
|
3
|
-
ClientSessionSyncProcessor,
|
4
|
-
ParamsObject,
|
5
|
-
PreparedBindValues,
|
6
|
-
QueryBuilder,
|
7
|
-
UnexpectedError,
|
8
|
-
} from '@livestore/common'
|
9
1
|
import {
|
2
|
+
type Bindable,
|
3
|
+
type ClientSession,
|
4
|
+
type ClientSessionSyncProcessor,
|
10
5
|
Devtools,
|
11
6
|
getDurationMsFromSpan,
|
12
|
-
|
7
|
+
getExecStatementsFromMaterializer,
|
13
8
|
getResultSchema,
|
9
|
+
hashMaterializerResults,
|
14
10
|
IntentionalShutdownCause,
|
15
11
|
isQueryBuilder,
|
16
12
|
liveStoreVersion,
|
17
13
|
makeClientSessionSyncProcessor,
|
14
|
+
type PreparedBindValues,
|
18
15
|
prepareBindValues,
|
16
|
+
type QueryBuilder,
|
19
17
|
QueryBuilderAstSymbol,
|
20
18
|
replaceSessionIdSymbol,
|
19
|
+
UnexpectedError,
|
21
20
|
} from '@livestore/common'
|
22
21
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
23
22
|
import { getEventDef, LiveStoreEvent, SystemTables } from '@livestore/common/schema'
|
24
23
|
import { assertNever, isDevEnv, notYetImplemented } from '@livestore/utils'
|
25
24
|
import type { Scope } from '@livestore/utils/effect'
|
26
|
-
import { Cause, Effect, Fiber, Inspectable, OtelTracer, Runtime, Schema, Stream } from '@livestore/utils/effect'
|
25
|
+
import { Cause, Effect, Fiber, Inspectable, Option, OtelTracer, Runtime, Schema, Stream } from '@livestore/utils/effect'
|
27
26
|
import { nanoid } from '@livestore/utils/nanoid'
|
28
27
|
import * as otel from '@opentelemetry/api'
|
29
28
|
|
@@ -36,6 +35,7 @@ import type {
|
|
36
35
|
} from '../live-queries/base-class.js'
|
37
36
|
import { makeReactivityGraph } from '../live-queries/base-class.js'
|
38
37
|
import { makeExecBeforeFirstRun } from '../live-queries/client-document-get-query.js'
|
38
|
+
import { queryDb } from '../live-queries/db-query.js'
|
39
39
|
import type { Ref } from '../reactive.js'
|
40
40
|
import { SqliteDbWrapper } from '../SqliteDbWrapper.js'
|
41
41
|
import { ReferenceCountedSet } from '../utils/data-structures.js'
|
@@ -114,16 +114,33 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
114
114
|
schema,
|
115
115
|
clientSession,
|
116
116
|
runtime: effectContext.runtime,
|
117
|
-
materializeEvent: (eventDecoded, { otelContext, withChangeset }) => {
|
117
|
+
materializeEvent: (eventDecoded, { otelContext, withChangeset, materializerHashLeader }) => {
|
118
118
|
const { eventDef, materializer } = getEventDef(schema, eventDecoded.name)
|
119
119
|
|
120
|
-
const execArgsArr =
|
120
|
+
const execArgsArr = getExecStatementsFromMaterializer({
|
121
121
|
eventDef,
|
122
122
|
materializer,
|
123
|
-
|
123
|
+
dbState: this.sqliteDbWrapper,
|
124
124
|
event: { decoded: eventDecoded, encoded: undefined },
|
125
125
|
})
|
126
126
|
|
127
|
+
const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none()
|
128
|
+
|
129
|
+
if (
|
130
|
+
materializerHashLeader._tag === 'Some' &&
|
131
|
+
materializerHash._tag === 'Some' &&
|
132
|
+
materializerHashLeader.value !== materializerHash.value
|
133
|
+
) {
|
134
|
+
void this.shutdown(
|
135
|
+
Cause.fail(
|
136
|
+
UnexpectedError.make({
|
137
|
+
cause: `Materializer hash mismatch detected for event "${eventDecoded.name}".`,
|
138
|
+
note: `Please make sure your event materializer is a pure function without side effects.`,
|
139
|
+
}),
|
140
|
+
),
|
141
|
+
)
|
142
|
+
}
|
143
|
+
|
127
144
|
const writeTablesForEvent = new Set<string>()
|
128
145
|
|
129
146
|
const exec = () => {
|
@@ -132,12 +149,21 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
132
149
|
bindValues,
|
133
150
|
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
134
151
|
} of execArgsArr) {
|
135
|
-
|
152
|
+
try {
|
153
|
+
this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
|
154
|
+
} catch (cause) {
|
155
|
+
throw UnexpectedError.make({
|
156
|
+
cause,
|
157
|
+
note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
|
158
|
+
})
|
159
|
+
}
|
136
160
|
|
137
161
|
// durationMsTotal += durationMs
|
138
162
|
for (const table of writeTables) {
|
139
163
|
writeTablesForEvent.add(table)
|
140
164
|
}
|
165
|
+
|
166
|
+
this.sqliteDbWrapper.debug.head = eventDecoded.seqNum
|
141
167
|
}
|
142
168
|
}
|
143
169
|
|
@@ -145,13 +171,14 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
145
171
|
| { _tag: 'sessionChangeset'; data: Uint8Array; debug: any }
|
146
172
|
| { _tag: 'no-op' }
|
147
173
|
| { _tag: 'unset' } = { _tag: 'unset' }
|
174
|
+
|
148
175
|
if (withChangeset === true) {
|
149
176
|
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
150
177
|
} else {
|
151
178
|
exec()
|
152
179
|
}
|
153
180
|
|
154
|
-
return { writeTables: writeTablesForEvent, sessionChangeset }
|
181
|
+
return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash }
|
155
182
|
},
|
156
183
|
rollback: (changeset) => {
|
157
184
|
this.sqliteDbWrapper.rollback(changeset)
|
@@ -264,7 +291,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
264
291
|
* ```
|
265
292
|
*/
|
266
293
|
subscribe = <TResult>(
|
267
|
-
query: LiveQueryDef<TResult, 'def' | 'signal-def'> | LiveQuery<TResult>,
|
294
|
+
query: LiveQueryDef<TResult, 'def' | 'signal-def'> | LiveQuery<TResult> | QueryBuilder<TResult, any, any>,
|
268
295
|
options: {
|
269
296
|
/** Called when the query result has changed */
|
270
297
|
onUpdate: (value: TResult) => void
|
@@ -284,14 +311,15 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
284
311
|
): Unsubscribe =>
|
285
312
|
this.otel.tracer.startActiveSpan(
|
286
313
|
`LiveStore.subscribe`,
|
287
|
-
{ attributes: { label: options?.label, queryLabel: query.label } },
|
314
|
+
{ attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
|
288
315
|
options?.otelContext ?? this.otel.queriesSpanContext,
|
289
316
|
(span) => {
|
290
317
|
// console.debug('store sub', query$.id, query$.label)
|
291
318
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
292
319
|
|
293
|
-
const queryRcRef =
|
294
|
-
|
320
|
+
const queryRcRef = isQueryBuilder(query)
|
321
|
+
? queryDb(query).make(this.reactivityGraph.context!)
|
322
|
+
: query._tag === 'def' || query._tag === 'signal-def'
|
295
323
|
? query.make(this.reactivityGraph.context!)
|
296
324
|
: {
|
297
325
|
value: query as LiveQuery<TResult>,
|
@@ -385,13 +413,17 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
385
413
|
| LiveQuery<TResult>
|
386
414
|
| LiveQueryDef<TResult>
|
387
415
|
| SignalDef<TResult>
|
388
|
-
| { query: string; bindValues:
|
416
|
+
| { query: string; bindValues: Bindable; schema?: Schema.Schema<TResult> },
|
389
417
|
options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
|
390
418
|
): TResult => {
|
391
419
|
if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
|
392
|
-
|
420
|
+
const res = this.sqliteDbWrapper.cachedSelect(query.query, prepareBindValues(query.bindValues, query.query), {
|
393
421
|
otelContext: options?.otelContext,
|
394
422
|
}) as any
|
423
|
+
if (query.schema) {
|
424
|
+
return Schema.decodeSync(query.schema)(res)
|
425
|
+
}
|
426
|
+
return res
|
395
427
|
} else if (isQueryBuilder(query)) {
|
396
428
|
const ast = query[QueryBuilderAstSymbol]
|
397
429
|
if (ast._tag === 'RowQuery') {
|
@@ -405,7 +437,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
405
437
|
|
406
438
|
const sqlRes = query.asSql()
|
407
439
|
const schema = getResultSchema(query)
|
408
|
-
const rawRes = this.sqliteDbWrapper.
|
440
|
+
const rawRes = this.sqliteDbWrapper.cachedSelect(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
|
409
441
|
otelContext: options?.otelContext,
|
410
442
|
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
411
443
|
})
|
@@ -558,12 +590,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
558
590
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
559
591
|
|
560
592
|
try {
|
593
|
+
// Materialize events to state
|
561
594
|
const { writeTables } = (() => {
|
562
595
|
try {
|
563
596
|
const materializeEvents = () => this.syncProcessor.push(events, { otelContext })
|
564
597
|
|
565
598
|
if (events.length > 1) {
|
566
|
-
// TODO: what to do about leader transaction here?
|
567
599
|
return this.sqliteDbWrapper.txn(materializeEvents)
|
568
600
|
} else {
|
569
601
|
return materializeEvents()
|
@@ -19,7 +19,9 @@ Error
|
|
19
19
|
|
20
20
|
const stackInfo = extractStackInfoFromStackTrace(stackTrace)
|
21
21
|
// Replacing file paths for snapshot testing as they are not stable
|
22
|
-
stackInfo.frames.forEach((_) =>
|
22
|
+
stackInfo.frames.forEach((_) => {
|
23
|
+
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
24
|
+
})
|
23
25
|
expect(stackInfo).toMatchInlineSnapshot(`
|
24
26
|
{
|
25
27
|
"frames": [
|
@@ -61,7 +63,9 @@ Error
|
|
61
63
|
|
62
64
|
const stackInfo = extractStackInfoFromStackTrace(stackTrace)
|
63
65
|
// Replacing file paths for snapshot testing as they are not stable
|
64
|
-
stackInfo.frames.forEach((_) =>
|
66
|
+
stackInfo.frames.forEach((_) => {
|
67
|
+
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
68
|
+
})
|
65
69
|
expect(stackInfo).toMatchInlineSnapshot(`
|
66
70
|
{
|
67
71
|
"frames": [
|
package/src/utils/stack-info.ts
CHANGED
@@ -32,7 +32,11 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
|
|
32
32
|
const frames: StackFrame[] = []
|
33
33
|
let hasReachedStart = false
|
34
34
|
|
35
|
-
while (
|
35
|
+
while (true) {
|
36
|
+
match = namePattern.exec(stackTrace)
|
37
|
+
if (match === null) {
|
38
|
+
break
|
39
|
+
}
|
36
40
|
const [, name, filePath] = match as any as [string, string, string]
|
37
41
|
// console.debug(name, filePath)
|
38
42
|
|
package/src/utils/tests/otel.ts
CHANGED
@@ -24,7 +24,7 @@ export const getSimplifiedRootSpan = (
|
|
24
24
|
const createStoreSpanData = spans.find((_) => _.name === 'createStore')
|
25
25
|
if (createStoreSpanData === undefined) {
|
26
26
|
throw new Error(
|
27
|
-
|
27
|
+
`Could not find the root span named 'createStore'. Available spans: ${spans.map((s) => s.name).join(', ')}`,
|
28
28
|
)
|
29
29
|
}
|
30
30
|
const rootSpan = spansMap.get(createStoreSpanData.spanContext().spanId)!
|