@livestore/livestore 0.4.0-dev.9 → 0.4.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/README.md +0 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.js +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +5 -5
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +8 -8
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +4 -3
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +133 -5
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +187 -8
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.d.ts +2 -0
- package/dist/effect/LiveStore.test.d.ts.map +1 -0
- package/dist/effect/LiveStore.test.js +42 -0
- package/dist/effect/LiveStore.test.js.map +1 -0
- package/dist/effect/mod.d.ts +1 -1
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +3 -1
- package/dist/effect/mod.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +110 -7
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +2 -2
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +1 -1
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
- package/dist/live-queries/client-document-get-query.js +4 -3
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts +56 -0
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +58 -2
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +21 -19
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +106 -23
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.d.ts +49 -0
- package/dist/live-queries/signal.d.ts.map +1 -1
- package/dist/live-queries/signal.js +49 -0
- package/dist/live-queries/signal.js.map +1 -1
- package/dist/live-queries/signal.test.js +2 -2
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/mod.d.ts +3 -3
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -2
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +9 -9
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +9 -26
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +2 -2
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +215 -0
- package/dist/store/StoreRegistry.d.ts.map +1 -0
- package/dist/store/StoreRegistry.js +267 -0
- package/dist/store/StoreRegistry.js.map +1 -0
- package/dist/store/StoreRegistry.test.d.ts +2 -0
- package/dist/store/StoreRegistry.test.d.ts.map +1 -0
- package/dist/store/StoreRegistry.test.js +381 -0
- package/dist/store/StoreRegistry.test.js.map +1 -0
- package/dist/store/create-store.d.ts +98 -18
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +49 -20
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +5 -16
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +59 -18
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.d.ts +2 -0
- package/dist/store/store-eventstream.test.d.ts.map +1 -0
- package/dist/store/store-eventstream.test.js +65 -0
- package/dist/store/store-eventstream.test.js.map +1 -0
- package/dist/store/store-types.d.ts +285 -27
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +77 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.d.ts +2 -0
- package/dist/store/store-types.test.d.ts.map +1 -0
- package/dist/store/store-types.test.js +39 -0
- package/dist/store/store-types.test.js.map +1 -0
- package/dist/store/store.d.ts +253 -66
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +442 -153
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/stack-info.js +2 -2
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +20 -5
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +7 -0
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +5 -5
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +59 -17
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +5 -3
- package/src/SqliteDbWrapper.ts +12 -11
- package/src/ambient.d.ts +0 -7
- package/src/effect/LiveStore.test.ts +61 -0
- package/src/effect/LiveStore.ts +388 -13
- package/src/effect/mod.ts +13 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +604 -192
- package/src/live-queries/base-class.ts +126 -28
- package/src/live-queries/client-document-get-query.ts +6 -4
- package/src/live-queries/computed.ts +59 -2
- package/src/live-queries/db-query.test.ts +162 -24
- package/src/live-queries/db-query.ts +23 -20
- package/src/live-queries/signal.test.ts +3 -2
- package/src/live-queries/signal.ts +49 -0
- package/src/mod.ts +19 -2
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +22 -23
- package/src/store/StoreRegistry.test.ts +540 -0
- package/src/store/StoreRegistry.ts +418 -0
- package/src/store/create-store.ts +158 -39
- package/src/store/devtools.ts +77 -33
- package/src/store/store-eventstream.test.ts +114 -0
- package/src/store/store-types.test.ts +52 -0
- package/src/store/store-types.ts +360 -40
- package/src/store/store.ts +571 -236
- package/src/utils/dev.ts +2 -3
- package/src/utils/stack-info.ts +2 -2
- package/src/utils/tests/fixture.ts +9 -1
- package/src/utils/tests/otel.ts +8 -7
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
|
4
|
+
import type { MockSyncBackend } from '@livestore/common'
|
|
5
|
+
import { type ClientSessionLeaderThreadProxy, makeMockSyncBackend, type UnknownError } from '@livestore/common'
|
|
6
|
+
import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
|
|
7
|
+
import { EventFactory } from '@livestore/common/testing'
|
|
8
|
+
import type { ShutdownDeferred, Store } from '@livestore/livestore'
|
|
9
|
+
import { createStore, makeShutdownDeferred } from '@livestore/livestore'
|
|
10
|
+
import { omitUndefineds } from '@livestore/utils'
|
|
11
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
12
|
+
import type { OtelTracer, Scope } from '@livestore/utils/effect'
|
|
13
|
+
import { Context, Effect, FetchHttpClient, Layer, Logger, LogLevel, Queue, Stream } from '@livestore/utils/effect'
|
|
14
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
|
15
|
+
import { PlatformNode } from '@livestore/utils/node'
|
|
16
|
+
|
|
17
|
+
import { events, schema } from '../utils/tests/fixture.ts'
|
|
18
|
+
|
|
19
|
+
const withTestCtx = Vitest.makeWithTestCtx({
|
|
20
|
+
makeLayer: () =>
|
|
21
|
+
Layer.mergeAll(
|
|
22
|
+
TestContextLive,
|
|
23
|
+
PlatformNode.NodeFileSystem.layer,
|
|
24
|
+
FetchHttpClient.layer,
|
|
25
|
+
Logger.minimumLogLevel(LogLevel.Debug),
|
|
26
|
+
),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The purpose of this test is a store integration test for event streaming.
|
|
31
|
+
* Main test covering event streaming logic itself is located in:
|
|
32
|
+
* tests/package-common/src/leader-thread/stream-events.test.ts
|
|
33
|
+
*/
|
|
34
|
+
Vitest.describe('Store events API', () => {
|
|
35
|
+
Vitest.scopedLive('should resume when reconnected to sync backend', (test) =>
|
|
36
|
+
Effect.gen(function* () {
|
|
37
|
+
const { makeStore, mockSyncBackend } = yield* TestContext
|
|
38
|
+
const store = yield* makeStore()
|
|
39
|
+
yield* mockSyncBackend.connect
|
|
40
|
+
|
|
41
|
+
const eventFactory = EventFactory.makeFactory(events)({
|
|
42
|
+
client: EventFactory.clientIdentity('other-client', 'static-session-id'),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Queue is used in order to allow analyzing the stream in stages
|
|
46
|
+
const eventsQueue = yield* Queue.unbounded<LiveStoreEvent.Client.ForSchema<typeof schema>>()
|
|
47
|
+
|
|
48
|
+
yield* store.eventsStream().pipe(
|
|
49
|
+
Stream.tap((event) => Queue.offer(eventsQueue, event)),
|
|
50
|
+
Stream.runDrain,
|
|
51
|
+
Effect.forkScoped,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
store.commit(eventFactory.todoCreated.next({ id: '1', text: 't1', completed: false }))
|
|
55
|
+
const initialEvent = yield* Queue.take(eventsQueue)
|
|
56
|
+
expect(initialEvent.name).toEqual('todo.created')
|
|
57
|
+
expect(initialEvent.args).toMatchObject({ id: '1' })
|
|
58
|
+
|
|
59
|
+
yield* mockSyncBackend.disconnect
|
|
60
|
+
store.commit(eventFactory.todoCreated.next({ id: '2', text: 't2', completed: false }))
|
|
61
|
+
const maybeWhileDisconnected = yield* Queue.take(eventsQueue).pipe(Effect.timeout('250 millis'), Effect.option)
|
|
62
|
+
expect(maybeWhileDisconnected._tag).toEqual('None')
|
|
63
|
+
|
|
64
|
+
yield* mockSyncBackend.connect
|
|
65
|
+
const resumedEvent = yield* Queue.take(eventsQueue)
|
|
66
|
+
expect(resumedEvent.name).toEqual('todo.created')
|
|
67
|
+
expect(resumedEvent.args).toMatchObject({ id: '2' })
|
|
68
|
+
}).pipe(withTestCtx(test)),
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
class TestContext extends Context.Tag('TestContext')<
|
|
73
|
+
TestContext,
|
|
74
|
+
{
|
|
75
|
+
makeStore: (args?: {
|
|
76
|
+
boot?: (store: Store) => void
|
|
77
|
+
testing?: {
|
|
78
|
+
overrides?: {
|
|
79
|
+
clientSession?: {
|
|
80
|
+
leaderThreadProxy?: (
|
|
81
|
+
original: ClientSessionLeaderThreadProxy.ClientSessionLeaderThreadProxy,
|
|
82
|
+
) => Partial<ClientSessionLeaderThreadProxy.ClientSessionLeaderThreadProxy>
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}) => Effect.Effect<Store, UnknownError, Scope.Scope | OtelTracer.OtelTracer>
|
|
87
|
+
mockSyncBackend: MockSyncBackend
|
|
88
|
+
shutdownDeferred: ShutdownDeferred
|
|
89
|
+
}
|
|
90
|
+
>() {}
|
|
91
|
+
|
|
92
|
+
const TestContextLive = Layer.scoped(
|
|
93
|
+
TestContext,
|
|
94
|
+
Effect.gen(function* () {
|
|
95
|
+
const mockSyncBackend = yield* makeMockSyncBackend()
|
|
96
|
+
const shutdownDeferred = yield* makeShutdownDeferred
|
|
97
|
+
|
|
98
|
+
const makeStore: typeof TestContext.Service.makeStore = (args) => {
|
|
99
|
+
const adapter = makeInMemoryAdapter({
|
|
100
|
+
sync: { backend: () => mockSyncBackend.makeSyncBackend, onSyncError: 'shutdown' },
|
|
101
|
+
...omitUndefineds({ testing: args?.testing }),
|
|
102
|
+
})
|
|
103
|
+
return createStore({
|
|
104
|
+
schema: schema as LiveStoreSchema,
|
|
105
|
+
adapter,
|
|
106
|
+
storeId: nanoid(),
|
|
107
|
+
shutdownDeferred,
|
|
108
|
+
...omitUndefineds({ boot: args?.boot }),
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { makeStore, mockSyncBackend, shutdownDeferred }
|
|
113
|
+
}),
|
|
114
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { QueryBuilder } from '@livestore/common'
|
|
4
|
+
import { QueryBuilderTypeId } from '@livestore/common'
|
|
5
|
+
import { Schema } from '@livestore/utils/effect'
|
|
6
|
+
|
|
7
|
+
import { TypeId } from '../live-queries/base-class.ts'
|
|
8
|
+
import { queryDb, signal } from '../live-queries/mod.ts'
|
|
9
|
+
import { isQueryable } from './store-types.ts'
|
|
10
|
+
|
|
11
|
+
const makeQueryBuilder = (): QueryBuilder<any, any, any> =>
|
|
12
|
+
({
|
|
13
|
+
[QueryBuilderTypeId]: QueryBuilderTypeId,
|
|
14
|
+
ResultType: null,
|
|
15
|
+
asSql: () => ({ query: 'select 1', bindValues: [], usedTables: new Set<string>() }),
|
|
16
|
+
toString: () => 'select 1',
|
|
17
|
+
}) as unknown as QueryBuilder<any, any, any>
|
|
18
|
+
|
|
19
|
+
describe('isQueryable', () => {
|
|
20
|
+
it('identifies live query definitions', () => {
|
|
21
|
+
const def = queryDb({
|
|
22
|
+
query: 'select 1 as value',
|
|
23
|
+
schema: Schema.Array(Schema.Struct({ value: Schema.Number })),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
expect(isQueryable(def)).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('identifies signal definitions', () => {
|
|
30
|
+
const sig = signal(0, { label: 'count' })
|
|
31
|
+
|
|
32
|
+
expect(isQueryable(sig)).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('identifies live query instances', () => {
|
|
36
|
+
const liveQueryLike = { [TypeId]: TypeId } as const
|
|
37
|
+
|
|
38
|
+
expect(isQueryable(liveQueryLike)).toBe(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('identifies query builders', () => {
|
|
42
|
+
const qb = makeQueryBuilder()
|
|
43
|
+
|
|
44
|
+
expect(isQueryable(qb)).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('rejects unrelated values', () => {
|
|
48
|
+
expect(isQueryable(null)).toBe(false)
|
|
49
|
+
expect(isQueryable(undefined)).toBe(false)
|
|
50
|
+
expect(isQueryable({})).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
})
|
package/src/store/store-types.ts
CHANGED
|
@@ -1,46 +1,79 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ClientSession,
|
|
3
|
-
ClientSessionSyncProcessorSimulationParams,
|
|
4
|
-
IntentionalShutdownCause,
|
|
5
|
-
InvalidPullError,
|
|
6
|
-
IsOfflineError,
|
|
7
|
-
MaterializeError,
|
|
8
|
-
StoreInterrupted,
|
|
9
|
-
SyncError,
|
|
10
|
-
UnexpectedError,
|
|
11
|
-
} from '@livestore/common'
|
|
12
|
-
import type { EventSequenceNumber, LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
|
|
13
|
-
import type { Effect, Runtime, Scope } from '@livestore/utils/effect'
|
|
14
|
-
import { Deferred } from '@livestore/utils/effect'
|
|
15
1
|
import type * as otel from '@opentelemetry/api'
|
|
16
2
|
|
|
17
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
type ClientSession,
|
|
5
|
+
type ClientSessionSyncProcessor,
|
|
6
|
+
type ClientSessionSyncProcessorSimulationParams,
|
|
7
|
+
type IntentionalShutdownCause,
|
|
8
|
+
isQueryBuilder,
|
|
9
|
+
type MaterializeError,
|
|
10
|
+
type QueryBuilder,
|
|
11
|
+
type StoreInterrupted,
|
|
12
|
+
type BackendIdMismatchError,
|
|
13
|
+
type UnknownError,
|
|
14
|
+
} from '@livestore/common'
|
|
15
|
+
import type { StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
16
|
+
import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
|
|
17
|
+
import type { Effect, Runtime, Schema, Scope } from '@livestore/utils/effect'
|
|
18
|
+
import { Deferred, Predicate } from '@livestore/utils/effect'
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
LiveQuery,
|
|
22
|
+
LiveQueryDef,
|
|
23
|
+
ReactivityGraph,
|
|
24
|
+
ReactivityGraphContext,
|
|
25
|
+
SignalDef,
|
|
26
|
+
} from '../live-queries/base-class.ts'
|
|
27
|
+
import { TypeId } from '../live-queries/base-class.ts'
|
|
28
|
+
import type { DebugRefreshReasonBase, Ref } from '../reactive.ts'
|
|
29
|
+
import type { SqliteDbWrapper } from '../SqliteDbWrapper.ts'
|
|
30
|
+
import type { ReferenceCountedSet } from '../utils/data-structures.ts'
|
|
18
31
|
import type { StackInfo } from '../utils/stack-info.ts'
|
|
19
32
|
import type { Store } from './store.ts'
|
|
20
33
|
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Union type representing the possible states of a LiveStore context.
|
|
36
|
+
*
|
|
37
|
+
* Used by framework integrations (React, Solid, etc.) to track Store lifecycle:
|
|
38
|
+
* - `running`: Store is active and ready for queries/commits
|
|
39
|
+
* - `error`: Store failed during boot or operation
|
|
40
|
+
* - `shutdown`: Store was intentionally shut down or interrupted
|
|
41
|
+
*
|
|
42
|
+
* @typeParam TSchema - The LiveStore schema type. Defaults to `LiveStoreSchema.Any`.
|
|
43
|
+
*/
|
|
44
|
+
export type LiveStoreContext<TSchema extends LiveStoreSchema = LiveStoreSchema.Any> =
|
|
45
|
+
| LiveStoreContextRunning<TSchema>
|
|
23
46
|
| {
|
|
24
47
|
stage: 'error'
|
|
25
|
-
error:
|
|
48
|
+
error: unknown
|
|
26
49
|
}
|
|
27
50
|
| {
|
|
28
51
|
stage: 'shutdown'
|
|
29
|
-
cause: IntentionalShutdownCause | StoreInterrupted |
|
|
52
|
+
cause: IntentionalShutdownCause | StoreInterrupted | UnknownError
|
|
30
53
|
}
|
|
31
54
|
|
|
32
55
|
export type ShutdownDeferred = Deferred.Deferred<
|
|
33
56
|
IntentionalShutdownCause,
|
|
34
|
-
|
|
57
|
+
UnknownError | StoreInterrupted | MaterializeError | BackendIdMismatchError
|
|
35
58
|
>
|
|
36
59
|
export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
|
|
37
60
|
IntentionalShutdownCause,
|
|
38
|
-
|
|
61
|
+
UnknownError | StoreInterrupted | MaterializeError | BackendIdMismatchError
|
|
39
62
|
>()
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Context state when the Store is active and ready for use.
|
|
66
|
+
*
|
|
67
|
+
* This is the normal operating state where you can query data, commit events,
|
|
68
|
+
* and subscribe to changes.
|
|
69
|
+
*
|
|
70
|
+
* @typeParam TSchema - The LiveStore schema type. Defaults to `LiveStoreSchema.Any`
|
|
71
|
+
* for backwards compatibility, but prefer providing the concrete schema type
|
|
72
|
+
* for full type safety.
|
|
73
|
+
*/
|
|
74
|
+
export type LiveStoreContextRunning<TSchema extends LiveStoreSchema = LiveStoreSchema.Any> = {
|
|
42
75
|
stage: 'running'
|
|
43
|
-
store: Store
|
|
76
|
+
store: Store<TSchema>
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
export type OtelOptions = {
|
|
@@ -48,7 +81,108 @@ export type OtelOptions = {
|
|
|
48
81
|
rootSpanContext: otel.Context
|
|
49
82
|
}
|
|
50
83
|
|
|
51
|
-
export
|
|
84
|
+
export const StoreInternalsSymbol = Symbol.for('livestore.StoreInternals')
|
|
85
|
+
export type StoreInternalsSymbol = typeof StoreInternalsSymbol
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Opaque bag containing the Store's implementation details.
|
|
89
|
+
*
|
|
90
|
+
* Not part of the public API — shapes and semantics may change without notice.
|
|
91
|
+
* Access only from within the @livestore/livestore package (and Devtools) via
|
|
92
|
+
* `StoreInternalsSymbol` to avoid accidental coupling in application code.
|
|
93
|
+
*/
|
|
94
|
+
export type StoreInternals = {
|
|
95
|
+
/**
|
|
96
|
+
* Runtime event schema used for encoding/decoding events.
|
|
97
|
+
*
|
|
98
|
+
* Exposed primarily for Devtools (e.g. databrowser) to validate ad‑hoc
|
|
99
|
+
* event payloads. Application code should not depend on it directly.
|
|
100
|
+
*/
|
|
101
|
+
readonly eventSchema: Schema.Schema<LiveStoreEvent.Client.Decoded, LiveStoreEvent.Client.Encoded>
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The active client session backing this Store. Provides access to the
|
|
105
|
+
* leader thread, network status, and shutdown signaling.
|
|
106
|
+
*
|
|
107
|
+
* Do not close or mutate directly — use `store.shutdown(...)`.
|
|
108
|
+
*/
|
|
109
|
+
readonly clientSession: ClientSession
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Wrapper around the local SQLite state database. Centralizes query
|
|
113
|
+
* planning, caching, and change tracking used by reads and materializers.
|
|
114
|
+
*/
|
|
115
|
+
readonly sqliteDbWrapper: SqliteDbWrapper
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Effect runtime and scope used to fork background fibers for the Store.
|
|
119
|
+
*
|
|
120
|
+
* - `runtime` executes effects from imperative Store APIs.
|
|
121
|
+
* - `lifetimeScope` owns forked fibers; closed during Store shutdown.
|
|
122
|
+
*/
|
|
123
|
+
readonly effectContext: {
|
|
124
|
+
/** Effect runtime to run Store effects with proper environment. */
|
|
125
|
+
readonly runtime: Runtime.Runtime<Scope.Scope>
|
|
126
|
+
/** Scope that owns all long‑lived fibers spawned by the Store. */
|
|
127
|
+
readonly lifetimeScope: Scope.Scope
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* OpenTelemetry primitives used for instrumentation of commits, queries,
|
|
132
|
+
* and Store boot lifecycle.
|
|
133
|
+
*/
|
|
134
|
+
readonly otel: StoreOtel
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The Store's reactive graph instance used to model dependencies and
|
|
138
|
+
* propagate updates. Provides APIs to create refs/thunks/effects and to
|
|
139
|
+
* subscribe to refresh cycles.
|
|
140
|
+
*/
|
|
141
|
+
readonly reactivityGraph: ReactivityGraph
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Per‑table reactive refs used to broadcast invalidations when materializers
|
|
145
|
+
* write to tables. Values are always `null`; equality is intentionally
|
|
146
|
+
* `false` to force recomputation.
|
|
147
|
+
*
|
|
148
|
+
* Keys are SQLite table names (user tables; some system tables may be
|
|
149
|
+
* intentionally excluded from refresh).
|
|
150
|
+
*/
|
|
151
|
+
readonly tableRefs: Readonly<Record<string, Ref<null, ReactivityGraphContext, RefreshReason>>>
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set of currently subscribed LiveQuery instances (reference‑counted).
|
|
155
|
+
* Used for Devtools and diagnostics.
|
|
156
|
+
*/
|
|
157
|
+
readonly activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Client‑session sync processor orchestrating push/pull and materialization
|
|
161
|
+
* of events into local state.
|
|
162
|
+
*/
|
|
163
|
+
readonly syncProcessor: ClientSessionSyncProcessor
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Starts background fibers for sync and observation. Must be run exactly
|
|
167
|
+
* once per Store instance. Scoped; installs finalizers to end spans and
|
|
168
|
+
* detach reactive refs.
|
|
169
|
+
*/
|
|
170
|
+
readonly boot: Effect.Effect<void, UnknownError, Scope.Scope>
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Tracks whether the Store has been shut down. When true, mutating APIs
|
|
174
|
+
* should reject via `checkShutdown`.
|
|
175
|
+
*/
|
|
176
|
+
isShutdown: boolean
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parameters for constructing a Store instance.
|
|
181
|
+
*
|
|
182
|
+
* @internal This type is used by the Store constructor and is not part of the public API.
|
|
183
|
+
* For creating stores, use `createStore()` or `StoreRegistry` instead.
|
|
184
|
+
*/
|
|
185
|
+
export type StoreConstructorParams<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> = {
|
|
52
186
|
clientSession: ClientSession
|
|
53
187
|
schema: TSchema
|
|
54
188
|
storeId: string
|
|
@@ -62,6 +196,7 @@ export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any,
|
|
|
62
196
|
batchUpdates: (runUpdates: () => void) => void
|
|
63
197
|
params: {
|
|
64
198
|
leaderPushBatchSize: number
|
|
199
|
+
eventQueryBatchSize?: number
|
|
65
200
|
simulation?: {
|
|
66
201
|
clientSessionSyncProcessor: typeof ClientSessionSyncProcessorSimulationParams.Type
|
|
67
202
|
}
|
|
@@ -69,12 +204,18 @@ export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any,
|
|
|
69
204
|
__runningInDevtools: boolean
|
|
70
205
|
}
|
|
71
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Tagged union describing why a reactive refresh occurred.
|
|
209
|
+
*
|
|
210
|
+
* Used internally for debugging and devtools to trace the cause of query re-evaluations.
|
|
211
|
+
* Each variant includes context about what triggered the refresh.
|
|
212
|
+
*/
|
|
72
213
|
export type RefreshReason =
|
|
73
214
|
| DebugRefreshReasonBase
|
|
74
215
|
| {
|
|
75
216
|
_tag: 'commit'
|
|
76
217
|
/** The events that were applied */
|
|
77
|
-
events: ReadonlyArray<LiveStoreEvent.
|
|
218
|
+
events: ReadonlyArray<LiveStoreEvent.Client.Decoded | LiveStoreEvent.Input.Decoded>
|
|
78
219
|
|
|
79
220
|
/** The tables that were written to by the event */
|
|
80
221
|
writeTables: ReadonlyArray<string>
|
|
@@ -90,10 +231,19 @@ export type RefreshReason =
|
|
|
90
231
|
| { _tag: 'subscribe.update'; label?: string }
|
|
91
232
|
| { _tag: 'manual'; label?: string }
|
|
92
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Debug information captured for each query execution.
|
|
236
|
+
*
|
|
237
|
+
* Used by devtools and performance monitoring to track query behavior.
|
|
238
|
+
*/
|
|
93
239
|
export type QueryDebugInfo = {
|
|
240
|
+
/** Query type discriminator ('db', 'computed', etc.) */
|
|
94
241
|
_tag: string
|
|
242
|
+
/** Human-readable query label */
|
|
95
243
|
label: string
|
|
244
|
+
/** SQL query string or computed function representation */
|
|
96
245
|
query: string
|
|
246
|
+
/** Execution time in milliseconds */
|
|
97
247
|
durationMs: number
|
|
98
248
|
}
|
|
99
249
|
|
|
@@ -111,27 +261,197 @@ export type StoreCommitOptions = {
|
|
|
111
261
|
otelContext?: otel.Context
|
|
112
262
|
}
|
|
113
263
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
264
|
+
/**
|
|
265
|
+
* filter: Narrowed to the store's event types
|
|
266
|
+
* includeClientOnly: Omitted from public API until supported
|
|
267
|
+
*/
|
|
268
|
+
export type StoreEventsOptions<TSchema extends LiveStoreSchema> = Omit<
|
|
269
|
+
StreamEventsOptions,
|
|
270
|
+
'filter' | 'includeClientOnly'
|
|
271
|
+
> & {
|
|
120
272
|
/**
|
|
121
|
-
* Only include events of the given names
|
|
273
|
+
* Only include events of the given names.
|
|
122
274
|
* @default undefined (include all)
|
|
123
275
|
*/
|
|
124
276
|
filter?: ReadonlyArray<keyof TSchema['_EventDefMapType']>
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Function returned by `store.subscribe()` to stop receiving updates.
|
|
281
|
+
*
|
|
282
|
+
* Call this to unsubscribe from a query and release the associated resources.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* const unsubscribe = store.subscribe(todos$, (todos) => console.log(todos))
|
|
287
|
+
* // Later...
|
|
288
|
+
* unsubscribe()
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
export type Unsubscribe = () => void
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Options for `store.subscribe()`.
|
|
295
|
+
*
|
|
296
|
+
* @typeParam TResult - The result type of the subscribed query
|
|
297
|
+
*/
|
|
298
|
+
export type SubscribeOptions<TResult> = {
|
|
299
|
+
/** Callback invoked when the subscription is established (receives the live query instance) */
|
|
300
|
+
onSubscribe?: (query$: LiveQuery<TResult>) => void
|
|
301
|
+
/** Callback invoked when the subscription is terminated */
|
|
302
|
+
onUnsubsubscribe?: () => void
|
|
303
|
+
/** Label for debugging and devtools */
|
|
304
|
+
label?: string
|
|
305
|
+
/** If true, skips invoking the callback for the initial value */
|
|
306
|
+
skipInitialRun?: boolean
|
|
307
|
+
/** OpenTelemetry context for tracing */
|
|
308
|
+
otelContext?: otel.Context
|
|
309
|
+
/** Stack trace info for debugging subscription origins */
|
|
310
|
+
stackInfo?: StackInfo
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** All query definitions or instances the store can execute or subscribe to. */
|
|
314
|
+
export type Queryable<TResult> =
|
|
315
|
+
| LiveQueryDef<TResult>
|
|
316
|
+
| SignalDef<TResult>
|
|
317
|
+
| LiveQuery<TResult>
|
|
318
|
+
| QueryBuilder<TResult, any, any>
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Helper types for `Queryable`.
|
|
322
|
+
*
|
|
323
|
+
* Provides type-level utilities to work with `Queryable` values.
|
|
324
|
+
*/
|
|
325
|
+
export namespace Queryable {
|
|
326
|
+
/**
|
|
327
|
+
* Extracts the result type from a `Queryable`.
|
|
328
|
+
*
|
|
329
|
+
* Example:
|
|
330
|
+
* - `Queryable.Result<LiveQueryDef<number>>` → `number`
|
|
331
|
+
* - `Queryable.Result<SignalDef<string>>` → `string`
|
|
332
|
+
* - `Queryable.Result<LiveQuery<{ id: string }>>` → `{ id: string }`
|
|
333
|
+
* - `Queryable.Result<LiveQueryDef<A> | SignalDef<B>>` → `A | B`
|
|
334
|
+
*/
|
|
335
|
+
export type Result<TQueryable extends Queryable<any>> = TQueryable extends Queryable<infer TResult> ? TResult : never
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Type guard that checks if a value is a query or signal definition.
|
|
340
|
+
*
|
|
341
|
+
* Use this to distinguish between definitions (blueprints) and instances (live queries).
|
|
342
|
+
* Definitions are created by `queryDb()`, `computed()`, and `signal()`.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```ts
|
|
346
|
+
* const todos$ = queryDb(tables.todos.all())
|
|
347
|
+
*
|
|
348
|
+
* if (isLiveQueryDef(todos$)) {
|
|
349
|
+
* console.log('This is a definition:', todos$.label)
|
|
350
|
+
* }
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
export const isLiveQueryDef = (value: unknown): value is LiveQueryDef<any> | SignalDef<any> => {
|
|
354
|
+
if (typeof value !== 'object' || value === null) {
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (!('_tag' in value)) {
|
|
359
|
+
return false
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const tag = (value as LiveQueryDef<any> | SignalDef<any>)._tag
|
|
363
|
+
if (tag !== 'def' && tag !== 'signal-def') {
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const candidate = value as LiveQueryDef<any>
|
|
368
|
+
if (typeof candidate.make !== 'function') {
|
|
369
|
+
// The store calls make() to turn the definition into a live query instance.
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (typeof candidate.hash !== 'string' || typeof candidate.label !== 'string') {
|
|
374
|
+
// Both identifiers must be present so the store can cache and log the query.
|
|
375
|
+
return false
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return true
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Type guard that checks if a value is a live query instance.
|
|
383
|
+
*
|
|
384
|
+
* Live query instances are stateful objects bound to a Store's reactivity graph.
|
|
385
|
+
* They're created internally when you use a definition with `store.query()` or `store.subscribe()`.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```ts
|
|
389
|
+
* const [, , , query$] = useClientDocument(tables.uiState)
|
|
390
|
+
*
|
|
391
|
+
* if (isLiveQueryInstance(query$)) {
|
|
392
|
+
* console.log('Execution count:', query$.runs)
|
|
393
|
+
* }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
export const isLiveQueryInstance = (value: unknown): value is LiveQuery<any> => Predicate.hasProperty(value, TypeId)
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Type guard that checks if a value can be used with `store.query()` or `store.subscribe()`.
|
|
400
|
+
*
|
|
401
|
+
* Queryable values include:
|
|
402
|
+
* - Query definitions (`LiveQueryDef` from `queryDb()`, `computed()`)
|
|
403
|
+
* - Signal definitions (`SignalDef` from `signal()`)
|
|
404
|
+
* - Live query instances (`LiveQuery`)
|
|
405
|
+
* - Query builders (e.g., `tables.todos.where(...)`)
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```ts
|
|
409
|
+
* const handleQuery = (input: unknown) => {
|
|
410
|
+
* if (isQueryable(input)) {
|
|
411
|
+
* return store.query(input)
|
|
412
|
+
* }
|
|
413
|
+
* throw new Error('Not a valid query')
|
|
414
|
+
* }
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
export const isQueryable = (value: unknown): value is Queryable<unknown> =>
|
|
418
|
+
isQueryBuilder(value) || isLiveQueryInstance(value) || isLiveQueryDef(value)
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Represents the current synchronization status of the store.
|
|
422
|
+
*
|
|
423
|
+
* This provides visibility into the sync state between the client session
|
|
424
|
+
* and the leader thread, allowing applications to show sync indicators
|
|
425
|
+
* or determine backend health.
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* const status = store.syncStatus()
|
|
430
|
+
* if (status.isSynced) {
|
|
431
|
+
* console.log('All changes synced')
|
|
432
|
+
* } else {
|
|
433
|
+
* console.log(`${status.pendingCount} events pending sync`)
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
export type SyncStatus = {
|
|
438
|
+
/**
|
|
439
|
+
* The local head sequence number (most recent event in the client session).
|
|
440
|
+
* Represented as a string in the format "e{global}.{client}" (e.g., "e5.2").
|
|
441
|
+
*/
|
|
442
|
+
localHead: string
|
|
125
443
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
444
|
+
* The upstream head sequence number (what the leader thread has confirmed).
|
|
445
|
+
* Represented as a string in the format "e{global}" (e.g., "e3").
|
|
128
446
|
*/
|
|
129
|
-
|
|
447
|
+
upstreamHead: string
|
|
130
448
|
/**
|
|
131
|
-
*
|
|
132
|
-
* @default false
|
|
449
|
+
* Number of events pending synchronization to the leader thread.
|
|
133
450
|
*/
|
|
134
|
-
|
|
451
|
+
pendingCount: number
|
|
452
|
+
/**
|
|
453
|
+
* Whether the client session is fully synced with the leader thread.
|
|
454
|
+
* True when there are no pending events (pendingCount === 0).
|
|
455
|
+
*/
|
|
456
|
+
isSynced: boolean
|
|
135
457
|
}
|
|
136
|
-
|
|
137
|
-
export type Unsubscribe = () => void
|