@livestore/livestore 0.4.0-dev.17 → 0.4.0-dev.18
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 +59 -0
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +24 -6
- package/dist/store/store.js.map +1 -1
- package/package.json +5 -5
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +220 -0
- package/src/live-queries/db-query.test.ts +98 -2
- package/src/store/store.ts +23 -6
|
@@ -6,7 +6,7 @@ import { assert, expect } from 'vitest'
|
|
|
6
6
|
|
|
7
7
|
import * as RG from '../reactive.ts'
|
|
8
8
|
import { StoreInternalsSymbol } from '../store/store-types.ts'
|
|
9
|
-
import { events, makeTodoMvc, tables } from '../utils/tests/fixture.ts'
|
|
9
|
+
import { events, makeTodoMvc, type Todo, tables } from '../utils/tests/fixture.ts'
|
|
10
10
|
import { getAllSimplifiedRootSpans, getSimplifiedRootSpan } from '../utils/tests/otel.ts'
|
|
11
11
|
import { computed } from './computed.ts'
|
|
12
12
|
import { queryDb } from './db-query.ts'
|
|
@@ -238,6 +238,53 @@ Vitest.describe('otel', () => {
|
|
|
238
238
|
),
|
|
239
239
|
)
|
|
240
240
|
|
|
241
|
+
Vitest.scopedLive('QueryBuilder subscription - skipInitialRun', () =>
|
|
242
|
+
Effect.gen(function* () {
|
|
243
|
+
const { store, exporter, span, provider } = yield* makeQuery
|
|
244
|
+
|
|
245
|
+
const callbackResults: Todo[] = []
|
|
246
|
+
const defaultTodo: Todo = { id: '', text: '', completed: false }
|
|
247
|
+
|
|
248
|
+
const queryBuilder = tables.todos
|
|
249
|
+
.where({ completed: false })
|
|
250
|
+
.first({ behaviour: 'fallback', fallback: () => defaultTodo })
|
|
251
|
+
|
|
252
|
+
const unsubscribe = store.subscribe(
|
|
253
|
+
queryBuilder,
|
|
254
|
+
(result) => {
|
|
255
|
+
callbackResults.push(result)
|
|
256
|
+
},
|
|
257
|
+
{ skipInitialRun: true },
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
expect(callbackResults).toHaveLength(0)
|
|
261
|
+
|
|
262
|
+
store.commit(events.todoCreated({ id: 't-skip', text: 'skip initial', completed: false }))
|
|
263
|
+
|
|
264
|
+
expect(callbackResults).toHaveLength(1)
|
|
265
|
+
expect(callbackResults[0]).toMatchObject({
|
|
266
|
+
id: 't-skip',
|
|
267
|
+
text: 'skip initial',
|
|
268
|
+
completed: false,
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
unsubscribe()
|
|
272
|
+
span.end()
|
|
273
|
+
|
|
274
|
+
return { exporter, provider }
|
|
275
|
+
}).pipe(
|
|
276
|
+
Effect.scoped,
|
|
277
|
+
Effect.tap(({ exporter, provider }) =>
|
|
278
|
+
Effect.promise(async () => {
|
|
279
|
+
await provider.forceFlush()
|
|
280
|
+
expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
|
|
281
|
+
expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
|
|
282
|
+
await provider.shutdown()
|
|
283
|
+
}),
|
|
284
|
+
),
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
|
|
241
288
|
Vitest.scopedLive('QueryBuilder subscription - unsubscribe functionality', () =>
|
|
242
289
|
Effect.gen(function* () {
|
|
243
290
|
const { store, exporter, span, provider } = yield* makeQuery
|
|
@@ -294,7 +341,7 @@ Vitest.describe('otel', () => {
|
|
|
294
341
|
Effect.gen(function* () {
|
|
295
342
|
const { store, exporter, span, provider } = yield* makeQuery
|
|
296
343
|
|
|
297
|
-
const defaultTodo = { id: '', text: '', completed: false }
|
|
344
|
+
const defaultTodo: Todo = { id: '', text: '', completed: false }
|
|
298
345
|
|
|
299
346
|
const queryBuilder = tables.todos
|
|
300
347
|
.where({ completed: false })
|
|
@@ -338,6 +385,55 @@ Vitest.describe('otel', () => {
|
|
|
338
385
|
),
|
|
339
386
|
)
|
|
340
387
|
|
|
388
|
+
Vitest.scopedLive('QueryBuilder subscription - async iterator with skipInitialRun', () =>
|
|
389
|
+
Effect.gen(function* () {
|
|
390
|
+
const { store, exporter, span, provider } = yield* makeQuery
|
|
391
|
+
|
|
392
|
+
const defaultTodo: Todo = { id: '', text: '', completed: false }
|
|
393
|
+
|
|
394
|
+
const queryBuilder = tables.todos
|
|
395
|
+
.where({ completed: false })
|
|
396
|
+
.first({ behaviour: 'fallback', fallback: () => defaultTodo })
|
|
397
|
+
|
|
398
|
+
yield* Effect.promise(async () => {
|
|
399
|
+
const iterator = store.subscribe(queryBuilder, { skipInitialRun: true })[Symbol.asyncIterator]()
|
|
400
|
+
|
|
401
|
+
const pending = Symbol('pending')
|
|
402
|
+
const nextPromise = iterator.next()
|
|
403
|
+
const raceResult = await Promise.race([nextPromise, Promise.resolve(pending)])
|
|
404
|
+
expect(raceResult).toBe(pending)
|
|
405
|
+
|
|
406
|
+
store.commit(events.todoCreated({ id: 't-async-skip', text: 'write tests later', completed: false }))
|
|
407
|
+
|
|
408
|
+
const update = await nextPromise
|
|
409
|
+
expect(update.done).toBe(false)
|
|
410
|
+
expect(update.value).toMatchObject({
|
|
411
|
+
id: 't-async-skip',
|
|
412
|
+
text: 'write tests later',
|
|
413
|
+
completed: false,
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
const doneResult = await iterator.return?.()
|
|
417
|
+
assert(doneResult)
|
|
418
|
+
expect(doneResult.done).toBe(true)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
span.end()
|
|
422
|
+
|
|
423
|
+
return { exporter, provider }
|
|
424
|
+
}).pipe(
|
|
425
|
+
Effect.scoped,
|
|
426
|
+
Effect.tap(({ exporter, provider }) =>
|
|
427
|
+
Effect.promise(async () => {
|
|
428
|
+
await provider.forceFlush()
|
|
429
|
+
expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
|
|
430
|
+
expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
|
|
431
|
+
await provider.shutdown()
|
|
432
|
+
}),
|
|
433
|
+
),
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
|
|
341
437
|
Vitest.scopedLive('QueryBuilder subscription - direct table subscription', () =>
|
|
342
438
|
Effect.gen(function* () {
|
|
343
439
|
const { store, exporter, span, provider } = yield* makeQuery
|
package/src/store/store.ts
CHANGED
|
@@ -419,10 +419,23 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
419
419
|
const query$ = queryRcRef.value
|
|
420
420
|
|
|
421
421
|
const label = `subscribe:${options?.label}`
|
|
422
|
+
let suppressCallback = options?.skipInitialRun === true
|
|
422
423
|
const effect = this[StoreInternalsSymbol].reactivityGraph.makeEffect(
|
|
423
|
-
(get, _otelContext, debugRefreshReason) =>
|
|
424
|
+
(get, _otelContext, debugRefreshReason) => {
|
|
425
|
+
const result = get(query$.results$, otelContext, debugRefreshReason)
|
|
426
|
+
if (suppressCallback) {
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
onUpdate(result)
|
|
430
|
+
},
|
|
424
431
|
{ label },
|
|
425
432
|
)
|
|
433
|
+
const runInitialEffect = () => {
|
|
434
|
+
effect.doEffect(otelContext, {
|
|
435
|
+
_tag: 'subscribe.initial',
|
|
436
|
+
label: `subscribe-initial-run:${options?.label}`,
|
|
437
|
+
})
|
|
438
|
+
}
|
|
426
439
|
|
|
427
440
|
if (options?.stackInfo) {
|
|
428
441
|
query$.activeSubscriptions.add(options.stackInfo)
|
|
@@ -432,11 +445,15 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
432
445
|
|
|
433
446
|
this[StoreInternalsSymbol].activeQueries.add(query$ as LiveQuery<TResult>)
|
|
434
447
|
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
448
|
+
if (!query$.isDestroyed) {
|
|
449
|
+
if (suppressCallback) {
|
|
450
|
+
// We still run once to register dependencies in the reactive graph, but suppress the initial callback so the
|
|
451
|
+
// caller truly skips the first emission; subsequent runs (after commits) will call the callback.
|
|
452
|
+
runInitialEffect()
|
|
453
|
+
suppressCallback = false
|
|
454
|
+
} else {
|
|
455
|
+
runInitialEffect()
|
|
456
|
+
}
|
|
440
457
|
}
|
|
441
458
|
|
|
442
459
|
const unsubscribe = () => {
|