@livestore/livestore 0.4.0-dev.22 → 0.4.0-dev.23
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 +2 -2
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +14 -7
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +0 -15
- 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/live-queries/base-class.d.ts +3 -3
- 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 +1 -1
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +2 -2
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.js +14 -14
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +2 -2
- package/dist/live-queries/db-query.test.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 +1 -1
- package/dist/mod.d.ts.map +1 -1
- 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 +30 -5
- package/dist/store/StoreRegistry.d.ts.map +1 -1
- package/dist/store/StoreRegistry.js +54 -31
- package/dist/store/StoreRegistry.js.map +1 -1
- package/dist/store/StoreRegistry.test.js +251 -250
- package/dist/store/StoreRegistry.test.js.map +1 -1
- package/dist/store/create-store.d.ts +6 -2
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +13 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -1
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +3 -3
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.js +2 -2
- package/dist/store/store-eventstream.test.js.map +1 -1
- package/dist/store/store-types.d.ts +70 -5
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.js +1 -1
- package/dist/store/store-types.test.js.map +1 -1
- package/dist/store/store.d.ts +81 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +128 -45
- package/dist/store/store.js.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 +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- 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 +58 -17
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +4 -2
- 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 +17 -26
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
- package/src/live-queries/base-class.ts +7 -6
- package/src/live-queries/client-document-get-query.ts +4 -2
- package/src/live-queries/computed.ts +3 -2
- package/src/live-queries/db-query.test.ts +3 -2
- package/src/live-queries/db-query.ts +15 -15
- package/src/live-queries/signal.test.ts +3 -2
- package/src/mod.ts +1 -0
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +22 -23
- package/src/store/StoreRegistry.test.ts +317 -293
- package/src/store/StoreRegistry.ts +63 -38
- package/src/store/create-store.ts +26 -11
- package/src/store/devtools.ts +5 -6
- package/src/store/store-eventstream.test.ts +4 -2
- package/src/store/store-types.test.ts +3 -1
- package/src/store/store-types.ts +47 -8
- package/src/store/store.ts +172 -55
- package/src/utils/dev.ts +2 -2
- package/src/utils/stack-info.ts +2 -2
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +8 -7
- package/docs/api/index.md +0 -3
- package/docs/building-with-livestore/complex-ui-state/index.md +0 -3
- package/docs/building-with-livestore/crud/index.md +0 -3
- package/docs/building-with-livestore/data-modeling/index.md +0 -30
- package/docs/building-with-livestore/debugging/index.md +0 -17
- package/docs/building-with-livestore/devtools/index.md +0 -79
- package/docs/building-with-livestore/events/index.md +0 -355
- package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
- package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -885
- package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
- package/docs/building-with-livestore/opentelemetry/index.md +0 -227
- package/docs/building-with-livestore/production-checklist/index.md +0 -5
- package/docs/building-with-livestore/reactivity-system/index.md +0 -202
- package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
- package/docs/building-with-livestore/state/materializers/index.md +0 -300
- package/docs/building-with-livestore/state/sql-queries/index.md +0 -94
- package/docs/building-with-livestore/state/sqlite/index.md +0 -45
- package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
- package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
- package/docs/building-with-livestore/store/index.md +0 -625
- package/docs/building-with-livestore/syncing/index.md +0 -136
- package/docs/building-with-livestore/tools/cli/index.md +0 -177
- package/docs/building-with-livestore/tools/mcp/index.md +0 -187
- package/docs/examples/cloudflare-adapter/index.md +0 -44
- package/docs/examples/expo-adapter/index.md +0 -44
- package/docs/examples/index.md +0 -55
- package/docs/examples/node-adapter/index.md +0 -44
- package/docs/examples/web-adapter/index.md +0 -52
- package/docs/framework-integrations/custom-elements/index.md +0 -142
- package/docs/framework-integrations/react-integration/index.md +0 -937
- package/docs/framework-integrations/solid-integration/index.md +0 -293
- package/docs/framework-integrations/svelte-integration/index.md +0 -42
- package/docs/framework-integrations/vue-integration/index.md +0 -294
- package/docs/getting-started/expo/index.md +0 -882
- package/docs/getting-started/node/index.md +0 -115
- package/docs/getting-started/react-web/index.md +0 -626
- package/docs/getting-started/solid/index.md +0 -3
- package/docs/getting-started/vue/index.md +0 -471
- package/docs/index.md +0 -208
- package/docs/llms.txt +0 -146
- package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
- package/docs/misc/FAQ/index.md +0 -37
- package/docs/misc/community/index.md +0 -88
- package/docs/misc/credits/index.md +0 -14
- package/docs/misc/design-partners/index.md +0 -13
- package/docs/misc/package-management/index.md +0 -21
- package/docs/misc/performance/index.md +0 -25
- package/docs/misc/resources/index.md +0 -46
- package/docs/misc/state-of-the-project/index.md +0 -37
- package/docs/misc/troubleshooting/index.md +0 -82
- package/docs/overview/concepts/index.md +0 -78
- package/docs/overview/how-livestore-works/index.md +0 -56
- package/docs/overview/introduction/index.md +0 -413
- package/docs/overview/technology-comparison/index.md +0 -40
- package/docs/overview/when-livestore/index.md +0 -81
- package/docs/overview/why-livestore/index.md +0 -111
- package/docs/patterns/ai/index.md +0 -15
- package/docs/patterns/anonymous-user-transition/index.md +0 -10
- package/docs/patterns/app-evolution/index.md +0 -72
- package/docs/patterns/auth/index.md +0 -377
- package/docs/patterns/effect/index.md +0 -1505
- package/docs/patterns/encryption/index.md +0 -6
- package/docs/patterns/external-data/index.md +0 -5
- package/docs/patterns/file-management/index.md +0 -11
- package/docs/patterns/file-structure/index.md +0 -14
- package/docs/patterns/list-ordering/index.md +0 -369
- package/docs/patterns/offline/index.md +0 -32
- package/docs/patterns/orm/index.md +0 -18
- package/docs/patterns/presence/index.md +0 -11
- package/docs/patterns/rich-text-editing/index.md +0 -11
- package/docs/patterns/server-side-clients/index.md +0 -97
- package/docs/patterns/side-effects/index.md +0 -11
- package/docs/patterns/state-machines/index.md +0 -11
- package/docs/patterns/storybook/index.md +0 -209
- package/docs/patterns/undo-redo/index.md +0 -9
- package/docs/patterns/version-control/index.md +0 -8
- package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
- package/docs/platform-adapters/electron-adapter/index.md +0 -15
- package/docs/platform-adapters/expo-adapter/index.md +0 -262
- package/docs/platform-adapters/node-adapter/index.md +0 -160
- package/docs/platform-adapters/tauri-adapter/index.md +0 -15
- package/docs/platform-adapters/web-adapter/index.md +0 -287
- package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
- package/docs/sustainable-open-source/contributing/info/index.md +0 -63
- package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
- package/docs/sustainable-open-source/sponsoring/index.md +0 -104
- package/docs/sync-providers/cloudflare/index.md +0 -773
- package/docs/sync-providers/custom/index.md +0 -65
- package/docs/sync-providers/electricsql/index.md +0 -159
- package/docs/sync-providers/s2/index.md +0 -230
- package/docs/tutorial/0-welcome/index.md +0 -48
- package/docs/tutorial/1-setup-starter-project/index.md +0 -105
- package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
- package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -530
- package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
- package/docs/tutorial/5-expand-business-logic/index.md +0 -174
- package/docs/tutorial/6-persist-ui-state/index.md +0 -453
- package/docs/tutorial/7-next-steps/index.md +0 -22
- package/docs/understanding-livestore/design-decisions/index.md +0 -33
- package/docs/understanding-livestore/event-sourcing/index.md +0 -40
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
type Schema,
|
|
17
17
|
type Scope,
|
|
18
18
|
} from '@livestore/utils/effect'
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
import { createStore, type CreateStoreOptions } from './create-store.ts'
|
|
20
21
|
import type { Store } from './store.ts'
|
|
21
22
|
import type { OtelOptions } from './store-types.ts'
|
|
22
23
|
|
|
@@ -65,10 +66,10 @@ export interface RegistryStoreOptions<
|
|
|
65
66
|
* have unmounted.
|
|
66
67
|
*
|
|
67
68
|
* @remarks
|
|
68
|
-
* -
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* - Per-store values override the registry-level default (set via `StoreRegistry` constructor's
|
|
70
|
+
* `defaultOptions.unusedCacheTime`)
|
|
71
|
+
* - The value is fixed when the store is first loaded into the registry. If the same `storeId` is
|
|
72
|
+
* requested again with a different `unusedCacheTime`, the original value is kept.
|
|
72
73
|
* - If set to `Infinity`, will disable automatic disposal
|
|
73
74
|
* - The maximum allowed time is about {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#maximum_delay_value | 24 days}
|
|
74
75
|
*
|
|
@@ -155,15 +156,16 @@ export class StoreRegistry {
|
|
|
155
156
|
readonly #runtime: Runtime.Runtime<Scope.Scope | OtelTracer.OtelTracer>
|
|
156
157
|
|
|
157
158
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
159
|
+
* Disposal callback for the runtime created by the registry.
|
|
160
|
+
* Undefined when caller provided their own runtime (caller owns cleanup in that case).
|
|
160
161
|
*/
|
|
161
|
-
readonly #
|
|
162
|
+
readonly #disposeOwnedRuntime: (() => Promise<void>) | undefined
|
|
162
163
|
|
|
163
164
|
/**
|
|
164
|
-
*
|
|
165
|
+
* In-flight loading promises keyed by storeId.
|
|
166
|
+
* Ensures concurrent `getOrLoadPromise` calls receive the same Promise reference.
|
|
165
167
|
*/
|
|
166
|
-
readonly #
|
|
168
|
+
readonly #loadingPromises: Map<string, Promise<Store<any, any>>> = new Map()
|
|
167
169
|
|
|
168
170
|
/**
|
|
169
171
|
* Creates a new StoreRegistry instance.
|
|
@@ -179,15 +181,18 @@ export class StoreRegistry {
|
|
|
179
181
|
* ```
|
|
180
182
|
*/
|
|
181
183
|
constructor(config: StoreRegistryConfig = {}) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
ManagedRuntime.make(Layer.mergeAll(Layer.scope, OtelLiveDummy))
|
|
184
|
+
if (config.runtime !== undefined) {
|
|
185
|
+
this.#runtime = config.runtime
|
|
186
|
+
} else {
|
|
187
|
+
const ownedRuntime = ManagedRuntime.make(Layer.mergeAll(Layer.scope, OtelLiveDummy))
|
|
188
|
+
this.#runtime = ownedRuntime.runtimeEffect.pipe(Effect.runSync)
|
|
189
|
+
this.#disposeOwnedRuntime = () => ownedRuntime.dispose()
|
|
190
|
+
}
|
|
186
191
|
|
|
187
192
|
this.#rcMap = RcMap.make({
|
|
188
193
|
lookup: ({ options }: StoreCacheKey) => {
|
|
189
194
|
// Merge registry defaults with call-site options (call-site takes precedence)
|
|
190
|
-
const mergedOptions = { ...
|
|
195
|
+
const mergedOptions = { ...config.defaultOptions, ...options }
|
|
191
196
|
return createStore(mergedOptions).pipe(
|
|
192
197
|
Effect.catchAllDefect((cause) => UnknownError.make({ cause })),
|
|
193
198
|
Effect.withSpan(`StoreRegistry.lookup:${mergedOptions.storeId}`),
|
|
@@ -200,9 +205,7 @@ export class StoreRegistry {
|
|
|
200
205
|
),
|
|
201
206
|
)
|
|
202
207
|
},
|
|
203
|
-
|
|
204
|
-
// See https://github.com/livestorejs/livestore/issues/917
|
|
205
|
-
idleTimeToLive: config.defaultOptions?.unusedCacheTime ?? DEFAULT_UNUSED_CACHE_TIME,
|
|
208
|
+
idleTimeToLive: ({ options }: StoreCacheKey) => options.unusedCacheTime ?? config.defaultOptions?.unusedCacheTime ?? DEFAULT_UNUSED_CACHE_TIME,
|
|
206
209
|
}).pipe(Runtime.runSync(this.#runtime))
|
|
207
210
|
}
|
|
208
211
|
|
|
@@ -260,29 +263,34 @@ export class StoreRegistry {
|
|
|
260
263
|
): Store<TSchema, TContext> | Promise<Store<TSchema, TContext>> => {
|
|
261
264
|
const exit = this.getOrLoad(options).pipe(Effect.scoped, Runtime.runSyncExit(this.#runtime))
|
|
262
265
|
|
|
263
|
-
if (Exit.isSuccess(exit)) return exit.value
|
|
266
|
+
if (Exit.isSuccess(exit) === true) return exit.value
|
|
264
267
|
|
|
265
268
|
// Check if the failure is due to async work
|
|
266
269
|
const defect = Cause.dieOption(exit.cause)
|
|
267
|
-
if (defect._tag
|
|
268
|
-
|
|
270
|
+
if (defect._tag !== 'Some') {
|
|
271
|
+
// Handle synchronous failure
|
|
272
|
+
throw Cause.squash(exit.cause)
|
|
273
|
+
}
|
|
269
274
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
if (Runtime.isAsyncFiberException(defect.value) === false) {
|
|
276
|
+
// Handle synchronous failure
|
|
277
|
+
throw Cause.squash(exit.cause)
|
|
278
|
+
}
|
|
273
279
|
|
|
274
|
-
|
|
275
|
-
const fiber = defect.value.fiber
|
|
276
|
-
const promise = Fiber.join(fiber)
|
|
277
|
-
.pipe(Runtime.runPromise(this.#runtime))
|
|
278
|
-
.finally(() => this.#loadingPromises.delete(storeId)) as Promise<Store<TSchema, TContext>>
|
|
280
|
+
const { storeId } = options
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
// Return cached promise if one exists (ensures concurrent calls get the same Promise reference)
|
|
283
|
+
const cached = this.#loadingPromises.get(storeId)
|
|
284
|
+
if (cached !== undefined) return cached as Promise<Store<TSchema, TContext>>
|
|
283
285
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
+
// Create and cache the promise
|
|
287
|
+
const fiber = defect.value.fiber as Fiber.RuntimeFiber<Store<TSchema, TContext>>
|
|
288
|
+
const promise = Fiber.join(fiber)
|
|
289
|
+
.pipe(Runtime.runPromise(this.#runtime))
|
|
290
|
+
.finally(() => this.#loadingPromises.delete(storeId))
|
|
291
|
+
|
|
292
|
+
this.#loadingPromises.set(storeId, promise)
|
|
293
|
+
return promise
|
|
286
294
|
}
|
|
287
295
|
|
|
288
296
|
/**
|
|
@@ -344,6 +352,25 @@ export class StoreRegistry {
|
|
|
344
352
|
// Do nothing; preload is best-effort
|
|
345
353
|
}
|
|
346
354
|
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Disposes the registry and all its managed stores, immediately releasing resources
|
|
358
|
+
* (database connections, WebSocket connections, web workers, etc.).
|
|
359
|
+
*
|
|
360
|
+
* Most applications should use a single `StoreRegistry` and don't need to call
|
|
361
|
+
* this method. It's only necessary when creating multiple short-lived registries to
|
|
362
|
+
* immediately release resources and avoid conflicts with subsequent registries.
|
|
363
|
+
*
|
|
364
|
+
* @returns A promise that resolves when disposal is complete
|
|
365
|
+
*
|
|
366
|
+
* @remarks
|
|
367
|
+
* - No-op if a custom `runtime` was provided to the constructor (caller owns cleanup)
|
|
368
|
+
* - Idempotent: safe to call multiple times
|
|
369
|
+
* - After disposal, the registry should not be used
|
|
370
|
+
*/
|
|
371
|
+
dispose = async (): Promise<void> => {
|
|
372
|
+
await this.#disposeOwnedRuntime?.()
|
|
373
|
+
}
|
|
347
374
|
}
|
|
348
375
|
|
|
349
376
|
/**
|
|
@@ -382,12 +409,10 @@ export class StoreRegistry {
|
|
|
382
409
|
* });
|
|
383
410
|
* ```
|
|
384
411
|
*/
|
|
385
|
-
export
|
|
412
|
+
export const storeOptions = <
|
|
386
413
|
TSchema extends LiveStoreSchema,
|
|
387
414
|
TContext = {},
|
|
388
415
|
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
389
416
|
>(
|
|
390
417
|
options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>,
|
|
391
|
-
): RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>
|
|
392
|
-
return options
|
|
393
|
-
}
|
|
418
|
+
): RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema> => options
|
|
@@ -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,12 @@ import {
|
|
|
5
7
|
type ClientSessionDevtoolsChannel,
|
|
6
8
|
type ClientSessionSyncProcessorSimulationParams,
|
|
7
9
|
type IntentionalShutdownCause,
|
|
8
|
-
type InvalidPullError,
|
|
9
|
-
type IsOfflineError,
|
|
10
10
|
LogConfig,
|
|
11
11
|
type MaterializeError,
|
|
12
|
+
type BackendIdMismatchError,
|
|
12
13
|
type MigrationsReport,
|
|
13
14
|
provideOtel,
|
|
14
|
-
type
|
|
15
|
+
type ServerAheadError,
|
|
15
16
|
UnknownError,
|
|
16
17
|
} from '@livestore/common'
|
|
17
18
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
@@ -32,16 +33,20 @@ import {
|
|
|
32
33
|
TaskTracing,
|
|
33
34
|
} from '@livestore/utils/effect'
|
|
34
35
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
35
|
-
import * as otel from '@opentelemetry/api'
|
|
36
36
|
|
|
37
37
|
import { connectDevtoolsToStore } from './devtools.ts'
|
|
38
|
-
import { STORE_DEFAULT_PARAMS, Store } from './store.ts'
|
|
39
38
|
import type {
|
|
40
39
|
LiveStoreContextRunning as LiveStoreContextRunning_,
|
|
41
40
|
OtelOptions,
|
|
42
41
|
ShutdownDeferred,
|
|
43
42
|
} from './store-types.ts'
|
|
44
43
|
import { StoreInternalsSymbol } from './store-types.ts'
|
|
44
|
+
import { STORE_DEFAULT_PARAMS, Store } from './store.ts'
|
|
45
|
+
|
|
46
|
+
declare global {
|
|
47
|
+
/** Store instances for console debugging */
|
|
48
|
+
var __debugLiveStore: Record<string, Store<any, any>> | undefined
|
|
49
|
+
}
|
|
45
50
|
|
|
46
51
|
/**
|
|
47
52
|
* @deprecated Use `makeStoreContext()` from `@livestore/livestore/effect` instead.
|
|
@@ -320,7 +325,7 @@ export const createStore = <
|
|
|
320
325
|
const shutdown = (
|
|
321
326
|
exit: Exit.Exit<
|
|
322
327
|
IntentionalShutdownCause,
|
|
323
|
-
UnknownError | MaterializeError |
|
|
328
|
+
UnknownError | MaterializeError | BackendIdMismatchError
|
|
324
329
|
>,
|
|
325
330
|
) =>
|
|
326
331
|
Effect.gen(function* () {
|
|
@@ -332,7 +337,7 @@ export const createStore = <
|
|
|
332
337
|
),
|
|
333
338
|
)
|
|
334
339
|
|
|
335
|
-
if (shutdownDeferred) {
|
|
340
|
+
if (shutdownDeferred !== undefined) {
|
|
336
341
|
yield* Deferred.done(shutdownDeferred, exit)
|
|
337
342
|
}
|
|
338
343
|
|
|
@@ -364,7 +369,7 @@ export const createStore = <
|
|
|
364
369
|
syncPayloadEncoded,
|
|
365
370
|
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
366
371
|
|
|
367
|
-
if (LS_DEV && clientSession.leaderThread.initialState.migrationsReport.migrations.length > 0) {
|
|
372
|
+
if (LS_DEV === true && clientSession.leaderThread.initialState.migrationsReport.migrations.length > 0) {
|
|
368
373
|
yield* Effect.logDebug(
|
|
369
374
|
'[@livestore/livestore:createStore] migrationsReport',
|
|
370
375
|
...clientSession.leaderThread.initialState.migrationsReport.migrations.map((m) =>
|
|
@@ -383,7 +388,7 @@ export const createStore = <
|
|
|
383
388
|
effectContext: { lifetimeScope, runtime },
|
|
384
389
|
// TODO find a better way to detect if we're running LiveStore in the LiveStore devtools
|
|
385
390
|
// But for now this is a good enough approximation with little downsides
|
|
386
|
-
__runningInDevtools: getDevtoolsEnabled(disableDevtools)
|
|
391
|
+
__runningInDevtools: ! getDevtoolsEnabled(disableDevtools),
|
|
387
392
|
confirmUnsavedChanges,
|
|
388
393
|
// NOTE during boot we're not yet executing events in a batched context
|
|
389
394
|
// but only set the provided `batchUpdates` function after boot
|
|
@@ -420,11 +425,21 @@ export const createStore = <
|
|
|
420
425
|
|
|
421
426
|
yield* Deferred.succeed(storeDeferred, store as any as Store)
|
|
422
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
|
+
|
|
423
438
|
return store
|
|
424
439
|
}).pipe(
|
|
425
440
|
Effect.withSpan('createStore', { attributes: { debugInstanceId, storeId } }),
|
|
426
441
|
Effect.annotateLogs({ debugInstanceId, storeId }),
|
|
427
|
-
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
442
|
+
LS_DEV === true ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
428
443
|
Scope.extend(lifetimeScope),
|
|
429
444
|
)
|
|
430
445
|
})
|
|
@@ -433,7 +448,7 @@ const validateStoreId = (storeId: string) =>
|
|
|
433
448
|
Effect.gen(function* () {
|
|
434
449
|
const validChars = /^[a-zA-Z0-9_-]+$/
|
|
435
450
|
|
|
436
|
-
if (
|
|
451
|
+
if (validChars.test(storeId) === false) {
|
|
437
452
|
return yield* UnknownError.make({
|
|
438
453
|
cause: `Invalid storeId: ${storeId}. Only alphanumeric characters, underscores, and hyphens are allowed.`,
|
|
439
454
|
payload: { storeId },
|
package/src/store/devtools.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { nanoid } from '@livestore/utils/nanoid'
|
|
|
7
7
|
|
|
8
8
|
import { NOT_REFRESHED_YET } from '../reactive.ts'
|
|
9
9
|
import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.ts'
|
|
10
|
-
import type { Store } from './store.ts'
|
|
11
10
|
import { StoreInternalsSymbol } from './store-types.ts'
|
|
11
|
+
import type { Store } from './store.ts'
|
|
12
12
|
|
|
13
13
|
type Unsub = () => void
|
|
14
14
|
type RequestId = string
|
|
@@ -23,7 +23,7 @@ const requestNextTick: (cb: () => void) => number =
|
|
|
23
23
|
const cancelTick: (id: number) => void =
|
|
24
24
|
globalThis.cancelAnimationFrame === undefined ? (id: number) => clearTimeout(id) : globalThis.cancelAnimationFrame
|
|
25
25
|
|
|
26
|
-
export const connectDevtoolsToStore = ({
|
|
26
|
+
export const connectDevtoolsToStore = Effect.fn('LSD.devtools.connectStoreToDevtools')(function* ({
|
|
27
27
|
storeDevtoolsChannel,
|
|
28
28
|
store,
|
|
29
29
|
}: {
|
|
@@ -32,8 +32,7 @@ export const connectDevtoolsToStore = ({
|
|
|
32
32
|
Devtools.ClientSession.MessageFromApp
|
|
33
33
|
>
|
|
34
34
|
store: Store
|
|
35
|
-
})
|
|
36
|
-
Effect.gen(function* () {
|
|
35
|
+
}) {
|
|
37
36
|
const reactivityGraphSubcriptions: SubMap = new Map()
|
|
38
37
|
const liveQueriesSubscriptions: SubMap = new Map()
|
|
39
38
|
const debugInfoHistorySubscriptions: SubMap = new Map()
|
|
@@ -88,7 +87,7 @@ export const connectDevtoolsToStore = ({
|
|
|
88
87
|
// So far I could only observe this problem with webmesh proxy channels (e.g. for Expo)
|
|
89
88
|
// Proof: https://share.cleanshot.com/V9G87B0B
|
|
90
89
|
// Also see `leader-worker-devtools.ts` for same problem
|
|
91
|
-
if (handledRequestIds.has(requestId)) {
|
|
90
|
+
if (handledRequestIds.has(requestId) === true) {
|
|
92
91
|
return
|
|
93
92
|
}
|
|
94
93
|
|
|
@@ -347,4 +346,4 @@ export const connectDevtoolsToStore = ({
|
|
|
347
346
|
Stream.runDrain,
|
|
348
347
|
Effect.withSpan('LSD.devtools.onMessage'),
|
|
349
348
|
)
|
|
350
|
-
|
|
349
|
+
}, UnknownError.mapToUnknownError)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
|
|
1
3
|
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
|
2
4
|
import type { MockSyncBackend } from '@livestore/common'
|
|
3
5
|
import { type ClientSessionLeaderThreadProxy, makeMockSyncBackend, type UnknownError } from '@livestore/common'
|
|
@@ -6,12 +8,12 @@ import { EventFactory } from '@livestore/common/testing'
|
|
|
6
8
|
import type { ShutdownDeferred, Store } from '@livestore/livestore'
|
|
7
9
|
import { createStore, makeShutdownDeferred } from '@livestore/livestore'
|
|
8
10
|
import { omitUndefineds } from '@livestore/utils'
|
|
11
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
9
12
|
import type { OtelTracer, Scope } from '@livestore/utils/effect'
|
|
10
13
|
import { Context, Effect, FetchHttpClient, Layer, Logger, LogLevel, Queue, Stream } from '@livestore/utils/effect'
|
|
11
14
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
12
15
|
import { PlatformNode } from '@livestore/utils/node'
|
|
13
|
-
|
|
14
|
-
import { expect } from 'vitest'
|
|
16
|
+
|
|
15
17
|
import { events, schema } from '../utils/tests/fixture.ts'
|
|
16
18
|
|
|
17
19
|
const withTestCtx = Vitest.makeWithTestCtx({
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
1
3
|
import type { QueryBuilder } from '@livestore/common'
|
|
2
4
|
import { QueryBuilderTypeId } from '@livestore/common'
|
|
3
5
|
import { Schema } from '@livestore/utils/effect'
|
|
4
|
-
|
|
6
|
+
|
|
5
7
|
import { TypeId } from '../live-queries/base-class.ts'
|
|
6
8
|
import { queryDb, signal } from '../live-queries/mod.ts'
|
|
7
9
|
import { isQueryable } from './store-types.ts'
|
package/src/store/store-types.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
type ClientSession,
|
|
3
5
|
type ClientSessionSyncProcessor,
|
|
4
6
|
type ClientSessionSyncProcessorSimulationParams,
|
|
5
7
|
type IntentionalShutdownCause,
|
|
6
|
-
type InvalidPullError,
|
|
7
|
-
type IsOfflineError,
|
|
8
8
|
isQueryBuilder,
|
|
9
9
|
type MaterializeError,
|
|
10
10
|
type QueryBuilder,
|
|
11
11
|
type StoreInterrupted,
|
|
12
|
-
type
|
|
12
|
+
type BackendIdMismatchError,
|
|
13
13
|
type UnknownError,
|
|
14
14
|
} from '@livestore/common'
|
|
15
15
|
import type { StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
16
16
|
import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
|
|
17
17
|
import type { Effect, Runtime, Schema, Scope } from '@livestore/utils/effect'
|
|
18
18
|
import { Deferred, Predicate } from '@livestore/utils/effect'
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
import type {
|
|
21
21
|
LiveQuery,
|
|
22
22
|
LiveQueryDef,
|
|
@@ -45,20 +45,20 @@ export type LiveStoreContext<TSchema extends LiveStoreSchema = LiveStoreSchema.A
|
|
|
45
45
|
| LiveStoreContextRunning<TSchema>
|
|
46
46
|
| {
|
|
47
47
|
stage: 'error'
|
|
48
|
-
error:
|
|
48
|
+
error: unknown
|
|
49
49
|
}
|
|
50
50
|
| {
|
|
51
51
|
stage: 'shutdown'
|
|
52
|
-
cause: IntentionalShutdownCause | StoreInterrupted |
|
|
52
|
+
cause: IntentionalShutdownCause | StoreInterrupted | UnknownError
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export type ShutdownDeferred = Deferred.Deferred<
|
|
56
56
|
IntentionalShutdownCause,
|
|
57
|
-
UnknownError |
|
|
57
|
+
UnknownError | StoreInterrupted | MaterializeError | BackendIdMismatchError
|
|
58
58
|
>
|
|
59
59
|
export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
|
|
60
60
|
IntentionalShutdownCause,
|
|
61
|
-
UnknownError |
|
|
61
|
+
UnknownError | StoreInterrupted | MaterializeError | BackendIdMismatchError
|
|
62
62
|
>()
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -416,3 +416,42 @@ export const isLiveQueryInstance = (value: unknown): value is LiveQuery<any> =>
|
|
|
416
416
|
*/
|
|
417
417
|
export const isQueryable = (value: unknown): value is Queryable<unknown> =>
|
|
418
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
|
|
443
|
+
/**
|
|
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").
|
|
446
|
+
*/
|
|
447
|
+
upstreamHead: string
|
|
448
|
+
/**
|
|
449
|
+
* Number of events pending synchronization to the leader thread.
|
|
450
|
+
*/
|
|
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
|
|
457
|
+
}
|