@livestore/livestore 0.0.0-snapshot-6c08ae981df3f97c859084351e00b463f8dc8fb5 → 0.0.0-snapshot-c81e633d08ce770cc8cad9586fb0eb6d70f39f0e
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/live-queries/db-query.test.js +32 -1
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/store/store-types.d.ts +12 -1
- 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 +24 -27
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +31 -11
- package/dist/store/store.js.map +1 -1
- package/package.json +5 -5
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +87 -0
- package/src/live-queries/db-query.test.ts +49 -1
- package/src/mod.ts +8 -1
- package/src/store/store-types.ts +18 -0
- package/src/store/store.ts +57 -47
|
@@ -2,7 +2,7 @@ import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
|
2
2
|
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
3
3
|
import * as otel from '@opentelemetry/api'
|
|
4
4
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
5
|
-
import { expect } from 'vitest'
|
|
5
|
+
import { assert, expect } from 'vitest'
|
|
6
6
|
|
|
7
7
|
import * as RG from '../reactive.ts'
|
|
8
8
|
import { events, makeTodoMvc, tables } from '../utils/tests/fixture.ts'
|
|
@@ -289,6 +289,54 @@ 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)[Symbol.asyncIterator]()
|
|
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
|
+
assert(doneResult)
|
|
321
|
+
expect(doneResult.done).toBe(true)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
span.end()
|
|
325
|
+
|
|
326
|
+
return { exporter, provider }
|
|
327
|
+
}).pipe(
|
|
328
|
+
Effect.scoped,
|
|
329
|
+
Effect.tap(({ exporter, provider }) =>
|
|
330
|
+
Effect.promise(async () => {
|
|
331
|
+
await provider.forceFlush()
|
|
332
|
+
expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
|
|
333
|
+
expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
|
|
334
|
+
await provider.shutdown()
|
|
335
|
+
}),
|
|
336
|
+
),
|
|
337
|
+
),
|
|
338
|
+
)
|
|
339
|
+
|
|
292
340
|
Vitest.scopedLive('QueryBuilder subscription - direct table subscription', () =>
|
|
293
341
|
Effect.gen(function* () {
|
|
294
342
|
const { store, exporter, span, provider } = yield* makeQuery
|
package/src/mod.ts
CHANGED
|
@@ -37,7 +37,14 @@ 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 {
|
|
40
|
+
export type {
|
|
41
|
+
OtelOptions,
|
|
42
|
+
Queryable,
|
|
43
|
+
QueryDebugInfo,
|
|
44
|
+
RefreshReason,
|
|
45
|
+
SubscribeOptions,
|
|
46
|
+
Unsubscribe,
|
|
47
|
+
} from './store/store-types.ts'
|
|
41
48
|
export {
|
|
42
49
|
type LiveStoreContext,
|
|
43
50
|
type LiveStoreContextRunning,
|
package/src/store/store-types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
InvalidPullError,
|
|
6
6
|
IsOfflineError,
|
|
7
7
|
MaterializeError,
|
|
8
|
+
QueryBuilder,
|
|
8
9
|
StoreInterrupted,
|
|
9
10
|
SyncError,
|
|
10
11
|
UnexpectedError,
|
|
@@ -14,6 +15,7 @@ import type { Effect, Runtime, Scope } from '@livestore/utils/effect'
|
|
|
14
15
|
import { Deferred } from '@livestore/utils/effect'
|
|
15
16
|
import type * as otel from '@opentelemetry/api'
|
|
16
17
|
|
|
18
|
+
import type { LiveQuery, LiveQueryDef, SignalDef } from '../live-queries/base-class.ts'
|
|
17
19
|
import type { DebugRefreshReasonBase } from '../reactive.ts'
|
|
18
20
|
import type { StackInfo } from '../utils/stack-info.ts'
|
|
19
21
|
import type { Store } from './store.ts'
|
|
@@ -135,3 +137,19 @@ export type StoreEventsOptions<TSchema extends LiveStoreSchema> = {
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
export type Unsubscribe = () => void
|
|
140
|
+
|
|
141
|
+
export type SubscribeOptions<TResult> = {
|
|
142
|
+
onSubscribe?: (query$: LiveQuery<TResult>) => void
|
|
143
|
+
onUnsubsubscribe?: () => void
|
|
144
|
+
label?: string
|
|
145
|
+
skipInitialRun?: boolean
|
|
146
|
+
otelContext?: otel.Context
|
|
147
|
+
stackInfo?: StackInfo
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** All query definitions or instances the store can execute or subscribe to. */
|
|
151
|
+
export type Queryable<TResult> =
|
|
152
|
+
| LiveQueryDef<TResult>
|
|
153
|
+
| SignalDef<TResult>
|
|
154
|
+
| LiveQuery<TResult>
|
|
155
|
+
| QueryBuilder<TResult, any, any>
|
package/src/store/store.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
makeClientSessionSyncProcessor,
|
|
15
15
|
type PreparedBindValues,
|
|
16
16
|
prepareBindValues,
|
|
17
|
-
type QueryBuilder,
|
|
18
17
|
QueryBuilderAstSymbol,
|
|
19
18
|
replaceSessionIdSymbol,
|
|
20
19
|
UnexpectedError,
|
|
@@ -38,13 +37,7 @@ import {
|
|
|
38
37
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
39
38
|
import * as otel from '@opentelemetry/api'
|
|
40
39
|
|
|
41
|
-
import type {
|
|
42
|
-
LiveQuery,
|
|
43
|
-
LiveQueryDef,
|
|
44
|
-
ReactivityGraph,
|
|
45
|
-
ReactivityGraphContext,
|
|
46
|
-
SignalDef,
|
|
47
|
-
} from '../live-queries/base-class.ts'
|
|
40
|
+
import type { LiveQuery, ReactivityGraph, ReactivityGraphContext, SignalDef } from '../live-queries/base-class.ts'
|
|
48
41
|
import { makeReactivityGraph } from '../live-queries/base-class.ts'
|
|
49
42
|
import { makeExecBeforeFirstRun } from '../live-queries/client-document-get-query.ts'
|
|
50
43
|
import { queryDb } from '../live-queries/db-query.ts'
|
|
@@ -52,16 +45,26 @@ import type { Ref } from '../reactive.ts'
|
|
|
52
45
|
import { SqliteDbWrapper } from '../SqliteDbWrapper.ts'
|
|
53
46
|
import { ReferenceCountedSet } from '../utils/data-structures.ts'
|
|
54
47
|
import { downloadBlob, exposeDebugUtils } from '../utils/dev.ts'
|
|
55
|
-
import type { StackInfo } from '../utils/stack-info.ts'
|
|
56
48
|
import type {
|
|
49
|
+
Queryable,
|
|
57
50
|
RefreshReason,
|
|
58
51
|
StoreCommitOptions,
|
|
59
52
|
StoreEventsOptions,
|
|
60
53
|
StoreOptions,
|
|
61
54
|
StoreOtel,
|
|
55
|
+
SubscribeOptions,
|
|
62
56
|
Unsubscribe,
|
|
63
57
|
} from './store-types.ts'
|
|
64
58
|
|
|
59
|
+
type SubscribeFn = {
|
|
60
|
+
<TResult>(
|
|
61
|
+
query: Queryable<TResult>,
|
|
62
|
+
onUpdate: (value: TResult) => void,
|
|
63
|
+
options?: SubscribeOptions<TResult>,
|
|
64
|
+
): Unsubscribe
|
|
65
|
+
<TResult>(query: Queryable<TResult>, options?: SubscribeOptions<TResult>): AsyncIterable<TResult>
|
|
66
|
+
}
|
|
67
|
+
|
|
65
68
|
if (isDevEnv()) {
|
|
66
69
|
exposeDebugUtils()
|
|
67
70
|
}
|
|
@@ -349,32 +352,39 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
349
352
|
}
|
|
350
353
|
|
|
351
354
|
/**
|
|
352
|
-
* Subscribe to the results of a query
|
|
353
|
-
*
|
|
355
|
+
* Subscribe to the results of a query.
|
|
356
|
+
*
|
|
357
|
+
* - When providing an `onUpdate` callback it returns an {@link Unsubscribe} function.
|
|
358
|
+
* - Without a callback it returns an {@link AsyncIterable} that yields query results.
|
|
354
359
|
*
|
|
355
360
|
* @example
|
|
356
361
|
* ```ts
|
|
357
362
|
* const unsubscribe = store.subscribe(query$, (result) => console.log(result))
|
|
358
363
|
* ```
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* for await (const result of store.subscribe(query$)) {
|
|
368
|
+
* console.log(result)
|
|
369
|
+
* }
|
|
370
|
+
* ```
|
|
359
371
|
*/
|
|
360
|
-
subscribe = <TResult>(
|
|
361
|
-
query:
|
|
362
|
-
|
|
372
|
+
subscribe = (<TResult>(
|
|
373
|
+
query: Queryable<TResult>,
|
|
374
|
+
onUpdateOrOptions?: ((value: TResult) => void) | SubscribeOptions<TResult>,
|
|
375
|
+
maybeOptions?: SubscribeOptions<TResult>,
|
|
376
|
+
): Unsubscribe | AsyncIterable<TResult> => {
|
|
377
|
+
if (typeof onUpdateOrOptions === 'function') {
|
|
378
|
+
return this.subscribeWithCallback(query, onUpdateOrOptions, maybeOptions)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return this.subscribeAsAsyncIterable(query, onUpdateOrOptions)
|
|
382
|
+
}) as SubscribeFn
|
|
383
|
+
|
|
384
|
+
private subscribeWithCallback = <TResult>(
|
|
385
|
+
query: Queryable<TResult>,
|
|
363
386
|
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
|
-
},
|
|
387
|
+
options?: SubscribeOptions<TResult>,
|
|
378
388
|
): Unsubscribe => {
|
|
379
389
|
this.checkShutdown('subscribe')
|
|
380
390
|
|
|
@@ -383,7 +393,6 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
383
393
|
{ attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
|
|
384
394
|
options?.otelContext ?? this.otel.queriesSpanContext,
|
|
385
395
|
(span) => {
|
|
386
|
-
// console.debug('store sub', query$.id, query$.label)
|
|
387
396
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
388
397
|
|
|
389
398
|
const queryRcRef = isQueryBuilder(query)
|
|
@@ -410,13 +419,14 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
410
419
|
|
|
411
420
|
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
|
412
421
|
|
|
413
|
-
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
|
414
422
|
if (options?.skipInitialRun !== true && !query$.isDestroyed) {
|
|
415
|
-
effect.doEffect(otelContext, {
|
|
423
|
+
effect.doEffect(otelContext, {
|
|
424
|
+
_tag: 'subscribe.initial',
|
|
425
|
+
label: `subscribe-initial-run:${options?.label}`,
|
|
426
|
+
})
|
|
416
427
|
}
|
|
417
428
|
|
|
418
429
|
const unsubscribe = () => {
|
|
419
|
-
// console.debug('store unsub', query$.id, query$.label)
|
|
420
430
|
try {
|
|
421
431
|
this.reactivityGraph.destroyNode(effect)
|
|
422
432
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
@@ -438,10 +448,16 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
438
448
|
)
|
|
439
449
|
}
|
|
440
450
|
|
|
441
|
-
|
|
442
|
-
query
|
|
443
|
-
options?:
|
|
444
|
-
):
|
|
451
|
+
private subscribeAsAsyncIterable = <TResult>(
|
|
452
|
+
query: Queryable<TResult>,
|
|
453
|
+
options?: SubscribeOptions<TResult>,
|
|
454
|
+
): AsyncIterable<TResult> => {
|
|
455
|
+
this.checkShutdown('subscribe')
|
|
456
|
+
|
|
457
|
+
return Stream.toAsyncIterable(this.subscribeStream(query, options))
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
subscribeStream = <TResult>(query: Queryable<TResult>, options?: SubscribeOptions<TResult>): Stream.Stream<TResult> =>
|
|
445
461
|
Stream.asyncPush<TResult>((emit) =>
|
|
446
462
|
Effect.gen(this, function* () {
|
|
447
463
|
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(
|
|
@@ -451,11 +467,10 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
451
467
|
|
|
452
468
|
yield* Effect.acquireRelease(
|
|
453
469
|
Effect.sync(() =>
|
|
454
|
-
this.subscribe(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
),
|
|
470
|
+
this.subscribe(query, (result) => emit.single(result), {
|
|
471
|
+
...(options ?? {}),
|
|
472
|
+
otelContext,
|
|
473
|
+
}),
|
|
459
474
|
),
|
|
460
475
|
(unsub) => Effect.sync(() => unsub()),
|
|
461
476
|
)
|
|
@@ -477,12 +492,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
477
492
|
* ```
|
|
478
493
|
*/
|
|
479
494
|
query = <TResult>(
|
|
480
|
-
query:
|
|
481
|
-
| QueryBuilder<TResult, any, any>
|
|
482
|
-
| LiveQuery<TResult>
|
|
483
|
-
| LiveQueryDef<TResult>
|
|
484
|
-
| SignalDef<TResult>
|
|
485
|
-
| { query: string; bindValues: Bindable; schema?: Schema.Schema<TResult> },
|
|
495
|
+
query: Queryable<TResult> | { query: string; bindValues: Bindable; schema?: Schema.Schema<TResult> },
|
|
486
496
|
options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
|
|
487
497
|
): TResult => {
|
|
488
498
|
this.checkShutdown('query')
|