@livestore/livestore 0.4.0-dev.1 → 0.4.0-dev.11

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.
Files changed (61) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/effect/LiveStore.d.ts.map +1 -1
  3. package/dist/effect/LiveStore.js +2 -4
  4. package/dist/effect/LiveStore.js.map +1 -1
  5. package/dist/live-queries/base-class.d.ts.map +1 -1
  6. package/dist/live-queries/base-class.js.map +1 -1
  7. package/dist/live-queries/db-query.d.ts.map +1 -1
  8. package/dist/live-queries/db-query.js +7 -4
  9. package/dist/live-queries/db-query.js.map +1 -1
  10. package/dist/live-queries/db-query.test.js +53 -24
  11. package/dist/live-queries/db-query.test.js.map +1 -1
  12. package/dist/mod.d.ts +2 -2
  13. package/dist/mod.d.ts.map +1 -1
  14. package/dist/mod.js +1 -1
  15. package/dist/mod.js.map +1 -1
  16. package/dist/reactive.d.ts +10 -10
  17. package/dist/reactive.d.ts.map +1 -1
  18. package/dist/reactive.js +36 -27
  19. package/dist/reactive.js.map +1 -1
  20. package/dist/reactive.test.js +115 -0
  21. package/dist/reactive.test.js.map +1 -1
  22. package/dist/store/create-store.d.ts.map +1 -1
  23. package/dist/store/create-store.js +3 -3
  24. package/dist/store/create-store.js.map +1 -1
  25. package/dist/store/store-types.d.ts +13 -2
  26. package/dist/store/store-types.d.ts.map +1 -1
  27. package/dist/store/store-types.js.map +1 -1
  28. package/dist/store/store.d.ts +45 -29
  29. package/dist/store/store.d.ts.map +1 -1
  30. package/dist/store/store.js +165 -100
  31. package/dist/store/store.js.map +1 -1
  32. package/dist/utils/dev.d.ts +3 -0
  33. package/dist/utils/dev.d.ts.map +1 -1
  34. package/dist/utils/dev.js.map +1 -1
  35. package/dist/utils/tests/fixture.d.ts.map +1 -1
  36. package/dist/utils/tests/fixture.js +2 -1
  37. package/dist/utils/tests/fixture.js.map +1 -1
  38. package/dist/utils/tests/otel.d.ts +15 -14
  39. package/dist/utils/tests/otel.d.ts.map +1 -1
  40. package/dist/utils/tests/otel.js +20 -15
  41. package/dist/utils/tests/otel.js.map +1 -1
  42. package/package.json +7 -7
  43. package/src/ambient.d.ts +3 -3
  44. package/src/effect/LiveStore.ts +2 -4
  45. package/src/live-queries/__snapshots__/db-query.test.ts.snap +354 -130
  46. package/src/live-queries/base-class.ts +6 -3
  47. package/src/live-queries/db-query.test.ts +70 -24
  48. package/src/live-queries/db-query.ts +7 -4
  49. package/src/mod.ts +10 -1
  50. package/src/reactive.test.ts +150 -1
  51. package/src/reactive.ts +47 -39
  52. package/src/store/create-store.ts +12 -4
  53. package/src/store/store-types.ts +23 -2
  54. package/src/store/store.ts +262 -193
  55. package/src/utils/dev.ts +5 -0
  56. package/src/utils/tests/fixture.ts +2 -1
  57. package/src/utils/tests/otel.ts +31 -20
  58. package/dist/store/store-shutdown.test.d.ts +0 -2
  59. package/dist/store/store-shutdown.test.d.ts.map +0 -1
  60. package/dist/store/store-shutdown.test.js +0 -103
  61. package/dist/store/store-shutdown.test.js.map +0 -1
@@ -1,15 +1,18 @@
1
- import { type Bindable, type ClientSession, type ClientSessionSyncProcessor, type QueryBuilder, UnexpectedError } from '@livestore/common';
1
+ import { type Bindable, type ClientSession, type ClientSessionSyncProcessor, MaterializeError, UnexpectedError } from '@livestore/common';
2
2
  import type { LiveStoreSchema } from '@livestore/common/schema';
3
3
  import { LiveStoreEvent } from '@livestore/common/schema';
4
4
  import type { Scope } from '@livestore/utils/effect';
5
5
  import { Cause, Effect, Inspectable, Schema, Stream } from '@livestore/utils/effect';
6
6
  import * as otel from '@opentelemetry/api';
7
- import type { LiveQuery, LiveQueryDef, ReactivityGraph, ReactivityGraphContext, SignalDef } from '../live-queries/base-class.ts';
7
+ import type { LiveQuery, ReactivityGraph, ReactivityGraphContext, SignalDef } from '../live-queries/base-class.ts';
8
8
  import type { Ref } from '../reactive.ts';
9
9
  import { SqliteDbWrapper } from '../SqliteDbWrapper.ts';
10
10
  import { ReferenceCountedSet } from '../utils/data-structures.ts';
11
- import type { StackInfo } from '../utils/stack-info.ts';
12
- import type { RefreshReason, StoreCommitOptions, StoreEventsOptions, StoreOptions, StoreOtel, Unsubscribe } from './store-types.ts';
11
+ import type { Queryable, RefreshReason, StoreCommitOptions, StoreEventsOptions, StoreOptions, StoreOtel, SubscribeOptions, Unsubscribe } from './store-types.ts';
12
+ type SubscribeFn = {
13
+ <TResult>(query: Queryable<TResult>, onUpdate: (value: TResult) => void, options?: SubscribeOptions<TResult>): Unsubscribe;
14
+ <TResult>(query: Queryable<TResult>, options?: SubscribeOptions<TResult>): AsyncIterable<TResult>;
15
+ };
13
16
  export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> extends Inspectable.Class {
14
17
  readonly storeId: string;
15
18
  reactivityGraph: ReactivityGraph;
@@ -18,6 +21,24 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
18
21
  schema: LiveStoreSchema;
19
22
  context: TContext;
20
23
  otel: StoreOtel;
24
+ /**
25
+ * Reactive connectivity updates emitted by the backing sync backend.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { Effect, Stream } from 'effect'
30
+ *
31
+ * const status = await store.networkStatus.pipe(Effect.runPromise)
32
+ *
33
+ * await store.networkStatus.changes.pipe(
34
+ * Stream.tap((next) => console.log('network status update', next)),
35
+ * Stream.runDrain,
36
+ * Effect.scoped,
37
+ * Effect.runPromise,
38
+ * )
39
+ * ```
40
+ */
41
+ readonly networkStatus: ClientSession['leaderThread']['networkStatus'];
21
42
  /**
22
43
  * Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
23
44
  * This only works in combination with `equal: () => false` which will always trigger a refresh.
@@ -38,34 +59,27 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
38
59
  get clientId(): string;
39
60
  private checkShutdown;
40
61
  /**
41
- * Subscribe to the results of a query
42
- * Returns a function to cancel the subscription.
62
+ * Subscribe to the results of a query.
63
+ *
64
+ * - When providing an `onUpdate` callback it returns an {@link Unsubscribe} function.
65
+ * - Without a callback it returns an {@link AsyncIterable} that yields query results.
43
66
  *
44
67
  * @example
45
68
  * ```ts
46
- * const unsubscribe = store.subscribe(query$, { onUpdate: (result) => console.log(result) })
69
+ * const unsubscribe = store.subscribe(query$, (result) => console.log(result))
70
+ * ```
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * for await (const result of store.subscribe(query$)) {
75
+ * console.log(result)
76
+ * }
47
77
  * ```
48
78
  */
49
- subscribe: <TResult>(query: LiveQueryDef<TResult, "def" | "signal-def"> | LiveQuery<TResult> | QueryBuilder<TResult, any, any>, options: {
50
- /** Called when the query result has changed */
51
- onUpdate: (value: TResult) => void;
52
- onSubscribe?: (query$: LiveQuery<TResult>) => void;
53
- /** Gets called after the query subscription has been removed */
54
- onUnsubsubscribe?: () => void;
55
- label?: string;
56
- /**
57
- * Skips the initial `onUpdate` callback
58
- * @default false
59
- */
60
- skipInitialRun?: boolean;
61
- otelContext?: otel.Context;
62
- /** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
63
- stackInfo?: StackInfo;
64
- }) => Unsubscribe;
65
- subscribeStream: <TResult>(query$: LiveQueryDef<TResult>, options?: {
66
- label?: string;
67
- skipInitialRun?: boolean;
68
- } | undefined) => Stream.Stream<TResult>;
79
+ subscribe: SubscribeFn;
80
+ private subscribeWithCallback;
81
+ private subscribeAsAsyncIterable;
82
+ subscribeStream: <TResult>(query: Queryable<TResult>, options?: SubscribeOptions<TResult>) => Stream.Stream<TResult>;
69
83
  /**
70
84
  * Synchronously queries the database without creating a LiveQuery.
71
85
  * This is useful for queries that don't need to be reactive.
@@ -80,7 +94,7 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
80
94
  * const completedTodos = store.query({ query: 'SELECT * FROM todo WHERE complete = 1', bindValues: {} })
81
95
  * ```
82
96
  */
83
- query: <TResult>(query: QueryBuilder<TResult, any, any> | LiveQuery<TResult> | LiveQueryDef<TResult> | SignalDef<TResult> | {
97
+ query: <TResult>(query: Queryable<TResult> | {
84
98
  query: string;
85
99
  bindValues: Bindable;
86
100
  schema?: Schema.Schema<TResult>;
@@ -199,12 +213,14 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
199
213
  *
200
214
  * This is called automatically when the store was created using the React or Effect API.
201
215
  */
202
- shutdown: (cause?: Cause.Cause<UnexpectedError>) => Effect.Effect<void>;
216
+ shutdown: (cause?: Cause.Cause<UnexpectedError | MaterializeError>) => Effect.Effect<void>;
203
217
  toJSON: () => {
204
218
  _tag: string;
205
219
  reactivityGraph: import("../reactive.ts").ReactiveGraphSnapshot;
206
220
  };
207
221
  private runEffectFork;
222
+ private runEffectPromise;
208
223
  private getCommitArgs;
209
224
  }
225
+ export {};
210
226
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { Devtools, getDurationMsFromSpan, getExecStatementsFromMaterializer, getResultSchema, hashMaterializerResults, IntentionalShutdownCause, isQueryBuilder, liveStoreVersion, makeClientSessionSyncProcessor, prepareBindValues, QueryBuilderAstSymbol, replaceSessionIdSymbol, UnexpectedError, } from '@livestore/common';
2
- import { getEventDef, LiveStoreEvent, SystemTables } from '@livestore/common/schema';
3
- import { assertNever, isDevEnv, notYetImplemented } from '@livestore/utils';
1
+ import { Devtools, getExecStatementsFromMaterializer, getResultSchema, hashMaterializerResults, IntentionalShutdownCause, isQueryBuilder, liveStoreVersion, MaterializeError, MaterializerHashMismatchError, makeClientSessionSyncProcessor, prepareBindValues, QueryBuilderAstSymbol, replaceSessionIdSymbol, UnexpectedError, } from '@livestore/common';
2
+ import { LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema';
3
+ import { assertNever, isDevEnv, notYetImplemented, omitUndefineds, shouldNeverHappen } from '@livestore/utils';
4
4
  import { Cause, Effect, Exit, Fiber, Inspectable, Option, OtelTracer, Runtime, Schema, Stream, } from '@livestore/utils/effect';
5
5
  import { nanoid } from '@livestore/utils/nanoid';
6
6
  import * as otel from '@opentelemetry/api';
@@ -21,6 +21,24 @@ export class Store extends Inspectable.Class {
21
21
  schema;
22
22
  context;
23
23
  otel;
24
+ /**
25
+ * Reactive connectivity updates emitted by the backing sync backend.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { Effect, Stream } from 'effect'
30
+ *
31
+ * const status = await store.networkStatus.pipe(Effect.runPromise)
32
+ *
33
+ * await store.networkStatus.changes.pipe(
34
+ * Stream.tap((next) => console.log('network status update', next)),
35
+ * Stream.runDrain,
36
+ * Effect.scoped,
37
+ * Effect.runPromise,
38
+ * )
39
+ * ```
40
+ */
41
+ networkStatus;
24
42
  /**
25
43
  * Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
26
44
  * This only works in combination with `equal: () => false` which will always trigger a refresh.
@@ -43,6 +61,7 @@ export class Store extends Inspectable.Class {
43
61
  this.clientSession = clientSession;
44
62
  this.schema = schema;
45
63
  this.context = context;
64
+ this.networkStatus = clientSession.leaderThread.networkStatus;
46
65
  this.effectContext = effectContext;
47
66
  const reactivityGraph = makeReactivityGraph();
48
67
  const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext);
@@ -50,23 +69,41 @@ export class Store extends Inspectable.Class {
50
69
  schema,
51
70
  clientSession,
52
71
  runtime: effectContext.runtime,
53
- materializeEvent: (eventDecoded, { otelContext, withChangeset, materializerHashLeader }) => {
54
- const { eventDef, materializer } = getEventDef(schema, eventDecoded.name);
72
+ materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')((eventEncoded, { withChangeset, materializerHashLeader }) =>
73
+ // We need to use `Effect.gen` (even though we're using `Effect.fn`) so that we can pass `this` to the function
74
+ Effect.gen(this, function* () {
75
+ const resolution = yield* resolveEventDef(schema, {
76
+ operation: '@livestore/livestore:store:materializeEvent',
77
+ event: eventEncoded,
78
+ });
79
+ if (resolution._tag === 'unknown') {
80
+ // Runtime schema doesn't know this event yet; skip materialization but
81
+ // keep the log entry so upgraded clients can replay it later.
82
+ return {
83
+ writeTables: new Set(),
84
+ sessionChangeset: { _tag: 'no-op' },
85
+ materializerHash: Option.none(),
86
+ };
87
+ }
88
+ const { eventDef, materializer } = resolution;
55
89
  const execArgsArr = getExecStatementsFromMaterializer({
56
90
  eventDef,
57
91
  materializer,
58
92
  dbState: this.sqliteDbWrapper,
59
- event: { decoded: eventDecoded, encoded: undefined },
93
+ event: { decoded: undefined, encoded: eventEncoded },
60
94
  });
61
95
  const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none();
96
+ // Hash mismatch detection only occurs during the pull path (when receiving events from the leader).
97
+ // During push path (local commits), materializerHashLeader is always Option.none(), so this condition
98
+ // will never be met. The check happens when the same event comes back from the leader during sync,
99
+ // allowing us to compare the leader's computed hash with our local re-materialization hash.
62
100
  if (materializerHashLeader._tag === 'Some' &&
63
101
  materializerHash._tag === 'Some' &&
64
102
  materializerHashLeader.value !== materializerHash.value) {
65
- void this.shutdown(Cause.fail(UnexpectedError.make({
66
- cause: `Materializer hash mismatch detected for event "${eventDecoded.name}".`,
67
- note: `Please make sure your event materializer is a pure function without side effects.`,
68
- })));
103
+ return yield* MaterializerHashMismatchError.make({ eventName: eventEncoded.name });
69
104
  }
105
+ const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie);
106
+ const otelContext = otel.trace.setSpan(otel.context.active(), span);
70
107
  const writeTablesForEvent = new Set();
71
108
  const exec = () => {
72
109
  for (const { statementSql, bindValues, writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql), } of execArgsArr) {
@@ -74,16 +111,17 @@ export class Store extends Inspectable.Class {
74
111
  this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables });
75
112
  }
76
113
  catch (cause) {
114
+ // TOOD refactor with `SqliteError`
77
115
  throw UnexpectedError.make({
78
116
  cause,
79
- note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
117
+ note: `Error executing materializer for event "${eventEncoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
80
118
  });
81
119
  }
82
120
  // durationMsTotal += durationMs
83
121
  for (const table of writeTables) {
84
122
  writeTablesForEvent.add(table);
85
123
  }
86
- this.sqliteDbWrapper.debug.head = eventDecoded.seqNum;
124
+ this.sqliteDbWrapper.debug.head = eventEncoded.seqNum;
87
125
  }
88
126
  };
89
127
  let sessionChangeset = { _tag: 'unset' };
@@ -94,7 +132,7 @@ export class Store extends Inspectable.Class {
94
132
  exec();
95
133
  }
96
134
  return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash };
97
- },
135
+ }).pipe(Effect.mapError((cause) => MaterializeError.make({ cause })))),
98
136
  rollback: (changeset) => {
99
137
  this.sqliteDbWrapper.rollback(changeset);
100
138
  },
@@ -109,8 +147,12 @@ export class Store extends Inspectable.Class {
109
147
  },
110
148
  span: syncSpan,
111
149
  params: {
112
- leaderPushBatchSize: params.leaderPushBatchSize,
113
- simulation: params.simulation?.clientSessionSyncProcessor,
150
+ ...omitUndefineds({
151
+ leaderPushBatchSize: params.leaderPushBatchSize,
152
+ }),
153
+ ...(params.simulation?.clientSessionSyncProcessor !== undefined
154
+ ? { simulation: params.simulation.clientSessionSyncProcessor }
155
+ : {}),
114
156
  },
115
157
  confirmUnsavedChanges,
116
158
  });
@@ -188,18 +230,32 @@ export class Store extends Inspectable.Class {
188
230
  }
189
231
  };
190
232
  /**
191
- * Subscribe to the results of a query
192
- * Returns a function to cancel the subscription.
233
+ * Subscribe to the results of a query.
234
+ *
235
+ * - When providing an `onUpdate` callback it returns an {@link Unsubscribe} function.
236
+ * - Without a callback it returns an {@link AsyncIterable} that yields query results.
193
237
  *
194
238
  * @example
195
239
  * ```ts
196
- * const unsubscribe = store.subscribe(query$, { onUpdate: (result) => console.log(result) })
240
+ * const unsubscribe = store.subscribe(query$, (result) => console.log(result))
241
+ * ```
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * for await (const result of store.subscribe(query$)) {
246
+ * console.log(result)
247
+ * }
197
248
  * ```
198
249
  */
199
- subscribe = (query, options) => {
250
+ subscribe = ((query, onUpdateOrOptions, maybeOptions) => {
251
+ if (typeof onUpdateOrOptions === 'function') {
252
+ return this.subscribeWithCallback(query, onUpdateOrOptions, maybeOptions);
253
+ }
254
+ return this.subscribeAsAsyncIterable(query, onUpdateOrOptions);
255
+ });
256
+ subscribeWithCallback = (query, onUpdate, options) => {
200
257
  this.checkShutdown('subscribe');
201
258
  return this.otel.tracer.startActiveSpan(`LiveStore.subscribe`, { attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } }, options?.otelContext ?? this.otel.queriesSpanContext, (span) => {
202
- // console.debug('store sub', query$.id, query$.label)
203
259
  const otelContext = otel.trace.setSpan(otel.context.active(), span);
204
260
  const queryRcRef = isQueryBuilder(query)
205
261
  ? queryDb(query).make(this.reactivityGraph.context)
@@ -211,18 +267,19 @@ export class Store extends Inspectable.Class {
211
267
  };
212
268
  const query$ = queryRcRef.value;
213
269
  const label = `subscribe:${options?.label}`;
214
- const effect = this.reactivityGraph.makeEffect((get, _otelContext, debugRefreshReason) => options.onUpdate(get(query$.results$, otelContext, debugRefreshReason)), { label });
270
+ const effect = this.reactivityGraph.makeEffect((get, _otelContext, debugRefreshReason) => onUpdate(get(query$.results$, otelContext, debugRefreshReason)), { label });
215
271
  if (options?.stackInfo) {
216
272
  query$.activeSubscriptions.add(options.stackInfo);
217
273
  }
218
274
  options?.onSubscribe?.(query$);
219
275
  this.activeQueries.add(query$);
220
- // Running effect right away to get initial value (unless `skipInitialRun` is set)
221
276
  if (options?.skipInitialRun !== true && !query$.isDestroyed) {
222
- effect.doEffect(otelContext, { _tag: 'subscribe.initial', label: `subscribe-initial-run:${options?.label}` });
277
+ effect.doEffect(otelContext, {
278
+ _tag: 'subscribe.initial',
279
+ label: `subscribe-initial-run:${options?.label}`,
280
+ });
223
281
  }
224
282
  const unsubscribe = () => {
225
- // console.debug('store unsub', query$.id, query$.label)
226
283
  try {
227
284
  this.reactivityGraph.destroyNode(effect);
228
285
  this.activeQueries.remove(query$);
@@ -239,13 +296,16 @@ export class Store extends Inspectable.Class {
239
296
  return unsubscribe;
240
297
  });
241
298
  };
242
- subscribeStream = (query$, options) => Stream.asyncPush((emit) => Effect.gen(this, function* () {
299
+ subscribeAsAsyncIterable = (query, options) => {
300
+ this.checkShutdown('subscribe');
301
+ return Stream.toAsyncIterable(this.subscribeStream(query, options));
302
+ };
303
+ subscribeStream = (query, options) => Stream.asyncPush((emit) => Effect.gen(this, function* () {
243
304
  const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)));
244
305
  const otelContext = otelSpan ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active();
245
- yield* Effect.acquireRelease(Effect.sync(() => this.subscribe(query$, {
246
- onUpdate: (result) => emit.single(result),
306
+ yield* Effect.acquireRelease(Effect.sync(() => this.subscribe(query, (result) => emit.single(result), {
307
+ ...(options ?? {}),
247
308
  otelContext,
248
- label: options?.label,
249
309
  })), (unsub) => Effect.sync(() => unsub()));
250
310
  }));
251
311
  /**
@@ -266,7 +326,7 @@ export class Store extends Inspectable.Class {
266
326
  this.checkShutdown('query');
267
327
  if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
268
328
  const res = this.sqliteDbWrapper.cachedSelect(query.query, prepareBindValues(query.bindValues, query.query), {
269
- otelContext: options?.otelContext,
329
+ ...omitUndefineds({ otelContext: options?.otelContext }),
270
330
  });
271
331
  if (query.schema) {
272
332
  return Schema.decodeSync(query.schema)(res);
@@ -290,10 +350,16 @@ export class Store extends Inspectable.Class {
290
350
  replaceSessionIdSymbol(sqlRes.bindValues, this.clientSession.sessionId);
291
351
  }
292
352
  const rawRes = this.sqliteDbWrapper.cachedSelect(sqlRes.query, sqlRes.bindValues, {
293
- otelContext: options?.otelContext,
353
+ ...omitUndefineds({ otelContext: options?.otelContext }),
294
354
  queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
295
355
  });
296
- return Schema.decodeSync(schema)(rawRes);
356
+ const decodeResult = Schema.decodeEither(schema)(rawRes);
357
+ if (decodeResult._tag === 'Right') {
358
+ return decodeResult.right;
359
+ }
360
+ else {
361
+ return shouldNeverHappen(`Failed to decode query result with for schema:`, schema.toString(), 'raw result:', rawRes, 'decode error:', decodeResult.left);
362
+ }
297
363
  }
298
364
  else if (query._tag === 'def') {
299
365
  const query$ = query.make(this.reactivityGraph.context);
@@ -306,7 +372,9 @@ export class Store extends Inspectable.Class {
306
372
  return signal$.value.get();
307
373
  }
308
374
  else {
309
- return query.run({ otelContext: options?.otelContext, debugRefreshReason: options?.debugRefreshReason });
375
+ return query.run({
376
+ ...omitUndefineds({ otelContext: options?.otelContext, debugRefreshReason: options?.debugRefreshReason }),
377
+ });
310
378
  }
311
379
  };
312
380
  /**
@@ -393,73 +461,65 @@ export class Store extends Inspectable.Class {
393
461
  commit = (firstEventOrTxnFnOrOptions, ...restEvents) => {
394
462
  this.checkShutdown('commit');
395
463
  const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents);
396
- for (const event of events) {
397
- replaceSessionIdSymbol(event.args, this.clientSession.sessionId);
398
- }
399
- if (events.length === 0)
400
- return;
401
- const skipRefresh = options?.skipRefresh ?? false;
402
- const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext);
403
- commitsSpan.addEvent('commit');
404
- // console.group('LiveStore.commit', { skipRefresh })
405
- // events.forEach((_) => console.debug(_.name, _.args))
406
- // console.groupEnd()
407
- let durationMs;
408
- return this.otel.tracer.startActiveSpan('LiveStore:commit', {
464
+ Effect.gen(this, function* () {
465
+ const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext);
466
+ commitsSpan?.addEvent('commit');
467
+ const currentSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie);
468
+ commitsSpan?.addLink({ context: currentSpan.spanContext() });
469
+ for (const event of events) {
470
+ replaceSessionIdSymbol(event.args, this.clientSession.sessionId);
471
+ }
472
+ if (events.length === 0)
473
+ return;
474
+ const localRuntime = yield* Effect.runtime();
475
+ const materializeEventsTx = Effect.try({
476
+ try: () => {
477
+ const runMaterializeEvents = () => {
478
+ return this.syncProcessor.push(events).pipe(Runtime.runSync(localRuntime));
479
+ };
480
+ if (events.length > 1) {
481
+ return this.sqliteDbWrapper.txn(runMaterializeEvents);
482
+ }
483
+ else {
484
+ return runMaterializeEvents();
485
+ }
486
+ },
487
+ catch: (cause) => UnexpectedError.make({ cause }),
488
+ });
489
+ // Materialize events to state
490
+ const { writeTables } = yield* materializeEventsTx;
491
+ const tablesToUpdate = [];
492
+ for (const tableName of writeTables) {
493
+ const tableRef = this.tableRefs[tableName];
494
+ assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
495
+ tablesToUpdate.push([tableRef, null]);
496
+ }
497
+ const debugRefreshReason = {
498
+ _tag: 'commit',
499
+ events,
500
+ writeTables: Array.from(writeTables),
501
+ };
502
+ const skipRefresh = options?.skipRefresh ?? false;
503
+ // Update all table refs together in a batch, to only trigger one reactive update
504
+ this.reactivityGraph.setRefs(tablesToUpdate, {
505
+ debugRefreshReason,
506
+ skipRefresh,
507
+ otelContext: otel.trace.setSpan(otel.context.active(), currentSpan),
508
+ });
509
+ }).pipe(Effect.withSpan('LiveStore:commit', {
510
+ root: true,
409
511
  attributes: {
410
512
  'livestore.eventsCount': events.length,
411
513
  'livestore.eventTags': events.map((_) => _.name),
412
- 'livestore.commitLabel': options?.label,
514
+ ...(options?.label && { 'livestore.commitLabel': options.label }),
413
515
  },
414
- links: options?.spanLinks,
415
- }, options?.otelContext ?? this.otel.commitsSpanContext, (span) => {
416
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
417
- try {
418
- // Materialize events to state
419
- const { writeTables } = (() => {
420
- try {
421
- const materializeEvents = () => this.syncProcessor.push(events, { otelContext });
422
- if (events.length > 1) {
423
- return this.sqliteDbWrapper.txn(materializeEvents);
424
- }
425
- else {
426
- return materializeEvents();
427
- }
428
- }
429
- catch (e) {
430
- console.error(e);
431
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
432
- throw e;
433
- }
434
- finally {
435
- span.end();
436
- }
437
- })();
438
- const tablesToUpdate = [];
439
- for (const tableName of writeTables) {
440
- const tableRef = this.tableRefs[tableName];
441
- assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
442
- tablesToUpdate.push([tableRef, null]);
443
- }
444
- const debugRefreshReason = {
445
- _tag: 'commit',
446
- events,
447
- writeTables: Array.from(writeTables),
448
- };
449
- // Update all table refs together in a batch, to only trigger one reactive update
450
- this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh });
451
- }
452
- catch (e) {
453
- console.error(e);
454
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
455
- throw e;
456
- }
457
- finally {
458
- span.end();
459
- durationMs = getDurationMsFromSpan(span);
460
- }
461
- return { durationMs };
462
- });
516
+ links: [
517
+ // Span link to LiveStore:commits
518
+ OtelTracer.makeSpanLink({ context: otel.trace.getSpanContext(this.otel.commitsSpanContext) }),
519
+ // User-provided span links
520
+ ...(options?.spanLinks?.map(OtelTracer.makeSpanLink) ?? []),
521
+ ],
522
+ }), Effect.tapErrorCause(Effect.logError), Effect.catchAllCause((cause) => Effect.fork(this.shutdown(cause))), Runtime.runSync(this.effectContext.runtime));
463
523
  };
464
524
  // #endregion commit
465
525
  /**
@@ -517,7 +577,6 @@ export class Store extends Inspectable.Class {
517
577
  * This is called automatically when the store was created using the React or Effect API.
518
578
  */
519
579
  shutdown = (cause) => {
520
- this.checkShutdown('shutdown');
521
580
  this.isShutdown = true;
522
581
  return this.clientSession.shutdown(cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })));
523
582
  };
@@ -556,12 +615,17 @@ export class Store extends Inspectable.Class {
556
615
  }))
557
616
  .pipe(this.runEffectFork);
558
617
  },
559
- syncStates: () => {
618
+ syncStates: () => Effect.gen(this, function* () {
619
+ const session = yield* this.syncProcessor.syncState;
620
+ const leader = yield* this.clientSession.leaderThread.syncState;
621
+ return { session, leader };
622
+ }).pipe(this.runEffectPromise),
623
+ printSyncStates: () => {
560
624
  Effect.gen(this, function* () {
561
625
  const session = yield* this.syncProcessor.syncState;
562
- console.log('Session sync state:', session.toJSON());
563
- const leader = yield* this.clientSession.leaderThread.getSyncState;
564
- console.log('Leader sync state:', leader.toJSON());
626
+ yield* Effect.log(`Session sync state: ${session.localHead} (upstream: ${session.upstreamHead})`, session.toJSON());
627
+ const leader = yield* this.clientSession.leaderThread.syncState;
628
+ yield* Effect.log(`Leader sync state: ${leader.localHead} (upstream: ${leader.upstreamHead})`, leader.toJSON());
565
629
  }).pipe(this.runEffectFork);
566
630
  },
567
631
  version: liveStoreVersion,
@@ -575,6 +639,7 @@ export class Store extends Inspectable.Class {
575
639
  reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
576
640
  });
577
641
  runEffectFork = (effect) => effect.pipe(Effect.forkIn(this.effectContext.lifetimeScope), Effect.tapCauseLogPretty, Runtime.runFork(this.effectContext.runtime));
642
+ runEffectPromise = (effect) => effect.pipe(Effect.tapCauseLogPretty, Runtime.runPromise(this.effectContext.runtime));
578
643
  getCommitArgs = (firstEventOrTxnFnOrOptions, restEvents) => {
579
644
  let events;
580
645
  let options;