@livestore/livestore 0.0.0-snapshot-6788a4cf00171a177a3a1e59cefc10f5bad71049 → 0.0.0-snapshot-783e1b05c962636b7bb28308b34f6cd9baf596e1

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.
@@ -1,92 +1,5 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`otel > QueryBuilder subscription - async iterator 1`] = `
4
- {
5
- "_name": "createStore",
6
- "attributes": {
7
- "debugInstanceId": "test",
8
- "storeId": "default",
9
- },
10
- "children": [
11
- {
12
- "_name": "livestore.in-memory-db:execute",
13
- "attributes": {
14
- "sql.query": "
15
- PRAGMA page_size=32768;
16
- PRAGMA cache_size=10000;
17
- PRAGMA synchronous='OFF';
18
- PRAGMA temp_store='MEMORY';
19
- PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
20
- ",
21
- },
22
- },
23
- {
24
- "_name": "@livestore/common:LeaderSyncProcessor:push",
25
- "attributes": {
26
- "batch": "undefined",
27
- "batchSize": 1,
28
- },
29
- },
30
- {
31
- "_name": "client-session-sync-processor:pull",
32
- "attributes": {
33
- "code.stacktrace": "<STACKTRACE>",
34
- "span.label": "⚠︎ Interrupted",
35
- "status.interrupted": true,
36
- },
37
- },
38
- {
39
- "_name": "LiveStore:sync",
40
- },
41
- {
42
- "_name": "LiveStore:commits",
43
- },
44
- {
45
- "_name": "LiveStore:queries",
46
- },
47
- ],
48
- }
49
- `;
50
-
51
- exports[`otel > QueryBuilder subscription - async iterator 2`] = `
52
- [
53
- {
54
- "_name": "LiveStore:commit",
55
- "attributes": {
56
- "livestore.eventTags": "[
57
- "todo.created"
58
- ]",
59
- "livestore.eventsCount": 1,
60
- },
61
- "children": [
62
- {
63
- "_name": "client-session-sync-processor:push",
64
- "attributes": {
65
- "batchSize": 1,
66
- "eventCounts": "{
67
- "todo.created": 1
68
- }",
69
- "mergeResultTag": "advance",
70
- },
71
- "children": [
72
- {
73
- "_name": "client-session-sync-processor:materialize-event",
74
- "children": [
75
- {
76
- "_name": "livestore.in-memory-db:execute",
77
- "attributes": {
78
- "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)",
79
- },
80
- },
81
- ],
82
- },
83
- ],
84
- },
85
- ],
86
- },
87
- ]
88
- `;
89
-
90
3
  exports[`otel > QueryBuilder subscription - basic functionality 1`] = `
91
4
  {
92
5
  "_name": "createStore",
@@ -289,53 +289,6 @@ Vitest.describe('otel', () => {
289
289
  ),
290
290
  )
291
291
 
292
- Vitest.scopedLive('QueryBuilder subscription - async iterator', () =>
293
- Effect.gen(function* () {
294
- const { store, exporter, span, provider } = yield* makeQuery
295
-
296
- const defaultTodo = { id: '', text: '', completed: false }
297
-
298
- const queryBuilder = tables.todos
299
- .where({ completed: false })
300
- .first({ behaviour: 'fallback', fallback: () => defaultTodo })
301
-
302
- yield* Effect.promise(async () => {
303
- const iterator = store.subscribe(queryBuilder)
304
-
305
- const initial = await iterator.next()
306
- expect(initial.done).toBe(false)
307
- expect(initial.value).toMatchObject(defaultTodo)
308
-
309
- store.commit(events.todoCreated({ id: 't-async', text: 'write tests', completed: false }))
310
-
311
- const update = await iterator.next()
312
- expect(update.done).toBe(false)
313
- expect(update.value).toMatchObject({
314
- id: 't-async',
315
- text: 'write tests',
316
- completed: false,
317
- })
318
-
319
- const doneResult = await iterator.return()
320
- expect(doneResult.done).toBe(true)
321
- })
322
-
323
- span.end()
324
-
325
- return { exporter, provider }
326
- }).pipe(
327
- Effect.scoped,
328
- Effect.tap(({ exporter, provider }) =>
329
- Effect.promise(async () => {
330
- await provider.forceFlush()
331
- expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
332
- expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
333
- await provider.shutdown()
334
- }),
335
- ),
336
- ),
337
- )
338
-
339
292
  Vitest.scopedLive('QueryBuilder subscription - direct table subscription', () =>
340
293
  Effect.gen(function* () {
341
294
  const { store, exporter, span, provider } = yield* makeQuery
package/src/mod.ts CHANGED
@@ -37,13 +37,7 @@ export {
37
37
  export { emptyDebugInfo, SqliteDbWrapper } from './SqliteDbWrapper.ts'
38
38
  export { type CreateStoreOptions, createStore, createStorePromise } from './store/create-store.ts'
39
39
  export { Store } from './store/store.ts'
40
- export type {
41
- OtelOptions,
42
- QueryDebugInfo,
43
- RefreshReason,
44
- SubscribeOptions,
45
- Unsubscribe,
46
- } from './store/store-types.ts'
40
+ export type { OtelOptions, QueryDebugInfo, RefreshReason, Unsubscribe } from './store/store-types.ts'
47
41
  export {
48
42
  type LiveStoreContext,
49
43
  type LiveStoreContextRunning,
@@ -14,7 +14,6 @@ import type { Effect, Runtime, Scope } from '@livestore/utils/effect'
14
14
  import { Deferred } from '@livestore/utils/effect'
15
15
  import type * as otel from '@opentelemetry/api'
16
16
 
17
- import type { LiveQuery } from '../live-queries/base-class.ts'
18
17
  import type { DebugRefreshReasonBase } from '../reactive.ts'
19
18
  import type { StackInfo } from '../utils/stack-info.ts'
20
19
  import type { Store } from './store.ts'
@@ -136,12 +135,3 @@ export type StoreEventsOptions<TSchema extends LiveStoreSchema> = {
136
135
  }
137
136
 
138
137
  export type Unsubscribe = () => void
139
-
140
- export type SubscribeOptions<TResult> = {
141
- onSubscribe?: (query$: LiveQuery<TResult>) => void
142
- onUnsubsubscribe?: () => void
143
- label?: string
144
- skipInitialRun?: boolean
145
- otelContext?: otel.Context
146
- stackInfo?: StackInfo
147
- }
@@ -20,7 +20,7 @@ import {
20
20
  UnexpectedError,
21
21
  } from '@livestore/common'
22
22
  import type { LiveStoreSchema } from '@livestore/common/schema'
23
- import { EventSequenceNumber, LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema'
23
+ import { LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema'
24
24
  import { assertNever, isDevEnv, notYetImplemented, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
25
25
  import type { Scope } from '@livestore/utils/effect'
26
26
  import {
@@ -52,21 +52,16 @@ import type { Ref } from '../reactive.ts'
52
52
  import { SqliteDbWrapper } from '../SqliteDbWrapper.ts'
53
53
  import { ReferenceCountedSet } from '../utils/data-structures.ts'
54
54
  import { downloadBlob, exposeDebugUtils } from '../utils/dev.ts'
55
+ import type { StackInfo } from '../utils/stack-info.ts'
55
56
  import type {
56
57
  RefreshReason,
57
58
  StoreCommitOptions,
58
59
  StoreEventsOptions,
59
60
  StoreOptions,
60
61
  StoreOtel,
61
- SubscribeOptions,
62
62
  Unsubscribe,
63
63
  } from './store-types.ts'
64
64
 
65
- type SubscribeQuery<TResult> =
66
- | LiveQueryDef<TResult, 'def' | 'signal-def'>
67
- | LiveQuery<TResult>
68
- | QueryBuilder<TResult, any, any>
69
-
70
65
  if (isDevEnv()) {
71
66
  exposeDebugUtils()
72
67
  }
@@ -354,125 +349,98 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
354
349
  }
355
350
 
356
351
  /**
357
- * Subscribe to the results of a query.
358
- *
359
- * - When providing an `onUpdate` callback it returns an {@link Unsubscribe} function.
360
- * - Without a callback it returns an {@link AsyncIterable} that yields query results.
352
+ * Subscribe to the results of a query
353
+ * Returns a function to cancel the subscription.
361
354
  *
362
355
  * @example
363
356
  * ```ts
364
357
  * const unsubscribe = store.subscribe(query$, (result) => console.log(result))
365
358
  * ```
366
- *
367
- * @example
368
- * ```ts
369
- * for await (const result of store.subscribe(query$)) {
370
- * console.log(result)
371
- * }
372
- * ```
373
359
  */
374
- subscribe: {
375
- <TResult>(
376
- query: SubscribeQuery<TResult>,
377
- onUpdate: (value: TResult) => void,
378
- options?: SubscribeOptions<TResult>,
379
- ): Unsubscribe
380
- <TResult>(query: SubscribeQuery<TResult>, options?: SubscribeOptions<TResult>): AsyncIterableIterator<TResult>
381
- } = <TResult>(
382
- query: SubscribeQuery<TResult>,
383
- onUpdateOrOptions?: ((value: TResult) => void) | SubscribeOptions<TResult>,
384
- maybeOptions?: SubscribeOptions<TResult>,
385
- ): Unsubscribe | AsyncIterableIterator<TResult> => {
386
- if (typeof onUpdateOrOptions === 'function') {
387
- const onUpdate = onUpdateOrOptions
388
- const options = maybeOptions
389
-
390
- this.checkShutdown('subscribe')
391
-
392
- return this.otel.tracer.startActiveSpan(
393
- `LiveStore.subscribe`,
394
- { attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
395
- options?.otelContext ?? this.otel.queriesSpanContext,
396
- (span) => {
397
- // console.debug('store sub', query$.id, query$.label)
398
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
399
-
400
- const queryRcRef = isQueryBuilder(query)
401
- ? queryDb(query).make(this.reactivityGraph.context!)
402
- : query._tag === 'def' || query._tag === 'signal-def'
403
- ? query.make(this.reactivityGraph.context!)
404
- : {
405
- value: query as LiveQuery<TResult>,
406
- deref: () => {},
407
- }
408
- const query$ = queryRcRef.value
360
+ subscribe = <TResult>(
361
+ query: LiveQueryDef<TResult, 'def' | 'signal-def'> | LiveQuery<TResult> | QueryBuilder<TResult, any, any>,
362
+ /** Called when the query result has changed */
363
+ onUpdate: (value: TResult) => void,
364
+ options?: {
365
+ onSubscribe?: (query$: LiveQuery<TResult>) => void
366
+ /** Gets called after the query subscription has been removed */
367
+ onUnsubsubscribe?: () => void
368
+ label?: string
369
+ /**
370
+ * Skips the initial `onUpdate` callback
371
+ * @default false
372
+ */
373
+ skipInitialRun?: boolean
374
+ otelContext?: otel.Context
375
+ /** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
376
+ stackInfo?: StackInfo
377
+ },
378
+ ): Unsubscribe => {
379
+ this.checkShutdown('subscribe')
409
380
 
410
- const label = `subscribe:${options?.label}`
411
- const effect = this.reactivityGraph.makeEffect(
412
- (get, _otelContext, debugRefreshReason) => onUpdate(get(query$.results$, otelContext, debugRefreshReason)),
413
- { label },
414
- )
381
+ return this.otel.tracer.startActiveSpan(
382
+ `LiveStore.subscribe`,
383
+ { attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
384
+ options?.otelContext ?? this.otel.queriesSpanContext,
385
+ (span) => {
386
+ // console.debug('store sub', query$.id, query$.label)
387
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
415
388
 
416
- if (options?.stackInfo) {
417
- query$.activeSubscriptions.add(options.stackInfo)
418
- }
389
+ const queryRcRef = isQueryBuilder(query)
390
+ ? queryDb(query).make(this.reactivityGraph.context!)
391
+ : query._tag === 'def' || query._tag === 'signal-def'
392
+ ? query.make(this.reactivityGraph.context!)
393
+ : {
394
+ value: query as LiveQuery<TResult>,
395
+ deref: () => {},
396
+ }
397
+ const query$ = queryRcRef.value
419
398
 
420
- options?.onSubscribe?.(query$)
399
+ const label = `subscribe:${options?.label}`
400
+ const effect = this.reactivityGraph.makeEffect(
401
+ (get, _otelContext, debugRefreshReason) => onUpdate(get(query$.results$, otelContext, debugRefreshReason)),
402
+ { label },
403
+ )
421
404
 
422
- this.activeQueries.add(query$ as LiveQuery<TResult>)
405
+ if (options?.stackInfo) {
406
+ query$.activeSubscriptions.add(options.stackInfo)
407
+ }
423
408
 
424
- // Running effect right away to get initial value (unless `skipInitialRun` is set)
425
- if (options?.skipInitialRun !== true && !query$.isDestroyed) {
426
- effect.doEffect(otelContext, {
427
- _tag: 'subscribe.initial',
428
- label: `subscribe-initial-run:${options?.label}`,
429
- })
430
- }
409
+ options?.onSubscribe?.(query$)
431
410
 
432
- const unsubscribe = () => {
433
- // console.debug('store unsub', query$.id, query$.label)
434
- try {
435
- this.reactivityGraph.destroyNode(effect)
436
- this.activeQueries.remove(query$ as LiveQuery<TResult>)
411
+ this.activeQueries.add(query$ as LiveQuery<TResult>)
437
412
 
438
- if (options?.stackInfo) {
439
- query$.activeSubscriptions.delete(options.stackInfo)
440
- }
413
+ // Running effect right away to get initial value (unless `skipInitialRun` is set)
414
+ if (options?.skipInitialRun !== true && !query$.isDestroyed) {
415
+ effect.doEffect(otelContext, { _tag: 'subscribe.initial', label: `subscribe-initial-run:${options?.label}` })
416
+ }
441
417
 
442
- queryRcRef.deref()
418
+ const unsubscribe = () => {
419
+ // console.debug('store unsub', query$.id, query$.label)
420
+ try {
421
+ this.reactivityGraph.destroyNode(effect)
422
+ this.activeQueries.remove(query$ as LiveQuery<TResult>)
443
423
 
444
- options?.onUnsubsubscribe?.()
445
- } finally {
446
- span.end()
424
+ if (options?.stackInfo) {
425
+ query$.activeSubscriptions.delete(options.stackInfo)
447
426
  }
448
- }
449
427
 
450
- return unsubscribe
451
- },
452
- )
453
- }
454
-
455
- return this.subscribeAsAsyncIterator(query, onUpdateOrOptions)
456
- }
428
+ queryRcRef.deref()
457
429
 
458
- private subscribeAsAsyncIterator = <TResult>(
459
- query: SubscribeQuery<TResult>,
460
- options?: SubscribeOptions<TResult>,
461
- ): AsyncIterableIterator<TResult> => {
462
- this.checkShutdown('subscribe')
463
-
464
- const iterator = Stream.toAsyncIterable(this.subscribeStream(query, options))[Symbol.asyncIterator]()
430
+ options?.onUnsubsubscribe?.()
431
+ } finally {
432
+ span.end()
433
+ }
434
+ }
465
435
 
466
- return Object.assign(iterator, {
467
- [Symbol.asyncIterator]() {
468
- return this
436
+ return unsubscribe
469
437
  },
470
- })
438
+ )
471
439
  }
472
440
 
473
441
  subscribeStream = <TResult>(
474
- query: SubscribeQuery<TResult>,
475
- options?: SubscribeOptions<TResult>,
442
+ query$: LiveQueryDef<TResult>,
443
+ options?: { label?: string; skipInitialRun?: boolean } | undefined,
476
444
  ): Stream.Stream<TResult> =>
477
445
  Stream.asyncPush<TResult>((emit) =>
478
446
  Effect.gen(this, function* () {
@@ -483,10 +451,11 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
483
451
 
484
452
  yield* Effect.acquireRelease(
485
453
  Effect.sync(() =>
486
- this.subscribe(query, (result) => emit.single(result), {
487
- ...(options ?? {}),
488
- otelContext,
489
- }),
454
+ this.subscribe(
455
+ query$,
456
+ (result) => emit.single(result),
457
+ omitUndefineds({ otelContext, label: options?.label }),
458
+ ),
490
459
  ),
491
460
  (unsub) => Effect.sync(() => unsub()),
492
461
  )
@@ -886,14 +855,11 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
886
855
  Effect.gen(this, function* () {
887
856
  const session = yield* this.syncProcessor.syncState
888
857
  yield* Effect.log(
889
- `Session sync state: ${EventSequenceNumber.toString(session.localHead)} (upstream: ${EventSequenceNumber.toString(session.upstreamHead)})`,
858
+ `Session sync state: ${session.localHead} (upstream: ${session.upstreamHead})`,
890
859
  session.toJSON(),
891
860
  )
892
861
  const leader = yield* this.clientSession.leaderThread.getSyncState
893
- yield* Effect.log(
894
- `Leader sync state: ${EventSequenceNumber.toString(leader.localHead)} (upstream: ${EventSequenceNumber.toString(leader.upstreamHead)})`,
895
- leader.toJSON(),
896
- )
862
+ yield* Effect.log(`Leader sync state: ${leader.localHead} (upstream: ${leader.upstreamHead})`, leader.toJSON())
897
863
  }).pipe(this.runEffectFork)
898
864
  },
899
865