@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.
- 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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as otel from '@opentelemetry/api'
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
type Adapter,
|
|
3
5
|
type BootStatus,
|
|
@@ -5,13 +7,13 @@ import {
|
|
|
5
7
|
type ClientSessionDevtoolsChannel,
|
|
6
8
|
type ClientSessionSyncProcessorSimulationParams,
|
|
7
9
|
type IntentionalShutdownCause,
|
|
8
|
-
|
|
9
|
-
type IsOfflineError,
|
|
10
|
+
LogConfig,
|
|
10
11
|
type MaterializeError,
|
|
12
|
+
type BackendIdMismatchError,
|
|
11
13
|
type MigrationsReport,
|
|
12
14
|
provideOtel,
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
+
type ServerAheadError,
|
|
16
|
+
UnknownError,
|
|
15
17
|
} from '@livestore/common'
|
|
16
18
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
17
19
|
import { isDevEnv, LS_DEV, omitUndefineds } from '@livestore/utils'
|
|
@@ -23,30 +25,45 @@ import {
|
|
|
23
25
|
Fiber,
|
|
24
26
|
identity,
|
|
25
27
|
Layer,
|
|
26
|
-
Logger,
|
|
27
|
-
LogLevel,
|
|
28
28
|
OtelTracer,
|
|
29
29
|
Queue,
|
|
30
30
|
Runtime,
|
|
31
|
-
|
|
31
|
+
Schema,
|
|
32
32
|
Scope,
|
|
33
33
|
TaskTracing,
|
|
34
34
|
} from '@livestore/utils/effect'
|
|
35
35
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
36
|
-
import * as otel from '@opentelemetry/api'
|
|
37
36
|
|
|
38
37
|
import { connectDevtoolsToStore } from './devtools.ts'
|
|
39
|
-
import { Store } from './store.ts'
|
|
40
38
|
import type {
|
|
41
39
|
LiveStoreContextRunning as LiveStoreContextRunning_,
|
|
42
40
|
OtelOptions,
|
|
43
41
|
ShutdownDeferred,
|
|
44
42
|
} from './store-types.ts'
|
|
43
|
+
import { StoreInternalsSymbol } from './store-types.ts'
|
|
44
|
+
import { STORE_DEFAULT_PARAMS, Store } from './store.ts'
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
declare global {
|
|
47
|
+
/** Store instances for console debugging */
|
|
48
|
+
var __debugLiveStore: Record<string, Store<any, any>> | undefined
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Use `makeStoreContext()` from `@livestore/livestore/effect` instead.
|
|
53
|
+
* This service doesn't preserve schema types. See the Effect integration docs for migration.
|
|
54
|
+
*
|
|
55
|
+
* @example Migration
|
|
56
|
+
* ```ts
|
|
57
|
+
* // Before (untyped)
|
|
58
|
+
* import { LiveStoreContextRunning } from '@livestore/livestore/effect'
|
|
59
|
+
* const { store } = yield* LiveStoreContextRunning
|
|
60
|
+
*
|
|
61
|
+
* // After (typed)
|
|
62
|
+
* import { makeStoreContext } from '@livestore/livestore/effect'
|
|
63
|
+
* const AppStore = makeStoreContext<typeof schema>()('app')
|
|
64
|
+
* const { store } = yield* AppStore.Tag
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
50
67
|
export class LiveStoreContextRunning extends Context.Tag('@livestore/livestore/effect/LiveStoreContextRunning')<
|
|
51
68
|
LiveStoreContextRunning,
|
|
52
69
|
LiveStoreContextRunning_
|
|
@@ -58,12 +75,19 @@ export class LiveStoreContextRunning extends Context.Tag('@livestore/livestore/e
|
|
|
58
75
|
}).pipe(Layer.unwrapScoped)
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Use `StoreContext.DeferredTag` from `makeStoreContext()` instead.
|
|
80
|
+
*/
|
|
61
81
|
export class DeferredStoreContext extends Context.Tag('@livestore/livestore/effect/DeferredStoreContext')<
|
|
62
82
|
DeferredStoreContext,
|
|
63
|
-
Deferred.Deferred<LiveStoreContextRunning['Type'],
|
|
83
|
+
Deferred.Deferred<LiveStoreContextRunning['Type'], UnknownError>
|
|
64
84
|
>() {}
|
|
65
85
|
|
|
66
|
-
export type LiveStoreContextProps<
|
|
86
|
+
export type LiveStoreContextProps<
|
|
87
|
+
TSchema extends LiveStoreSchema,
|
|
88
|
+
TContext = {},
|
|
89
|
+
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
90
|
+
> = {
|
|
67
91
|
schema: TSchema
|
|
68
92
|
/**
|
|
69
93
|
* The `storeId` can be used to isolate multiple stores from each other.
|
|
@@ -88,12 +112,47 @@ export type LiveStoreContextProps<TSchema extends LiveStoreSchema, TContext = {}
|
|
|
88
112
|
disableDevtools?: boolean | 'auto'
|
|
89
113
|
onBootStatus?: (status: BootStatus) => void
|
|
90
114
|
batchUpdates: (run: () => void) => void
|
|
115
|
+
/**
|
|
116
|
+
* Schema describing the shape of the sync payload and used to encode it.
|
|
117
|
+
*
|
|
118
|
+
* - If omitted, `Schema.JsonValue` is used (no additional typing/validation).
|
|
119
|
+
* - Prefer exporting a schema from your app (e.g. `export const SyncPayload = Schema.Struct({ authToken: Schema.String })`)
|
|
120
|
+
* and pass it here to get end-to-end type safety and validation.
|
|
121
|
+
*/
|
|
122
|
+
syncPayloadSchema?: TSyncPayloadSchema
|
|
123
|
+
/**
|
|
124
|
+
* Payload that is sent to the sync backend when connecting
|
|
125
|
+
*
|
|
126
|
+
* - Its TypeScript type is inferred from `syncPayloadSchema` (i.e. `typeof SyncPayload.Type`).
|
|
127
|
+
* - At runtime this value is encoded with `syncPayloadSchema` before being handed to the adapter.
|
|
128
|
+
*
|
|
129
|
+
* Example:
|
|
130
|
+
* const SyncPayload = Schema.Struct({ authToken: Schema.String })
|
|
131
|
+
* useStore({ ..., syncPayloadSchema: SyncPayload, syncPayload: { authToken: '...' } })
|
|
132
|
+
*/
|
|
133
|
+
syncPayload?: Schema.Schema.Type<TSyncPayloadSchema>
|
|
91
134
|
}
|
|
92
135
|
|
|
93
|
-
export interface CreateStoreOptions<
|
|
136
|
+
export interface CreateStoreOptions<
|
|
137
|
+
TSchema extends LiveStoreSchema,
|
|
138
|
+
TContext = {},
|
|
139
|
+
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
140
|
+
> extends LogConfig.WithLoggerOptions {
|
|
141
|
+
/** The LiveStore schema defining tables, events, and materializers. */
|
|
94
142
|
schema: TSchema
|
|
143
|
+
/** Adapter used for data storage and synchronization. */
|
|
95
144
|
adapter: Adapter
|
|
145
|
+
/**
|
|
146
|
+
* Unique identifier for the Store instance, stable for its lifetime.
|
|
147
|
+
*
|
|
148
|
+
* - **Valid characters**: Only alphanumeric characters, underscores (`_`), and hyphens (`-`)
|
|
149
|
+
* are allowed. Must match `/^[a-zA-Z0-9_-]+$/`.
|
|
150
|
+
* - **Globally unique**: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
|
|
151
|
+
* - **Use namespaces**: Prefix to avoid collisions and for easier identification when debugging
|
|
152
|
+
* (e.g., `app-root`, `workspace-abc123`, `issue-456`)
|
|
153
|
+
*/
|
|
96
154
|
storeId: string
|
|
155
|
+
/** User-defined context that will be attached to the created Store (e.g. for dependency injection). */
|
|
97
156
|
context?: TContext
|
|
98
157
|
boot?: (
|
|
99
158
|
store: Store<TSchema, TContext>,
|
|
@@ -102,6 +161,19 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
|
|
|
102
161
|
parentSpan: otel.Span
|
|
103
162
|
},
|
|
104
163
|
) => Effect.SyncOrPromiseOrEffect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
|
|
164
|
+
onBootStatus?: (status: BootStatus) => void
|
|
165
|
+
/**
|
|
166
|
+
* Needed in React so LiveStore can apply multiple events in a single render.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* // With React DOM
|
|
171
|
+
* import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
|
|
172
|
+
*
|
|
173
|
+
* // With React Native
|
|
174
|
+
* import { unstable_batchedUpdates as batchUpdates } from 'react-native'
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
105
177
|
batchUpdates?: (run: () => void) => void
|
|
106
178
|
/**
|
|
107
179
|
* Whether to disable devtools.
|
|
@@ -109,7 +181,6 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
|
|
|
109
181
|
* @default 'auto'
|
|
110
182
|
*/
|
|
111
183
|
disableDevtools?: boolean | 'auto'
|
|
112
|
-
onBootStatus?: (status: BootStatus) => void
|
|
113
184
|
shutdownDeferred?: ShutdownDeferred
|
|
114
185
|
/**
|
|
115
186
|
* Currently only used in the web adapter:
|
|
@@ -119,13 +190,29 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
|
|
|
119
190
|
*/
|
|
120
191
|
confirmUnsavedChanges?: boolean
|
|
121
192
|
/**
|
|
122
|
-
*
|
|
193
|
+
* Schema describing the shape of the sync payload and used to encode it.
|
|
194
|
+
*
|
|
195
|
+
* - If omitted, `Schema.JsonValue` is used (no additional typing/validation).
|
|
196
|
+
* - Prefer exporting a schema from your app (e.g. `export const SyncPayload = Schema.Struct({ authToken: Schema.String })`)
|
|
197
|
+
* and pass it here to get end-to-end type safety and validation.
|
|
198
|
+
*/
|
|
199
|
+
syncPayloadSchema?: TSyncPayloadSchema
|
|
200
|
+
/**
|
|
201
|
+
* Payload that is sent to the sync backend when connecting
|
|
202
|
+
*
|
|
203
|
+
* - Its TypeScript type is inferred from `syncPayloadSchema` (i.e. `typeof SyncPayload.Type`).
|
|
204
|
+
* - At runtime this value is encoded with `syncPayloadSchema` and carried through the adapter
|
|
205
|
+
* to the backend where it can be decoded with the same schema.
|
|
123
206
|
*
|
|
124
207
|
* @default undefined
|
|
125
208
|
*/
|
|
126
|
-
syncPayload?: Schema.
|
|
209
|
+
syncPayload?: Schema.Schema.Type<TSyncPayloadSchema>
|
|
210
|
+
/** Options provided to the Store constructor. */
|
|
127
211
|
params?: {
|
|
212
|
+
/** Max events pushed to the leader per write batch. */
|
|
128
213
|
leaderPushBatchSize?: number
|
|
214
|
+
/** Chunk size used when the stream replays confirmed events. */
|
|
215
|
+
eventQueryBatchSize?: number
|
|
129
216
|
simulation?: {
|
|
130
217
|
clientSessionSyncProcessor: typeof ClientSessionSyncProcessorSimulationParams.Type
|
|
131
218
|
}
|
|
@@ -135,15 +222,25 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
|
|
|
135
222
|
}
|
|
136
223
|
}
|
|
137
224
|
|
|
225
|
+
export type CreateStoreOptionsPromise<
|
|
226
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema.Any,
|
|
227
|
+
TContext = {},
|
|
228
|
+
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
229
|
+
> = CreateStoreOptions<TSchema, TContext, TSyncPayloadSchema> & {
|
|
230
|
+
signal?: AbortSignal
|
|
231
|
+
otelOptions?: Partial<OtelOptions>
|
|
232
|
+
}
|
|
233
|
+
|
|
138
234
|
/** Create a new LiveStore Store */
|
|
139
|
-
export const createStorePromise = async <
|
|
235
|
+
export const createStorePromise = async <
|
|
236
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema.Any,
|
|
237
|
+
TContext = {},
|
|
238
|
+
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
239
|
+
>({
|
|
140
240
|
signal,
|
|
141
241
|
otelOptions,
|
|
142
242
|
...options
|
|
143
|
-
}:
|
|
144
|
-
signal?: AbortSignal
|
|
145
|
-
otelOptions?: Partial<OtelOptions>
|
|
146
|
-
}): Promise<Store<TSchema, TContext>> =>
|
|
243
|
+
}: CreateStoreOptionsPromise<TSchema, TContext, TSyncPayloadSchema>): Promise<Store<TSchema, TContext>> =>
|
|
147
244
|
Effect.gen(function* () {
|
|
148
245
|
const scope = yield* Scope.make()
|
|
149
246
|
const runtime = yield* Effect.runtime()
|
|
@@ -162,12 +259,15 @@ export const createStorePromise = async <TSchema extends LiveStoreSchema = LiveS
|
|
|
162
259
|
provideOtel(omitUndefineds({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer })),
|
|
163
260
|
Effect.tapCauseLogPretty,
|
|
164
261
|
Effect.annotateLogs({ thread: 'window' }),
|
|
165
|
-
|
|
166
|
-
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
262
|
+
LogConfig.withLoggerConfig(options, { threadName: 'window' }),
|
|
167
263
|
Effect.runPromise,
|
|
168
264
|
)
|
|
169
265
|
|
|
170
|
-
export const createStore = <
|
|
266
|
+
export const createStore = <
|
|
267
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema.Any,
|
|
268
|
+
TContext = {},
|
|
269
|
+
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
270
|
+
>({
|
|
171
271
|
schema,
|
|
172
272
|
adapter,
|
|
173
273
|
storeId,
|
|
@@ -181,9 +281,10 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
181
281
|
debug,
|
|
182
282
|
confirmUnsavedChanges = true,
|
|
183
283
|
syncPayload,
|
|
184
|
-
|
|
284
|
+
syncPayloadSchema,
|
|
285
|
+
}: CreateStoreOptions<TSchema, TContext, TSyncPayloadSchema>): Effect.Effect<
|
|
185
286
|
Store<TSchema, TContext>,
|
|
186
|
-
|
|
287
|
+
UnknownError,
|
|
187
288
|
Scope.Scope | OtelTracer.OtelTracer
|
|
188
289
|
> =>
|
|
189
290
|
Effect.gen(function* () {
|
|
@@ -194,6 +295,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
194
295
|
yield* Effect.addFinalizer((_) => Scope.close(lifetimeScope, _))
|
|
195
296
|
|
|
196
297
|
const debugInstanceId = debug?.instanceId ?? nanoid(10)
|
|
298
|
+
const resolvedSyncPayloadSchema = (syncPayloadSchema ?? Schema.JsonValue) as TSyncPayloadSchema
|
|
197
299
|
|
|
198
300
|
return yield* Effect.gen(function* () {
|
|
199
301
|
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
@@ -223,7 +325,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
223
325
|
const shutdown = (
|
|
224
326
|
exit: Exit.Exit<
|
|
225
327
|
IntentionalShutdownCause,
|
|
226
|
-
|
|
328
|
+
UnknownError | MaterializeError | BackendIdMismatchError
|
|
227
329
|
>,
|
|
228
330
|
) =>
|
|
229
331
|
Effect.gen(function* () {
|
|
@@ -235,7 +337,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
235
337
|
),
|
|
236
338
|
)
|
|
237
339
|
|
|
238
|
-
if (shutdownDeferred) {
|
|
340
|
+
if (shutdownDeferred !== undefined) {
|
|
239
341
|
yield* Deferred.done(shutdownDeferred, exit)
|
|
240
342
|
}
|
|
241
343
|
|
|
@@ -250,6 +352,11 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
250
352
|
Fiber.join,
|
|
251
353
|
)
|
|
252
354
|
|
|
355
|
+
const syncPayloadEncoded =
|
|
356
|
+
syncPayload === undefined
|
|
357
|
+
? undefined
|
|
358
|
+
: yield* Schema.encode(resolvedSyncPayloadSchema)(syncPayload).pipe(UnknownError.mapToUnknownError)
|
|
359
|
+
|
|
253
360
|
const clientSession: ClientSession = yield* adapter({
|
|
254
361
|
schema,
|
|
255
362
|
storeId,
|
|
@@ -258,10 +365,11 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
258
365
|
shutdown,
|
|
259
366
|
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
260
367
|
debugInstanceId,
|
|
261
|
-
|
|
368
|
+
syncPayloadSchema: resolvedSyncPayloadSchema,
|
|
369
|
+
syncPayloadEncoded,
|
|
262
370
|
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
263
371
|
|
|
264
|
-
if (LS_DEV && clientSession.leaderThread.initialState.migrationsReport.migrations.length > 0) {
|
|
372
|
+
if (LS_DEV === true && clientSession.leaderThread.initialState.migrationsReport.migrations.length > 0) {
|
|
265
373
|
yield* Effect.logDebug(
|
|
266
374
|
'[@livestore/livestore:createStore] migrationsReport',
|
|
267
375
|
...clientSession.leaderThread.initialState.migrationsReport.migrations.map((m) =>
|
|
@@ -280,27 +388,28 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
280
388
|
effectContext: { lifetimeScope, runtime },
|
|
281
389
|
// TODO find a better way to detect if we're running LiveStore in the LiveStore devtools
|
|
282
390
|
// But for now this is a good enough approximation with little downsides
|
|
283
|
-
__runningInDevtools: getDevtoolsEnabled(disableDevtools)
|
|
391
|
+
__runningInDevtools: ! getDevtoolsEnabled(disableDevtools),
|
|
284
392
|
confirmUnsavedChanges,
|
|
285
393
|
// NOTE during boot we're not yet executing events in a batched context
|
|
286
394
|
// but only set the provided `batchUpdates` function after boot
|
|
287
395
|
batchUpdates: (run) => run(),
|
|
288
396
|
storeId,
|
|
289
397
|
params: {
|
|
290
|
-
leaderPushBatchSize: params?.leaderPushBatchSize ??
|
|
398
|
+
leaderPushBatchSize: params?.leaderPushBatchSize ?? STORE_DEFAULT_PARAMS.leaderPushBatchSize,
|
|
399
|
+
eventQueryBatchSize: params?.eventQueryBatchSize ?? STORE_DEFAULT_PARAMS.eventQueryBatchSize,
|
|
291
400
|
...omitUndefineds({ simulation: params?.simulation }),
|
|
292
401
|
},
|
|
293
402
|
})
|
|
294
403
|
|
|
295
404
|
// Starts background fibers (syncing, event processing, etc) for store
|
|
296
|
-
yield* store.boot
|
|
405
|
+
yield* store[StoreInternalsSymbol].boot
|
|
297
406
|
|
|
298
407
|
if (boot !== undefined) {
|
|
299
408
|
// TODO also incorporate `boot` function progress into `bootStatusQueue`
|
|
300
409
|
yield* Effect.tryAll(() =>
|
|
301
410
|
boot(store, { migrationsReport: clientSession.leaderThread.initialState.migrationsReport, parentSpan: span }),
|
|
302
411
|
).pipe(
|
|
303
|
-
|
|
412
|
+
UnknownError.mapToUnknownError,
|
|
304
413
|
Effect.provide(Layer.succeed(LiveStoreContextRunning, { stage: 'running', store: store as any as Store })),
|
|
305
414
|
Effect.withSpan('createStore:boot'),
|
|
306
415
|
)
|
|
@@ -311,16 +420,26 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
|
311
420
|
|
|
312
421
|
if (batchUpdates !== undefined) {
|
|
313
422
|
// Replacing the default batchUpdates function with the provided one after boot
|
|
314
|
-
store.reactivityGraph.context!.effectsWrapper = batchUpdates
|
|
423
|
+
store[StoreInternalsSymbol].reactivityGraph.context!.effectsWrapper = batchUpdates
|
|
315
424
|
}
|
|
316
425
|
|
|
317
426
|
yield* Deferred.succeed(storeDeferred, store as any as Store)
|
|
318
427
|
|
|
428
|
+
// Expose store on globalThis for console debugging
|
|
429
|
+
globalThis.__debugLiveStore ??= {}
|
|
430
|
+
globalThis.__debugLiveStore[storeId] = store
|
|
431
|
+
|
|
432
|
+
yield* Effect.addFinalizer(() =>
|
|
433
|
+
Effect.sync(() => {
|
|
434
|
+
delete globalThis.__debugLiveStore?.[storeId]
|
|
435
|
+
}),
|
|
436
|
+
)
|
|
437
|
+
|
|
319
438
|
return store
|
|
320
439
|
}).pipe(
|
|
321
440
|
Effect.withSpan('createStore', { attributes: { debugInstanceId, storeId } }),
|
|
322
441
|
Effect.annotateLogs({ debugInstanceId, storeId }),
|
|
323
|
-
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
442
|
+
LS_DEV === true ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
324
443
|
Scope.extend(lifetimeScope),
|
|
325
444
|
)
|
|
326
445
|
})
|
|
@@ -329,8 +448,8 @@ const validateStoreId = (storeId: string) =>
|
|
|
329
448
|
Effect.gen(function* () {
|
|
330
449
|
const validChars = /^[a-zA-Z0-9_-]+$/
|
|
331
450
|
|
|
332
|
-
if (
|
|
333
|
-
return yield*
|
|
451
|
+
if (validChars.test(storeId) === false) {
|
|
452
|
+
return yield* UnknownError.make({
|
|
334
453
|
cause: `Invalid storeId: ${storeId}. Only alphanumeric characters, underscores, and hyphens are allowed.`,
|
|
335
454
|
payload: { storeId },
|
|
336
455
|
})
|
package/src/store/devtools.ts
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { DebugInfo, SyncState } from '@livestore/common'
|
|
2
|
+
import {
|
|
3
|
+
Devtools,
|
|
4
|
+
devtoolsProtocolVersion,
|
|
5
|
+
isDevtoolsProtocolVersionSupported,
|
|
6
|
+
liveStoreVersion,
|
|
7
|
+
resolveDevtoolsProtocolVersion,
|
|
8
|
+
UnknownError,
|
|
9
|
+
} from '@livestore/common'
|
|
3
10
|
import { throttle } from '@livestore/utils'
|
|
4
11
|
import type { WebChannel } from '@livestore/utils/effect'
|
|
5
12
|
import { Effect, Stream } from '@livestore/utils/effect'
|
|
6
13
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
7
14
|
|
|
8
|
-
import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.ts'
|
|
9
15
|
import { NOT_REFRESHED_YET } from '../reactive.ts'
|
|
10
|
-
import type { SqliteDbWrapper } from '../SqliteDbWrapper.ts'
|
|
11
16
|
import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.ts'
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
type IStore = {
|
|
15
|
-
clientSession: ClientSession
|
|
16
|
-
reactivityGraph: ReactivityGraph
|
|
17
|
-
sqliteDbWrapper: SqliteDbWrapper
|
|
18
|
-
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
19
|
-
syncProcessor: ClientSessionSyncProcessor
|
|
20
|
-
}
|
|
17
|
+
import { StoreInternalsSymbol } from './store-types.ts'
|
|
18
|
+
import type { Store } from './store.ts'
|
|
21
19
|
|
|
22
20
|
type Unsub = () => void
|
|
23
21
|
type RequestId = string
|
|
@@ -32,7 +30,7 @@ const requestNextTick: (cb: () => void) => number =
|
|
|
32
30
|
const cancelTick: (id: number) => void =
|
|
33
31
|
globalThis.cancelAnimationFrame === undefined ? (id: number) => clearTimeout(id) : globalThis.cancelAnimationFrame
|
|
34
32
|
|
|
35
|
-
export const connectDevtoolsToStore = ({
|
|
33
|
+
export const connectDevtoolsToStore = Effect.fn('LSD.devtools.connectStoreToDevtools')(function* ({
|
|
36
34
|
storeDevtoolsChannel,
|
|
37
35
|
store,
|
|
38
36
|
}: {
|
|
@@ -40,15 +38,14 @@ export const connectDevtoolsToStore = ({
|
|
|
40
38
|
Devtools.ClientSession.MessageToApp,
|
|
41
39
|
Devtools.ClientSession.MessageFromApp
|
|
42
40
|
>
|
|
43
|
-
store:
|
|
44
|
-
})
|
|
45
|
-
Effect.gen(function* () {
|
|
41
|
+
store: Store
|
|
42
|
+
}) {
|
|
46
43
|
const reactivityGraphSubcriptions: SubMap = new Map()
|
|
47
44
|
const liveQueriesSubscriptions: SubMap = new Map()
|
|
48
45
|
const debugInfoHistorySubscriptions: SubMap = new Map()
|
|
49
46
|
const syncHeadClientSessionSubscriptions: SubMap = new Map()
|
|
50
47
|
|
|
51
|
-
const { clientId, sessionId } = store.clientSession
|
|
48
|
+
const { clientId, sessionId } = store[StoreInternalsSymbol].clientSession
|
|
52
49
|
|
|
53
50
|
yield* Effect.addFinalizer(() =>
|
|
54
51
|
Effect.sync(() => {
|
|
@@ -73,7 +70,21 @@ export const connectDevtoolsToStore = ({
|
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
if (decodedMessage._tag === 'LSD.ClientSession.Disconnect') {
|
|
76
|
-
//
|
|
73
|
+
// Gracefully tear down all DevTools subscriptions and close the channel.
|
|
74
|
+
// This prevents background fibers from lingering after DevTools closes
|
|
75
|
+
// (e.g. when a window is closed without sending explicit unsubs).
|
|
76
|
+
for (const unsub of reactivityGraphSubcriptions.values()) unsub()
|
|
77
|
+
reactivityGraphSubcriptions.clear()
|
|
78
|
+
for (const unsub of liveQueriesSubscriptions.values()) unsub()
|
|
79
|
+
liveQueriesSubscriptions.clear()
|
|
80
|
+
for (const unsub of debugInfoHistorySubscriptions.values()) unsub()
|
|
81
|
+
debugInfoHistorySubscriptions.clear()
|
|
82
|
+
for (const unsub of syncHeadClientSessionSubscriptions.values()) unsub()
|
|
83
|
+
syncHeadClientSessionSubscriptions.clear()
|
|
84
|
+
|
|
85
|
+
// Signal the WebChannel to shut down; this causes the `listen` stream
|
|
86
|
+
// to complete and allows the surrounding scoped fiber to exit.
|
|
87
|
+
storeDevtoolsChannel.shutdown.pipe(Effect.runFork)
|
|
77
88
|
return
|
|
78
89
|
}
|
|
79
90
|
|
|
@@ -83,7 +94,7 @@ export const connectDevtoolsToStore = ({
|
|
|
83
94
|
// So far I could only observe this problem with webmesh proxy channels (e.g. for Expo)
|
|
84
95
|
// Proof: https://share.cleanshot.com/V9G87B0B
|
|
85
96
|
// Also see `leader-worker-devtools.ts` for same problem
|
|
86
|
-
if (handledRequestIds.has(requestId)) {
|
|
97
|
+
if (handledRequestIds.has(requestId) === true) {
|
|
87
98
|
return
|
|
88
99
|
}
|
|
89
100
|
|
|
@@ -103,7 +114,7 @@ export const connectDevtoolsToStore = ({
|
|
|
103
114
|
() =>
|
|
104
115
|
sendToDevtools(
|
|
105
116
|
Devtools.ClientSession.ReactivityGraphRes.make({
|
|
106
|
-
reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
|
|
117
|
+
reactivityGraph: store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults }),
|
|
107
118
|
requestId: nanoid(10),
|
|
108
119
|
clientId,
|
|
109
120
|
sessionId,
|
|
@@ -121,14 +132,17 @@ export const connectDevtoolsToStore = ({
|
|
|
121
132
|
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
122
133
|
const throttledSend = throttle(send, 20)
|
|
123
134
|
|
|
124
|
-
reactivityGraphSubcriptions.set(
|
|
135
|
+
reactivityGraphSubcriptions.set(
|
|
136
|
+
subscriptionId,
|
|
137
|
+
store[StoreInternalsSymbol].reactivityGraph.subscribeToRefresh(throttledSend),
|
|
138
|
+
)
|
|
125
139
|
|
|
126
140
|
break
|
|
127
141
|
}
|
|
128
142
|
case 'LSD.ClientSession.DebugInfoReq': {
|
|
129
143
|
sendToDevtools(
|
|
130
144
|
Devtools.ClientSession.DebugInfoRes.make({
|
|
131
|
-
debugInfo: store.sqliteDbWrapper.debugInfo,
|
|
145
|
+
debugInfo: store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo,
|
|
132
146
|
requestId,
|
|
133
147
|
clientId,
|
|
134
148
|
sessionId,
|
|
@@ -144,13 +158,13 @@ export const connectDevtoolsToStore = ({
|
|
|
144
158
|
let tickHandle: number | undefined
|
|
145
159
|
|
|
146
160
|
const tick = () => {
|
|
147
|
-
buffer.push(store.sqliteDbWrapper.debugInfo)
|
|
161
|
+
buffer.push(store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo)
|
|
148
162
|
|
|
149
163
|
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
150
164
|
// will get the empty debug info
|
|
151
165
|
// TODO We need to come up with a more graceful way to do store. Probably via a single global
|
|
152
166
|
// `requestAnimationFrame` loop that is passed in somehow.
|
|
153
|
-
store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
167
|
+
store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
154
168
|
|
|
155
169
|
if (buffer.length > 10) {
|
|
156
170
|
sendToDevtools(
|
|
@@ -194,7 +208,7 @@ export const connectDevtoolsToStore = ({
|
|
|
194
208
|
break
|
|
195
209
|
}
|
|
196
210
|
case 'LSD.ClientSession.DebugInfoResetReq': {
|
|
197
|
-
store.sqliteDbWrapper.debugInfo.slowQueries.clear()
|
|
211
|
+
store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo.slowQueries.clear()
|
|
198
212
|
sendToDevtools(
|
|
199
213
|
Devtools.ClientSession.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
200
214
|
)
|
|
@@ -202,7 +216,10 @@ export const connectDevtoolsToStore = ({
|
|
|
202
216
|
}
|
|
203
217
|
case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
|
|
204
218
|
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
205
|
-
store.sqliteDbWrapper.cachedSelect(queryStr, bindValues, {
|
|
219
|
+
store[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(queryStr, bindValues, {
|
|
220
|
+
queriedTables,
|
|
221
|
+
skipCache: true,
|
|
222
|
+
})
|
|
206
223
|
sendToDevtools(
|
|
207
224
|
Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
208
225
|
)
|
|
@@ -223,7 +240,8 @@ export const connectDevtoolsToStore = ({
|
|
|
223
240
|
() =>
|
|
224
241
|
sendToDevtools(
|
|
225
242
|
Devtools.ClientSession.LiveQueriesRes.make({
|
|
226
|
-
liveQueries: [...store.activeQueries].map((q) => ({
|
|
243
|
+
liveQueries: [...store[StoreInternalsSymbol].activeQueries].map((q) => ({
|
|
244
|
+
/** TODO: include schema metadata for schema-aware rendering in devtools (e.g., schema AST/hash/identifier or table+columns for QueryBuilder-derived queries). */
|
|
227
245
|
_tag: q._tag,
|
|
228
246
|
id: q.id,
|
|
229
247
|
label: q.label,
|
|
@@ -251,7 +269,10 @@ export const connectDevtoolsToStore = ({
|
|
|
251
269
|
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
252
270
|
const throttledSend = throttle(send, 20)
|
|
253
271
|
|
|
254
|
-
liveQueriesSubscriptions.set(
|
|
272
|
+
liveQueriesSubscriptions.set(
|
|
273
|
+
subscriptionId,
|
|
274
|
+
store[StoreInternalsSymbol].reactivityGraph.subscribeToRefresh(throttledSend),
|
|
275
|
+
)
|
|
255
276
|
|
|
256
277
|
break
|
|
257
278
|
}
|
|
@@ -278,11 +299,11 @@ export const connectDevtoolsToStore = ({
|
|
|
278
299
|
}),
|
|
279
300
|
)
|
|
280
301
|
|
|
281
|
-
send(store.syncProcessor.syncState.pipe(Effect.runSync))
|
|
302
|
+
send(store[StoreInternalsSymbol].syncProcessor.syncState.pipe(Effect.runSync))
|
|
282
303
|
|
|
283
304
|
syncHeadClientSessionSubscriptions.set(
|
|
284
305
|
subscriptionId,
|
|
285
|
-
store.syncProcessor.syncState.changes.pipe(
|
|
306
|
+
store[StoreInternalsSymbol].syncProcessor.syncState.changes.pipe(
|
|
286
307
|
Stream.tap((syncState) => send(syncState)),
|
|
287
308
|
Stream.runDrain,
|
|
288
309
|
Effect.interruptible,
|
|
@@ -302,7 +323,30 @@ export const connectDevtoolsToStore = ({
|
|
|
302
323
|
break
|
|
303
324
|
}
|
|
304
325
|
case 'LSD.ClientSession.Ping': {
|
|
305
|
-
|
|
326
|
+
if (isDevtoolsProtocolVersionSupported(decodedMessage.devtoolsProtocolVersion) === false) {
|
|
327
|
+
sendToDevtools(
|
|
328
|
+
Devtools.ClientSession.VersionMismatch.make({
|
|
329
|
+
requestId,
|
|
330
|
+
clientId,
|
|
331
|
+
sessionId,
|
|
332
|
+
liveStoreVersion,
|
|
333
|
+
appVersion: liveStoreVersion,
|
|
334
|
+
receivedVersion: decodedMessage.liveStoreVersion,
|
|
335
|
+
appDevtoolsProtocolVersion: devtoolsProtocolVersion,
|
|
336
|
+
receivedDevtoolsProtocolVersion: resolveDevtoolsProtocolVersion(decodedMessage.devtoolsProtocolVersion),
|
|
337
|
+
}),
|
|
338
|
+
)
|
|
339
|
+
break
|
|
340
|
+
}
|
|
341
|
+
sendToDevtools(
|
|
342
|
+
Devtools.ClientSession.Pong.make({
|
|
343
|
+
requestId,
|
|
344
|
+
clientId,
|
|
345
|
+
sessionId,
|
|
346
|
+
liveStoreVersion,
|
|
347
|
+
devtoolsProtocolVersion,
|
|
348
|
+
}),
|
|
349
|
+
)
|
|
306
350
|
break
|
|
307
351
|
}
|
|
308
352
|
default: {
|
|
@@ -318,4 +362,4 @@ export const connectDevtoolsToStore = ({
|
|
|
318
362
|
Stream.runDrain,
|
|
319
363
|
Effect.withSpan('LSD.devtools.onMessage'),
|
|
320
364
|
)
|
|
321
|
-
|
|
365
|
+
}, UnknownError.mapToUnknownError)
|