@livestore/livestore 0.4.0-dev.8 → 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.
Files changed (129) 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 +8 -8
  8. package/dist/SqliteDbWrapper.js.map +1 -1
  9. package/dist/SqliteDbWrapper.test.js +4 -3
  10. package/dist/SqliteDbWrapper.test.js.map +1 -1
  11. package/dist/effect/LiveStore.d.ts +133 -5
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +187 -8
  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 +42 -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 +110 -7
  24. package/dist/live-queries/base-class.d.ts.map +1 -1
  25. package/dist/live-queries/base-class.js +2 -2
  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 +58 -2
  34. package/dist/live-queries/computed.js.map +1 -1
  35. package/dist/live-queries/db-query.d.ts.map +1 -1
  36. package/dist/live-queries/db-query.js +21 -19
  37. package/dist/live-queries/db-query.js.map +1 -1
  38. package/dist/live-queries/db-query.test.js +106 -23
  39. package/dist/live-queries/db-query.test.js.map +1 -1
  40. package/dist/live-queries/signal.d.ts +49 -0
  41. package/dist/live-queries/signal.d.ts.map +1 -1
  42. package/dist/live-queries/signal.js +49 -0
  43. package/dist/live-queries/signal.js.map +1 -1
  44. package/dist/live-queries/signal.test.js +2 -2
  45. package/dist/live-queries/signal.test.js.map +1 -1
  46. package/dist/mod.d.ts +3 -3
  47. package/dist/mod.d.ts.map +1 -1
  48. package/dist/mod.js +3 -2
  49. package/dist/mod.js.map +1 -1
  50. package/dist/reactive.d.ts +9 -9
  51. package/dist/reactive.d.ts.map +1 -1
  52. package/dist/reactive.js +9 -26
  53. package/dist/reactive.js.map +1 -1
  54. package/dist/reactive.test.js +2 -2
  55. package/dist/reactive.test.js.map +1 -1
  56. package/dist/store/StoreRegistry.d.ts +215 -0
  57. package/dist/store/StoreRegistry.d.ts.map +1 -0
  58. package/dist/store/StoreRegistry.js +267 -0
  59. package/dist/store/StoreRegistry.js.map +1 -0
  60. package/dist/store/StoreRegistry.test.d.ts +2 -0
  61. package/dist/store/StoreRegistry.test.d.ts.map +1 -0
  62. package/dist/store/StoreRegistry.test.js +381 -0
  63. package/dist/store/StoreRegistry.test.js.map +1 -0
  64. package/dist/store/create-store.d.ts +98 -18
  65. package/dist/store/create-store.d.ts.map +1 -1
  66. package/dist/store/create-store.js +49 -20
  67. package/dist/store/create-store.js.map +1 -1
  68. package/dist/store/devtools.d.ts +5 -16
  69. package/dist/store/devtools.d.ts.map +1 -1
  70. package/dist/store/devtools.js +59 -18
  71. package/dist/store/devtools.js.map +1 -1
  72. package/dist/store/store-eventstream.test.d.ts +2 -0
  73. package/dist/store/store-eventstream.test.d.ts.map +1 -0
  74. package/dist/store/store-eventstream.test.js +65 -0
  75. package/dist/store/store-eventstream.test.js.map +1 -0
  76. package/dist/store/store-types.d.ts +285 -27
  77. package/dist/store/store-types.d.ts.map +1 -1
  78. package/dist/store/store-types.js +77 -1
  79. package/dist/store/store-types.js.map +1 -1
  80. package/dist/store/store-types.test.d.ts +2 -0
  81. package/dist/store/store-types.test.d.ts.map +1 -0
  82. package/dist/store/store-types.test.js +39 -0
  83. package/dist/store/store-types.test.js.map +1 -0
  84. package/dist/store/store.d.ts +253 -66
  85. package/dist/store/store.d.ts.map +1 -1
  86. package/dist/store/store.js +442 -153
  87. package/dist/store/store.js.map +1 -1
  88. package/dist/utils/dev.d.ts.map +1 -1
  89. package/dist/utils/dev.js.map +1 -1
  90. package/dist/utils/stack-info.js +2 -2
  91. package/dist/utils/stack-info.js.map +1 -1
  92. package/dist/utils/tests/fixture.d.ts +20 -5
  93. package/dist/utils/tests/fixture.d.ts.map +1 -1
  94. package/dist/utils/tests/fixture.js +7 -0
  95. package/dist/utils/tests/fixture.js.map +1 -1
  96. package/dist/utils/tests/otel.d.ts.map +1 -1
  97. package/dist/utils/tests/otel.js +5 -5
  98. package/dist/utils/tests/otel.js.map +1 -1
  99. package/package.json +59 -17
  100. package/src/QueryCache.ts +1 -1
  101. package/src/SqliteDbWrapper.test.ts +5 -3
  102. package/src/SqliteDbWrapper.ts +12 -11
  103. package/src/ambient.d.ts +0 -7
  104. package/src/effect/LiveStore.test.ts +61 -0
  105. package/src/effect/LiveStore.ts +388 -13
  106. package/src/effect/mod.ts +13 -1
  107. package/src/live-queries/__snapshots__/db-query.test.ts.snap +604 -192
  108. package/src/live-queries/base-class.ts +126 -28
  109. package/src/live-queries/client-document-get-query.ts +6 -4
  110. package/src/live-queries/computed.ts +59 -2
  111. package/src/live-queries/db-query.test.ts +162 -24
  112. package/src/live-queries/db-query.ts +23 -20
  113. package/src/live-queries/signal.test.ts +3 -2
  114. package/src/live-queries/signal.ts +49 -0
  115. package/src/mod.ts +19 -2
  116. package/src/reactive.test.ts +3 -2
  117. package/src/reactive.ts +22 -23
  118. package/src/store/StoreRegistry.test.ts +540 -0
  119. package/src/store/StoreRegistry.ts +418 -0
  120. package/src/store/create-store.ts +158 -39
  121. package/src/store/devtools.ts +77 -33
  122. package/src/store/store-eventstream.test.ts +114 -0
  123. package/src/store/store-types.test.ts +52 -0
  124. package/src/store/store-types.ts +360 -40
  125. package/src/store/store.ts +571 -236
  126. package/src/utils/dev.ts +2 -3
  127. package/src/utils/stack-info.ts +2 -2
  128. package/src/utils/tests/fixture.ts +9 -1
  129. 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
+ })
@@ -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 type { DebugRefreshReasonBase } from '../reactive.ts'
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
- export type LiveStoreContext =
22
- | LiveStoreContextRunning
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: UnexpectedError | unknown
48
+ error: unknown
26
49
  }
27
50
  | {
28
51
  stage: 'shutdown'
29
- cause: IntentionalShutdownCause | StoreInterrupted | SyncError
52
+ cause: IntentionalShutdownCause | StoreInterrupted | UnknownError
30
53
  }
31
54
 
32
55
  export type ShutdownDeferred = Deferred.Deferred<
33
56
  IntentionalShutdownCause,
34
- UnexpectedError | SyncError | StoreInterrupted | MaterializeError | InvalidPullError | IsOfflineError
57
+ UnknownError | StoreInterrupted | MaterializeError | BackendIdMismatchError
35
58
  >
36
59
  export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
37
60
  IntentionalShutdownCause,
38
- UnexpectedError | SyncError | StoreInterrupted | MaterializeError | InvalidPullError | IsOfflineError
61
+ UnknownError | StoreInterrupted | MaterializeError | BackendIdMismatchError
39
62
  >()
40
63
 
41
- export type LiveStoreContextRunning = {
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 type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TContext = {}> = {
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.AnyDecoded | LiveStoreEvent.PartialAnyDecoded>
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
- export type StoreEventsOptions<TSchema extends LiveStoreSchema> = {
115
- /**
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
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
- * Whether to include client-only events or only return synced events
127
- * @default true
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
- includeClientOnly?: boolean
447
+ upstreamHead: string
130
448
  /**
131
- * Exclude own events that have not been pushed to the sync backend yet
132
- * @default false
449
+ * Number of events pending synchronization to the leader thread.
133
450
  */
134
- excludeUnpushed?: boolean
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