@livestore/livestore 0.4.0-dev.21 → 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 +130 -2
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +185 -6
- 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 +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 +2 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -0
- 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 +56 -6
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +32 -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 +16 -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 +59 -9
- 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 +102 -6
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +148 -47
- 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 +59 -18
- 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 +381 -8
- package/src/effect/mod.ts +13 -1
- 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 +2 -0
- 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 +76 -15
- package/src/store/devtools.ts +20 -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 +64 -13
- package/src/store/store.ts +197 -60
- 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 -5
- package/docs/building-with-livestore/crud/index.md +0 -5
- package/docs/building-with-livestore/data-modeling/index.md +0 -1
- 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/index.md +0 -30
- package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -891
- package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
- package/docs/building-with-livestore/opentelemetry/index.md +0 -208
- 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 -72
- 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 -281
- 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 -918
- 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 -736
- package/docs/getting-started/node/index.md +0 -115
- package/docs/getting-started/react-web/index.md +0 -573
- package/docs/getting-started/solid/index.md +0 -3
- package/docs/getting-started/vue/index.md +0 -471
- package/docs/index.md +0 -209
- package/docs/llms.txt +0 -147
- 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 -5
- 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 -5
- 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 -226
- package/docs/patterns/effect/index.md +0 -1495
- 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 -192
- 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 -245
- 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 -218
- 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 -511
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
1
2
|
import { QueryBuilderTypeId } from '@livestore/common';
|
|
2
3
|
import { Schema } from '@livestore/utils/effect';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
4
|
import { TypeId } from "../live-queries/base-class.js";
|
|
5
5
|
import { queryDb, signal } from "../live-queries/mod.js";
|
|
6
6
|
import { isQueryable } from "./store-types.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-types.test.js","sourceRoot":"","sources":["../../src/store/store-types.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store-types.test.js","sourceRoot":"","sources":["../../src/store/store-types.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAG7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,MAAM,gBAAgB,GAAG,GAAgC,EAAE,CACzD,CAAC;IACC,CAAC,kBAAkB,CAAC,EAAE,kBAAkB;IACxC,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;IACnF,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU;CAC3B,CAA2C,CAAA;AAE9C,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,GAAG,GAAG,OAAO,CAAC;YAClB,KAAK,EAAE,mBAAmB;YAC1B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;SAC9D,CAAC,CAAA;QAEF,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;QAEzC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,aAAa,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAW,CAAA;QAEnD,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAAA;QAE7B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC1C,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/dist/store/store.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type Bindable, type ClientSession, MaterializeError, UnknownError } from '@livestore/common';
|
|
1
|
+
import { type Bindable, type ClientSession, MaterializeError, type StorageMode, type SyncState, UnknownError } from '@livestore/common';
|
|
2
2
|
import type { LiveStoreSchema } from '@livestore/common/schema';
|
|
3
3
|
import { LiveStoreEvent } from '@livestore/common/schema';
|
|
4
4
|
import { Cause, Effect, Inspectable, Schema, Stream } from '@livestore/utils/effect';
|
|
5
5
|
import * as otel from '@opentelemetry/api';
|
|
6
6
|
import type { SignalDef } from '../live-queries/base-class.ts';
|
|
7
|
-
import { type Queryable, type RefreshReason, type StoreCommitOptions, type StoreEventsOptions, type StoreInternals, StoreInternalsSymbol, type
|
|
7
|
+
import { type Queryable, type RefreshReason, type StoreCommitOptions, type StoreConstructorParams, type StoreEventsOptions, type StoreInternals, StoreInternalsSymbol, type SubscribeOptions, type SyncStatus, type Unsubscribe } from './store-types.ts';
|
|
8
8
|
export type SubscribeFn = {
|
|
9
9
|
<TResult>(query: Queryable<TResult>, onUpdate: (value: TResult) => void, options?: SubscribeOptions<TResult>): Unsubscribe;
|
|
10
10
|
<TResult>(query: Queryable<TResult>, options?: SubscribeOptions<TResult>): AsyncIterable<TResult>;
|
|
@@ -26,8 +26,8 @@ export declare const STORE_DEFAULT_PARAMS: {
|
|
|
26
26
|
* ## Creating a Store
|
|
27
27
|
*
|
|
28
28
|
* Use `createStore` (Effect-based) or `createStorePromise` to obtain a Store instance.
|
|
29
|
-
* In React applications, use
|
|
30
|
-
*
|
|
29
|
+
* In React applications, use `StoreRegistry` with `<StoreRegistryProvider>` and the `useStore()` hook
|
|
30
|
+
* which manages the Store lifecycle.
|
|
31
31
|
*
|
|
32
32
|
* ## Querying Data
|
|
33
33
|
*
|
|
@@ -69,7 +69,7 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
|
|
|
69
69
|
/** User-defined context attached to this Store (e.g. for dependency injection). */
|
|
70
70
|
readonly context: TContext;
|
|
71
71
|
/** Options provided to the Store constructor. */
|
|
72
|
-
readonly params:
|
|
72
|
+
readonly params: StoreConstructorParams<TSchema, TContext>['params'];
|
|
73
73
|
/**
|
|
74
74
|
* Reactive connectivity updates emitted by the backing sync backend.
|
|
75
75
|
*
|
|
@@ -88,11 +88,28 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
|
|
|
88
88
|
* ```
|
|
89
89
|
*/
|
|
90
90
|
readonly networkStatus: ClientSession['leaderThread']['networkStatus'];
|
|
91
|
+
/**
|
|
92
|
+
* Indicates how data is being stored.
|
|
93
|
+
*
|
|
94
|
+
* - `persisted`: Data is persisted to disk (e.g., via OPFS on web, SQLite file on native)
|
|
95
|
+
* - `in-memory`: Data is only stored in memory and will be lost on page refresh
|
|
96
|
+
*
|
|
97
|
+
* The store operates in `in-memory` mode when persistent storage is unavailable,
|
|
98
|
+
* such as in Safari/Firefox private browsing mode where OPFS is restricted.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* if (store.storageMode === 'in-memory') {
|
|
103
|
+
* showWarning('Data will not be persisted in private browsing mode')
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
readonly storageMode: StorageMode;
|
|
91
108
|
/**
|
|
92
109
|
* Store internals. Not part of the public API — shapes and semantics may change without notice.
|
|
93
110
|
*/
|
|
94
111
|
readonly [StoreInternalsSymbol]: StoreInternals;
|
|
95
|
-
constructor({ clientSession, schema, otelOptions, context, batchUpdates, storeId, effectContext, params, confirmUnsavedChanges, __runningInDevtools, }:
|
|
112
|
+
constructor({ clientSession, schema, otelOptions, context, batchUpdates, storeId, effectContext, params, confirmUnsavedChanges, __runningInDevtools, }: StoreConstructorParams<TSchema, TContext>);
|
|
96
113
|
/**
|
|
97
114
|
* Current session identifier for this Store instance.
|
|
98
115
|
*
|
|
@@ -272,6 +289,65 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
|
|
|
272
289
|
* See `store.events` for details on options and behaviour.
|
|
273
290
|
*/
|
|
274
291
|
eventsStream: (options?: StoreEventsOptions<TSchema>) => Stream.Stream<LiveStoreEvent.Client.ForSchema<TSchema>, UnknownError>;
|
|
292
|
+
/**
|
|
293
|
+
* Returns the current synchronization status of the store.
|
|
294
|
+
*
|
|
295
|
+
* This is a synchronous operation that returns the sync state between the
|
|
296
|
+
* client session and the leader thread. Use this to display sync indicators
|
|
297
|
+
* or check if local changes have been pushed to the leader.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```ts
|
|
301
|
+
* const status = store.syncStatus()
|
|
302
|
+
* console.log(status.isSynced ? 'Synced' : `${status.pendingCount} pending`)
|
|
303
|
+
* ```
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* // Health check for backend connectivity
|
|
308
|
+
* const status = store.syncStatus()
|
|
309
|
+
* if (!status.isSynced && status.pendingCount > 100) {
|
|
310
|
+
* console.warn('Large backlog of unsynced events')
|
|
311
|
+
* }
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
syncStatus: () => SyncStatus;
|
|
315
|
+
/**
|
|
316
|
+
* Returns an Effect Stream of sync status updates.
|
|
317
|
+
*
|
|
318
|
+
* Emits the current status immediately and then whenever the sync state changes.
|
|
319
|
+
* Use this for Effect-based workflows or when you need more control over the stream.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* store.syncStatusStream().pipe(
|
|
324
|
+
* Stream.tap((status) => Effect.log(`Sync status: ${status.isSynced}`)),
|
|
325
|
+
* Stream.runDrain,
|
|
326
|
+
* )
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
syncStatusStream: () => Stream.Stream<SyncStatus>;
|
|
330
|
+
/**
|
|
331
|
+
* Subscribes to sync status changes.
|
|
332
|
+
*
|
|
333
|
+
* The callback is invoked immediately with the current status and then
|
|
334
|
+
* whenever the sync state changes (e.g., when events are pushed or confirmed).
|
|
335
|
+
*
|
|
336
|
+
* @param onUpdate - Callback invoked with the current sync status
|
|
337
|
+
* @returns Unsubscribe function to stop receiving updates
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* const unsubscribe = store.subscribeSyncStatus((status) => {
|
|
342
|
+
* updateUI(status.isSynced ? 'Synced' : 'Syncing...')
|
|
343
|
+
* })
|
|
344
|
+
*
|
|
345
|
+
* // Later, stop listening
|
|
346
|
+
* unsubscribe()
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
subscribeSyncStatus: (onUpdate: (status: SyncStatus) => void) => Unsubscribe;
|
|
350
|
+
private makeSyncStatus;
|
|
275
351
|
/**
|
|
276
352
|
* This can be used in combination with `skipRefresh` when committing events.
|
|
277
353
|
* We might need a better solution for this. Let's see.
|
|
@@ -291,6 +367,26 @@ export declare class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any
|
|
|
291
367
|
* This is called automatically when the store was created using the React or Effect API.
|
|
292
368
|
*/
|
|
293
369
|
shutdown: (cause?: Cause.Cause<UnknownError | MaterializeError>) => Effect.Effect<void>;
|
|
370
|
+
/**
|
|
371
|
+
* Helper methods useful during development
|
|
372
|
+
*
|
|
373
|
+
* @internal
|
|
374
|
+
*/
|
|
375
|
+
_dev: {
|
|
376
|
+
downloadDb: (source?: "local" | "leader") => void;
|
|
377
|
+
downloadEventlogDb: () => void;
|
|
378
|
+
hardReset: (mode?: "all-data" | "only-app-db") => void;
|
|
379
|
+
overrideNetworkStatus: (status: "online" | "offline") => void;
|
|
380
|
+
syncStates: () => Promise<{
|
|
381
|
+
session: SyncState.SyncState;
|
|
382
|
+
leader: SyncState.SyncState;
|
|
383
|
+
}>;
|
|
384
|
+
printSyncStates: () => void;
|
|
385
|
+
version: string;
|
|
386
|
+
otel: {
|
|
387
|
+
rootSpanContext: () => otel.SpanContext | undefined;
|
|
388
|
+
};
|
|
389
|
+
};
|
|
294
390
|
toJSON: () => {
|
|
295
391
|
_tag: string;
|
|
296
392
|
reactivityGraph: import("../reactive.ts").ReactiveGraphSnapshot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,aAAa,EAQlB,gBAAgB,EAOhB,YAAY,EACb,MAAM,mBAAmB,CAAA;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"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"}
|
package/dist/store/store.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Devtools, getExecStatementsFromMaterializer, getResultSchema, hashMaterializerResults, IntentionalShutdownCause, isQueryBuilder, liveStoreVersion, MaterializeError, MaterializerHashMismatchError, makeClientSessionSyncProcessor, prepareBindValues, QueryBuilderAstSymbol, replaceSessionIdSymbol, UnknownError, } from '@livestore/common';
|
|
2
|
-
import { LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema';
|
|
3
|
-
import { assertNever, isDevEnv, omitUndefineds, shouldNeverHappen } from '@livestore/utils';
|
|
2
|
+
import { EventSequenceNumber, LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema';
|
|
3
|
+
import { assertNever, isDevEnv, objectToString, omitUndefineds, shouldNeverHappen } from '@livestore/utils';
|
|
4
4
|
import { Cause, Effect, Exit, Fiber, Inspectable, Option, OtelTracer, Runtime, Schema, Stream, } from '@livestore/utils/effect';
|
|
5
5
|
import { nanoid } from '@livestore/utils/nanoid';
|
|
6
6
|
import * as otel from '@opentelemetry/api';
|
|
@@ -11,7 +11,7 @@ import { SqliteDbWrapper } from "../SqliteDbWrapper.js";
|
|
|
11
11
|
import { ReferenceCountedSet } from "../utils/data-structures.js";
|
|
12
12
|
import { downloadBlob, exposeDebugUtils } from "../utils/dev.js";
|
|
13
13
|
import { StoreInternalsSymbol, } from "./store-types.js";
|
|
14
|
-
if (isDevEnv()) {
|
|
14
|
+
if (isDevEnv() === true) {
|
|
15
15
|
exposeDebugUtils();
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
@@ -32,8 +32,8 @@ export const STORE_DEFAULT_PARAMS = {
|
|
|
32
32
|
* ## Creating a Store
|
|
33
33
|
*
|
|
34
34
|
* Use `createStore` (Effect-based) or `createStorePromise` to obtain a Store instance.
|
|
35
|
-
* In React applications, use
|
|
36
|
-
*
|
|
35
|
+
* In React applications, use `StoreRegistry` with `<StoreRegistryProvider>` and the `useStore()` hook
|
|
36
|
+
* which manages the Store lifecycle.
|
|
37
37
|
*
|
|
38
38
|
* ## Querying Data
|
|
39
39
|
*
|
|
@@ -94,11 +94,28 @@ export class Store extends Inspectable.Class {
|
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
96
|
networkStatus;
|
|
97
|
+
/**
|
|
98
|
+
* Indicates how data is being stored.
|
|
99
|
+
*
|
|
100
|
+
* - `persisted`: Data is persisted to disk (e.g., via OPFS on web, SQLite file on native)
|
|
101
|
+
* - `in-memory`: Data is only stored in memory and will be lost on page refresh
|
|
102
|
+
*
|
|
103
|
+
* The store operates in `in-memory` mode when persistent storage is unavailable,
|
|
104
|
+
* such as in Safari/Firefox private browsing mode where OPFS is restricted.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```tsx
|
|
108
|
+
* if (store.storageMode === 'in-memory') {
|
|
109
|
+
* showWarning('Data will not be persisted in private browsing mode')
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
storageMode;
|
|
97
114
|
/**
|
|
98
115
|
* Store internals. Not part of the public API — shapes and semantics may change without notice.
|
|
99
116
|
*/
|
|
100
117
|
[StoreInternalsSymbol];
|
|
101
|
-
|
|
118
|
+
//#region constructor
|
|
102
119
|
constructor({ clientSession, schema, otelOptions, context, batchUpdates, storeId, effectContext, params, confirmUnsavedChanges, __runningInDevtools, }) {
|
|
103
120
|
super();
|
|
104
121
|
this.storeId = storeId;
|
|
@@ -106,12 +123,11 @@ export class Store extends Inspectable.Class {
|
|
|
106
123
|
this.context = context;
|
|
107
124
|
this.params = params;
|
|
108
125
|
this.networkStatus = clientSession.leaderThread.networkStatus;
|
|
126
|
+
this.storageMode = clientSession.leaderThread.initialState.storageMode;
|
|
109
127
|
const reactivityGraph = makeReactivityGraph();
|
|
110
|
-
const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext);
|
|
111
128
|
const syncProcessor = makeClientSessionSyncProcessor({
|
|
112
129
|
schema,
|
|
113
130
|
clientSession,
|
|
114
|
-
runtime: effectContext.runtime,
|
|
115
131
|
materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')((eventEncoded, { withChangeset, materializerHashLeader }) =>
|
|
116
132
|
// We need to use `Effect.gen` (even though we're using `Effect.fn`) so that we can pass `this` to the function
|
|
117
133
|
Effect.gen(this, function* () {
|
|
@@ -135,7 +151,7 @@ export class Store extends Inspectable.Class {
|
|
|
135
151
|
dbState: this[StoreInternalsSymbol].sqliteDbWrapper,
|
|
136
152
|
event: { decoded: undefined, encoded: eventEncoded },
|
|
137
153
|
});
|
|
138
|
-
const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none();
|
|
154
|
+
const materializerHash = isDevEnv() === true ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none();
|
|
139
155
|
// Hash mismatch detection only occurs during the pull path (when receiving events from the leader).
|
|
140
156
|
// During push path (local commits), materializerHashLeader is always Option.none(), so this condition
|
|
141
157
|
// will never be met. The check happens when the same event comes back from the leader during sync,
|
|
@@ -191,7 +207,6 @@ export class Store extends Inspectable.Class {
|
|
|
191
207
|
}
|
|
192
208
|
reactivityGraph.setRefs(tablesToUpdate);
|
|
193
209
|
},
|
|
194
|
-
span: syncSpan,
|
|
195
210
|
params: {
|
|
196
211
|
...omitUndefineds({
|
|
197
212
|
leaderPushBatchSize: params.leaderPushBatchSize,
|
|
@@ -201,7 +216,7 @@ export class Store extends Inspectable.Class {
|
|
|
201
216
|
: {}),
|
|
202
217
|
},
|
|
203
218
|
confirmUnsavedChanges,
|
|
204
|
-
});
|
|
219
|
+
}).pipe(Runtime.runSync(effectContext.runtime));
|
|
205
220
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
206
221
|
const tableRefs = {};
|
|
207
222
|
const activeQueries = new ReferenceCountedSet();
|
|
@@ -227,7 +242,7 @@ export class Store extends Inspectable.Class {
|
|
|
227
242
|
const allTableNames = new Set(
|
|
228
243
|
// NOTE we're excluding the LiveStore schema and events tables as they are not user-facing
|
|
229
244
|
// unless LiveStore is running in the devtools
|
|
230
|
-
__runningInDevtools
|
|
245
|
+
__runningInDevtools === true
|
|
231
246
|
? this.schema.state.sqlite.tables.keys()
|
|
232
247
|
: Array.from(this.schema.state.sqlite.tables.keys()).filter((_) => !SystemTables.isStateSystemTable(_)));
|
|
233
248
|
const existingTableRefs = new Map(Array.from(reactivityGraph.atoms.values())
|
|
@@ -238,7 +253,7 @@ export class Store extends Inspectable.Class {
|
|
|
238
253
|
existingTableRefs.get(tableName) ??
|
|
239
254
|
reactivityGraph.makeRef(null, {
|
|
240
255
|
equal: () => false,
|
|
241
|
-
label: `tableRef:${tableName}`,
|
|
256
|
+
label: `tableRef:${String(tableName)}`,
|
|
242
257
|
meta: { liveStoreRefType: 'table' },
|
|
243
258
|
});
|
|
244
259
|
}
|
|
@@ -251,7 +266,6 @@ export class Store extends Inspectable.Class {
|
|
|
251
266
|
}
|
|
252
267
|
}
|
|
253
268
|
// End the otel spans
|
|
254
|
-
syncSpan.end();
|
|
255
269
|
commitsSpan.end();
|
|
256
270
|
queriesSpan.end();
|
|
257
271
|
}));
|
|
@@ -276,7 +290,7 @@ export class Store extends Inspectable.Class {
|
|
|
276
290
|
// Initialize stable network status property from client session
|
|
277
291
|
this.networkStatus = clientSession.leaderThread.networkStatus;
|
|
278
292
|
}
|
|
279
|
-
|
|
293
|
+
//#endregion constructor
|
|
280
294
|
/**
|
|
281
295
|
* Current session identifier for this Store instance.
|
|
282
296
|
*
|
|
@@ -296,7 +310,7 @@ export class Store extends Inspectable.Class {
|
|
|
296
310
|
return this[StoreInternalsSymbol].clientSession.clientId;
|
|
297
311
|
}
|
|
298
312
|
checkShutdown = (operation) => {
|
|
299
|
-
if (this[StoreInternalsSymbol].isShutdown) {
|
|
313
|
+
if (this[StoreInternalsSymbol].isShutdown === true) {
|
|
300
314
|
throw new UnknownError({
|
|
301
315
|
cause: `Store has been shut down (while performing "${operation}").`,
|
|
302
316
|
note: `You cannot perform this operation after the store has been shut down.`,
|
|
@@ -329,9 +343,14 @@ export class Store extends Inspectable.Class {
|
|
|
329
343
|
});
|
|
330
344
|
subscribeWithCallback = (query, onUpdate, options) => {
|
|
331
345
|
this.checkShutdown('subscribe');
|
|
332
|
-
return this[StoreInternalsSymbol].otel.tracer.startActiveSpan(`LiveStore.subscribe`, {
|
|
346
|
+
return this[StoreInternalsSymbol].otel.tracer.startActiveSpan(`LiveStore.subscribe`, {
|
|
347
|
+
attributes: {
|
|
348
|
+
label: options?.label,
|
|
349
|
+
queryLabel: isQueryBuilder(query) === true ? query.toString() : query.label,
|
|
350
|
+
},
|
|
351
|
+
}, options?.otelContext ?? this[StoreInternalsSymbol].otel.queriesSpanContext, (span) => {
|
|
333
352
|
const otelContext = otel.trace.setSpan(otel.context.active(), span);
|
|
334
|
-
const queryRcRef = isQueryBuilder(query)
|
|
353
|
+
const queryRcRef = isQueryBuilder(query) === true
|
|
335
354
|
? queryDb(query).make(this[StoreInternalsSymbol].reactivityGraph.context)
|
|
336
355
|
: query._tag === 'def' || query._tag === 'signal-def'
|
|
337
356
|
? query.make(this[StoreInternalsSymbol].reactivityGraph.context)
|
|
@@ -344,7 +363,7 @@ export class Store extends Inspectable.Class {
|
|
|
344
363
|
let suppressCallback = options?.skipInitialRun === true;
|
|
345
364
|
const effect = this[StoreInternalsSymbol].reactivityGraph.makeEffect((get, _otelContext, debugRefreshReason) => {
|
|
346
365
|
const result = get(query$.results$, otelContext, debugRefreshReason);
|
|
347
|
-
if (suppressCallback) {
|
|
366
|
+
if (suppressCallback === true) {
|
|
348
367
|
return;
|
|
349
368
|
}
|
|
350
369
|
onUpdate(result);
|
|
@@ -355,13 +374,13 @@ export class Store extends Inspectable.Class {
|
|
|
355
374
|
label: `subscribe-initial-run:${options?.label}`,
|
|
356
375
|
});
|
|
357
376
|
};
|
|
358
|
-
if (options?.stackInfo) {
|
|
377
|
+
if (options?.stackInfo !== undefined) {
|
|
359
378
|
query$.activeSubscriptions.add(options.stackInfo);
|
|
360
379
|
}
|
|
361
380
|
options?.onSubscribe?.(query$);
|
|
362
381
|
this[StoreInternalsSymbol].activeQueries.add(query$);
|
|
363
|
-
if (
|
|
364
|
-
if (suppressCallback) {
|
|
382
|
+
if (query$.isDestroyed === false) {
|
|
383
|
+
if (suppressCallback === true) {
|
|
365
384
|
// We still run once to register dependencies in the reactive graph, but suppress the initial callback so the
|
|
366
385
|
// caller truly skips the first emission; subsequent runs (after commits) will call the callback.
|
|
367
386
|
runInitialEffect();
|
|
@@ -375,7 +394,7 @@ export class Store extends Inspectable.Class {
|
|
|
375
394
|
try {
|
|
376
395
|
this[StoreInternalsSymbol].reactivityGraph.destroyNode(effect);
|
|
377
396
|
this[StoreInternalsSymbol].activeQueries.remove(query$);
|
|
378
|
-
if (options?.stackInfo) {
|
|
397
|
+
if (options?.stackInfo !== undefined) {
|
|
379
398
|
query$.activeSubscriptions.delete(options.stackInfo);
|
|
380
399
|
}
|
|
381
400
|
queryRcRef.deref();
|
|
@@ -394,9 +413,9 @@ export class Store extends Inspectable.Class {
|
|
|
394
413
|
};
|
|
395
414
|
subscribeStream = (query, options) => Stream.asyncPush((emit) => Effect.gen(this, function* () {
|
|
396
415
|
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)));
|
|
397
|
-
const otelContext = otelSpan ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active();
|
|
416
|
+
const otelContext = otelSpan !== undefined ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active();
|
|
398
417
|
yield* Effect.acquireRelease(Effect.sync(() => this.subscribe(query, (result) => emit.single(result), {
|
|
399
|
-
...
|
|
418
|
+
...options,
|
|
400
419
|
otelContext,
|
|
401
420
|
})), (unsub) => Effect.sync(() => unsub()));
|
|
402
421
|
}));
|
|
@@ -420,12 +439,12 @@ export class Store extends Inspectable.Class {
|
|
|
420
439
|
const res = this[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(query.query, prepareBindValues(query.bindValues, query.query), {
|
|
421
440
|
...omitUndefineds({ otelContext: options?.otelContext }),
|
|
422
441
|
});
|
|
423
|
-
if (query.schema) {
|
|
442
|
+
if (query.schema !== undefined) {
|
|
424
443
|
return Schema.decodeSync(query.schema)(res);
|
|
425
444
|
}
|
|
426
445
|
return res;
|
|
427
446
|
}
|
|
428
|
-
else if (isQueryBuilder(query)) {
|
|
447
|
+
else if (isQueryBuilder(query) === true) {
|
|
429
448
|
const ast = query[QueryBuilderAstSymbol];
|
|
430
449
|
if (ast._tag === 'RowQuery') {
|
|
431
450
|
makeExecBeforeFirstRun({
|
|
@@ -438,7 +457,7 @@ export class Store extends Inspectable.Class {
|
|
|
438
457
|
const sqlRes = query.asSql();
|
|
439
458
|
const schema = getResultSchema(query);
|
|
440
459
|
// Replace SessionIdSymbol in bind values before executing the query
|
|
441
|
-
if (sqlRes.bindValues) {
|
|
460
|
+
if (sqlRes.bindValues !== undefined) {
|
|
442
461
|
replaceSessionIdSymbol(sqlRes.bindValues, this[StoreInternalsSymbol].clientSession.sessionId);
|
|
443
462
|
}
|
|
444
463
|
const rawRes = this[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(sqlRes.query, sqlRes.bindValues, {
|
|
@@ -450,7 +469,7 @@ export class Store extends Inspectable.Class {
|
|
|
450
469
|
return decodeResult.right;
|
|
451
470
|
}
|
|
452
471
|
else {
|
|
453
|
-
return shouldNeverHappen(
|
|
472
|
+
return shouldNeverHappen('Failed to decode query result with for schema:', objectToString(schema), 'raw result:', rawRes, 'decode error:', decodeResult.left);
|
|
454
473
|
}
|
|
455
474
|
}
|
|
456
475
|
else if (query._tag === 'def') {
|
|
@@ -498,7 +517,7 @@ export class Store extends Inspectable.Class {
|
|
|
498
517
|
signalRef.deref();
|
|
499
518
|
}
|
|
500
519
|
};
|
|
501
|
-
|
|
520
|
+
//#region commit
|
|
502
521
|
/**
|
|
503
522
|
* Commit a list of events to the store which will immediately update the local database
|
|
504
523
|
* and sync the events across other clients (similar to a `git commit`).
|
|
@@ -564,22 +583,17 @@ export class Store extends Inspectable.Class {
|
|
|
564
583
|
if (events.length === 0)
|
|
565
584
|
return;
|
|
566
585
|
const localRuntime = yield* Effect.runtime();
|
|
567
|
-
const
|
|
586
|
+
const encodedEvents = yield* this[StoreInternalsSymbol].syncProcessor.encodeEvents(events);
|
|
587
|
+
const { writeTables } = yield* Effect.try({
|
|
568
588
|
try: () => {
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return this[StoreInternalsSymbol].sqliteDbWrapper.txn(runMaterializeEvents);
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
return runMaterializeEvents();
|
|
577
|
-
}
|
|
589
|
+
const materialize = () => this[StoreInternalsSymbol].syncProcessor.materializeEvents(encodedEvents).pipe(Runtime.runSync(localRuntime));
|
|
590
|
+
return events.length > 1
|
|
591
|
+
? this[StoreInternalsSymbol].sqliteDbWrapper.txn(materialize)
|
|
592
|
+
: materialize();
|
|
578
593
|
},
|
|
579
594
|
catch: (cause) => UnknownError.make({ cause }),
|
|
580
595
|
});
|
|
581
|
-
|
|
582
|
-
const { writeTables } = yield* materializeEventsTx;
|
|
596
|
+
yield* this[StoreInternalsSymbol].syncProcessor.push(encodedEvents);
|
|
583
597
|
const tablesToUpdate = [];
|
|
584
598
|
for (const tableName of writeTables) {
|
|
585
599
|
const tableRef = this[StoreInternalsSymbol].tableRefs[tableName];
|
|
@@ -615,7 +629,7 @@ export class Store extends Inspectable.Class {
|
|
|
615
629
|
],
|
|
616
630
|
}), Effect.tapErrorCause(Effect.logError), Effect.catchAllCause((cause) => Effect.fork(this.shutdown(cause))), Runtime.runSync(this[StoreInternalsSymbol].effectContext.runtime));
|
|
617
631
|
};
|
|
618
|
-
|
|
632
|
+
//#endregion commit
|
|
619
633
|
/**
|
|
620
634
|
* Returns an async iterable of events from the eventlog.
|
|
621
635
|
* Currently only events confirmed by the sync backend is supported.
|
|
@@ -683,6 +697,92 @@ export class Store extends Inspectable.Class {
|
|
|
683
697
|
};
|
|
684
698
|
return clientSession.leaderThread.events.stream(baseOptions).pipe(Stream.mapChunksEffect(Schema.decode(Schema.ChunkFromSelf(eventSchema))), Stream.catchTag('ParseError', (cause) => Stream.fail(UnknownError.make({ cause }))), Stream.tapError((error) => Effect.logError('Error in eventsStream', error)));
|
|
685
699
|
};
|
|
700
|
+
/**
|
|
701
|
+
* Returns the current synchronization status of the store.
|
|
702
|
+
*
|
|
703
|
+
* This is a synchronous operation that returns the sync state between the
|
|
704
|
+
* client session and the leader thread. Use this to display sync indicators
|
|
705
|
+
* or check if local changes have been pushed to the leader.
|
|
706
|
+
*
|
|
707
|
+
* @example
|
|
708
|
+
* ```ts
|
|
709
|
+
* const status = store.syncStatus()
|
|
710
|
+
* console.log(status.isSynced ? 'Synced' : `${status.pendingCount} pending`)
|
|
711
|
+
* ```
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```ts
|
|
715
|
+
* // Health check for backend connectivity
|
|
716
|
+
* const status = store.syncStatus()
|
|
717
|
+
* if (!status.isSynced && status.pendingCount > 100) {
|
|
718
|
+
* console.warn('Large backlog of unsynced events')
|
|
719
|
+
* }
|
|
720
|
+
* ```
|
|
721
|
+
*/
|
|
722
|
+
syncStatus = () => {
|
|
723
|
+
this.checkShutdown('syncStatus');
|
|
724
|
+
const syncState = this[StoreInternalsSymbol].syncProcessor.syncState.pipe(Effect.runSync);
|
|
725
|
+
const pendingCount = syncState.pending.length;
|
|
726
|
+
return {
|
|
727
|
+
localHead: EventSequenceNumber.Client.toString(syncState.localHead),
|
|
728
|
+
upstreamHead: EventSequenceNumber.Client.toString(syncState.upstreamHead),
|
|
729
|
+
pendingCount,
|
|
730
|
+
isSynced: pendingCount === 0,
|
|
731
|
+
};
|
|
732
|
+
};
|
|
733
|
+
/**
|
|
734
|
+
* Returns an Effect Stream of sync status updates.
|
|
735
|
+
*
|
|
736
|
+
* Emits the current status immediately and then whenever the sync state changes.
|
|
737
|
+
* Use this for Effect-based workflows or when you need more control over the stream.
|
|
738
|
+
*
|
|
739
|
+
* @example
|
|
740
|
+
* ```ts
|
|
741
|
+
* store.syncStatusStream().pipe(
|
|
742
|
+
* Stream.tap((status) => Effect.log(`Sync status: ${status.isSynced}`)),
|
|
743
|
+
* Stream.runDrain,
|
|
744
|
+
* )
|
|
745
|
+
* ```
|
|
746
|
+
*/
|
|
747
|
+
syncStatusStream = () => {
|
|
748
|
+
const syncStateSubscribable = this[StoreInternalsSymbol].syncProcessor.syncState;
|
|
749
|
+
return Stream.concat(Stream.fromEffect(syncStateSubscribable.pipe(Effect.map(this.makeSyncStatus))), syncStateSubscribable.changes.pipe(Stream.map(this.makeSyncStatus)));
|
|
750
|
+
};
|
|
751
|
+
/**
|
|
752
|
+
* Subscribes to sync status changes.
|
|
753
|
+
*
|
|
754
|
+
* The callback is invoked immediately with the current status and then
|
|
755
|
+
* whenever the sync state changes (e.g., when events are pushed or confirmed).
|
|
756
|
+
*
|
|
757
|
+
* @param onUpdate - Callback invoked with the current sync status
|
|
758
|
+
* @returns Unsubscribe function to stop receiving updates
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* ```ts
|
|
762
|
+
* const unsubscribe = store.subscribeSyncStatus((status) => {
|
|
763
|
+
* updateUI(status.isSynced ? 'Synced' : 'Syncing...')
|
|
764
|
+
* })
|
|
765
|
+
*
|
|
766
|
+
* // Later, stop listening
|
|
767
|
+
* unsubscribe()
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
subscribeSyncStatus = (onUpdate) => {
|
|
771
|
+
this.checkShutdown('subscribeSyncStatus');
|
|
772
|
+
const fiber = this.syncStatusStream().pipe(Stream.tap((status) => Effect.sync(() => onUpdate(status))), Stream.runDrain, this.runEffectFork);
|
|
773
|
+
return () => {
|
|
774
|
+
Fiber.interrupt(fiber).pipe(Runtime.runFork(this[StoreInternalsSymbol].effectContext.runtime));
|
|
775
|
+
};
|
|
776
|
+
};
|
|
777
|
+
makeSyncStatus = (syncState) => {
|
|
778
|
+
const pendingCount = syncState.pending.length;
|
|
779
|
+
return {
|
|
780
|
+
localHead: EventSequenceNumber.Client.toString(syncState.localHead),
|
|
781
|
+
upstreamHead: EventSequenceNumber.Client.toString(syncState.upstreamHead),
|
|
782
|
+
pendingCount,
|
|
783
|
+
isSynced: pendingCount === 0,
|
|
784
|
+
};
|
|
785
|
+
};
|
|
686
786
|
/**
|
|
687
787
|
* This can be used in combination with `skipRefresh` when committing events.
|
|
688
788
|
* We might need a better solution for this. Let's see.
|
|
@@ -704,7 +804,7 @@ export class Store extends Inspectable.Class {
|
|
|
704
804
|
shutdownPromise = async (cause) => {
|
|
705
805
|
this.checkShutdown('shutdownPromise');
|
|
706
806
|
this[StoreInternalsSymbol].isShutdown = true;
|
|
707
|
-
await this.shutdown(cause ? Cause.fail(cause) : undefined).pipe(this.runEffectFork, Fiber.join, Effect.runPromise);
|
|
807
|
+
await this.shutdown(cause !== undefined ? Cause.fail(cause) : undefined).pipe(this.runEffectFork, Fiber.join, Effect.runPromise);
|
|
708
808
|
};
|
|
709
809
|
/**
|
|
710
810
|
* Shuts down the store and closes the client session.
|
|
@@ -713,7 +813,7 @@ export class Store extends Inspectable.Class {
|
|
|
713
813
|
*/
|
|
714
814
|
shutdown = (cause) => {
|
|
715
815
|
this[StoreInternalsSymbol].isShutdown = true;
|
|
716
|
-
return this[StoreInternalsSymbol].clientSession.shutdown(cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })));
|
|
816
|
+
return this[StoreInternalsSymbol].clientSession.shutdown(cause !== undefined ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })));
|
|
717
817
|
};
|
|
718
818
|
/**
|
|
719
819
|
* Helper methods useful during development
|
|
@@ -752,6 +852,7 @@ export class Store extends Inspectable.Class {
|
|
|
752
852
|
}))
|
|
753
853
|
.pipe(this.runEffectFork);
|
|
754
854
|
},
|
|
855
|
+
// NOTE: Explicit return type needed to avoid TS2742 (inferred type references internal path)
|
|
755
856
|
syncStates: () => Effect.gen(this, function* () {
|
|
756
857
|
const session = yield* this[StoreInternalsSymbol].syncProcessor.syncState;
|
|
757
858
|
const leader = yield* this[StoreInternalsSymbol].clientSession.leaderThread.syncState;
|
|
@@ -760,9 +861,9 @@ export class Store extends Inspectable.Class {
|
|
|
760
861
|
printSyncStates: () => {
|
|
761
862
|
Effect.gen(this, function* () {
|
|
762
863
|
const session = yield* this[StoreInternalsSymbol].syncProcessor.syncState;
|
|
763
|
-
yield* Effect.log(`Session sync state: ${session.localHead} (upstream: ${session.upstreamHead})`, session.toJSON());
|
|
864
|
+
yield* Effect.log(`Session sync state: ${objectToString(session.localHead)} (upstream: ${objectToString(session.upstreamHead)})`, session.toJSON());
|
|
764
865
|
const leader = yield* this[StoreInternalsSymbol].clientSession.leaderThread.syncState;
|
|
765
|
-
yield* Effect.log(`Leader sync state: ${leader.localHead} (upstream: ${leader.upstreamHead})`, leader.toJSON());
|
|
866
|
+
yield* Effect.log(`Leader sync state: ${objectToString(leader.localHead)} (upstream: ${objectToString(leader.upstreamHead)})`, leader.toJSON());
|
|
766
867
|
}).pipe(this.runEffectFork);
|
|
767
868
|
},
|
|
768
869
|
version: liveStoreVersion,
|