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