zerodrift 1.0.3 → 1.1.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 +15 -3
- package/dist/core/BaseModel.d.ts +2 -2
- package/dist/core/BaseModel.js +4 -4
- package/dist/core/BaseSSEConnection.d.ts +9 -3
- package/dist/core/BaseSSEConnection.js +23 -3
- package/dist/core/CompoundIndexFetcher.d.ts +2 -2
- package/dist/core/CompoundIndexFetcher.js +3 -3
- package/dist/core/Database.d.ts +2 -1
- package/dist/core/Database.js +1 -1
- package/dist/core/LazyCollection.d.ts +2 -2
- package/dist/core/LazyCollection.js +1 -1
- package/dist/core/LazyOwnedCollection.d.ts +2 -2
- package/dist/core/LazyOwnedCollection.js +1 -1
- package/dist/core/MemoryAdapter.d.ts +1 -1
- package/dist/core/MemoryAdapter.js +1 -1
- package/dist/core/ModelRegistry.d.ts +1 -1
- package/dist/core/ModelRegistry.js +2 -2
- package/dist/core/ModelStream.d.ts +4 -4
- package/dist/core/ModelStream.js +3 -3
- package/dist/core/ObjectPool.d.ts +2 -2
- package/dist/core/ObjectPool.js +2 -2
- package/dist/core/Store.d.ts +4 -4
- package/dist/core/Store.js +1 -1
- package/dist/core/StoreManager.d.ts +23 -12
- package/dist/core/StoreManager.js +25 -13
- package/dist/core/SyncConnection.d.ts +6 -6
- package/dist/core/SyncConnection.js +11 -10
- package/dist/core/Transaction.d.ts +2 -2
- package/dist/core/Transaction.js +1 -1
- package/dist/core/TransactionQueue.d.ts +6 -6
- package/dist/core/TransactionQueue.js +4 -4
- package/dist/core/decorators.d.ts +1 -1
- package/dist/core/decorators.js +4 -4
- package/dist/core/index.d.ts +16 -16
- package/dist/core/index.js +9 -9
- package/dist/core/internal.d.ts +13 -13
- package/dist/core/internal.js +11 -11
- package/dist/core/observability.js +1 -1
- package/dist/core/refAccessors.d.ts +2 -2
- package/dist/core/types.d.ts +1 -1
- package/dist/react/index.d.ts +22 -12
- package/dist/react/index.js +31 -23
- package/dist/schema/builders.d.ts +1 -1
- package/dist/schema/builders.js +1 -1
- package/dist/schema/compile.d.ts +1 -1
- package/dist/schema/compile.js +6 -6
- package/dist/schema/createStore.d.ts +5 -5
- package/dist/schema/createStore.js +4 -4
- package/dist/schema/extend.d.ts +2 -2
- package/dist/schema/index.d.ts +13 -13
- package/dist/schema/index.js +7 -7
- package/dist/schema/infer.d.ts +10 -1
- package/dist/schema/types.d.ts +1 -1
- package/dist/schema/zod.d.ts +125 -12
- package/dist/schema/zod.js +24 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,15 +157,17 @@ Both authoring paths compile to the same registry, so schema entities and decora
|
|
|
157
157
|
|
|
158
158
|
## React quick start
|
|
159
159
|
|
|
160
|
-
Wrap your app in `<SyncProvider>` once.
|
|
160
|
+
Wrap your app in `<SyncProvider>` once. For the decorator path, import your model file as a side effect so decorators run before bootstrap; for the schema-first path, pass `schema={schema}` and the provider registers entities before fetching.
|
|
161
161
|
|
|
162
162
|
```tsx
|
|
163
163
|
import { SyncProvider } from "zerodrift/react";
|
|
164
|
-
import "./
|
|
164
|
+
import { schema } from "./schema"; // schema-first
|
|
165
|
+
// import "./models"; // or: decorator path — side-effect import
|
|
165
166
|
|
|
166
167
|
export default function Providers({ children }) {
|
|
167
168
|
return (
|
|
168
169
|
<SyncProvider
|
|
170
|
+
schema={schema}
|
|
169
171
|
config={{
|
|
170
172
|
workspaceId: "workspace-123",
|
|
171
173
|
transport: {
|
|
@@ -194,6 +196,16 @@ export default function Providers({ children }) {
|
|
|
194
196
|
}
|
|
195
197
|
```
|
|
196
198
|
|
|
199
|
+
In schema-first children, pull the typed store with `useStore<typeof schema>()` (add `typeof extensions` as the second generic if you also passed extensions):
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import { useStore } from "zerodrift/react";
|
|
203
|
+
import { schema } from "./schema";
|
|
204
|
+
|
|
205
|
+
const store = useStore<typeof schema>();
|
|
206
|
+
const { data: issue } = useRecord(store.issue, issueId);
|
|
207
|
+
```
|
|
208
|
+
|
|
197
209
|
Common reads and writes. The read hooks take a **handle** — a model class
|
|
198
210
|
(decorator path) or a `store.<entity>` namespace (schema path) — and infer
|
|
199
211
|
the record type from it. Every result has the same shape:
|
|
@@ -227,7 +239,7 @@ const { data: teams } = useRecords(store.team);
|
|
|
227
239
|
const { data: teamIssues } = useRecordsByIndex(store.issue, "teamId", teamId);
|
|
228
240
|
```
|
|
229
241
|
|
|
230
|
-
See [agent-docs/08-react-integration.md](agent-docs/08-react-integration.md) for hook return shapes, context-driven id generation, Storybook seeding, and testing patterns.
|
|
242
|
+
See [agent-docs/08-react-integration.md](agent-docs/08-react-integration.md) for hook return shapes, context-driven id generation, Storybook seeding, and testing patterns. For the full `transport` field list (`bootstrapSyncGroups`, `modelStreams`, `sseClientFactory`, `syncTransform`, …) see [TransportConfig reference](agent-docs/07-realtime-sync.md#transportconfig-reference).
|
|
231
243
|
|
|
232
244
|
## Headless usage
|
|
233
245
|
|
package/dist/core/BaseModel.d.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* - BackRef for @BackReference properties
|
|
14
14
|
* These are stored on __collections and __backRefs, read by the decorator getters.
|
|
15
15
|
*/
|
|
16
|
-
import { type PropertyChange, type IObjectPool, type IStoreManager } from "./types";
|
|
17
|
-
import { LazyCollectionBase, BackRef } from "./LazyCollection";
|
|
16
|
+
import { type PropertyChange, type IObjectPool, type IStoreManager } from "./types.js";
|
|
17
|
+
import { LazyCollectionBase, BackRef } from "./LazyCollection.js";
|
|
18
18
|
import { type IObservableValue } from "mobx";
|
|
19
19
|
export declare class BaseModel {
|
|
20
20
|
id: string;
|
package/dist/core/BaseModel.js
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* - BackRef for @BackReference properties
|
|
14
14
|
* These are stored on __collections and __backRefs, read by the decorator getters.
|
|
15
15
|
*/
|
|
16
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
17
|
-
import { PropertyType, DEFAULT_TRANSIENT_INDEX_DEPTH, } from "./types";
|
|
18
|
-
import { RefCollection, BackRef } from "./LazyCollection";
|
|
19
|
-
import { OwnedRefs } from "./LazyOwnedCollection";
|
|
16
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
17
|
+
import { PropertyType, DEFAULT_TRANSIENT_INDEX_DEPTH, } from "./types.js";
|
|
18
|
+
import { RefCollection, BackRef } from "./LazyCollection.js";
|
|
19
|
+
import { OwnedRefs } from "./LazyOwnedCollection.js";
|
|
20
20
|
import { action, computed, observable, reaction, runInAction, } from "mobx";
|
|
21
21
|
// The four PropertyTypes that have MobX boxes and direct setters — the "flat scalar" fields.
|
|
22
22
|
// Used by makeModelObservable (to create boxes) and assign() (to filter writable keys).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EngineErrorContext } from "./types";
|
|
1
|
+
import { type EngineErrorContext } from "./types.js";
|
|
2
2
|
export interface SSEClient {
|
|
3
3
|
onmessage: ((event: {
|
|
4
4
|
data: string;
|
|
@@ -8,19 +8,25 @@ export interface SSEClient {
|
|
|
8
8
|
}
|
|
9
9
|
export type SSEClientFactory = (url: string) => SSEClient;
|
|
10
10
|
export type SSEErrorReporter = (err: Error, context: EngineErrorContext) => void;
|
|
11
|
+
/** Either a fixed URL or a thunk re-evaluated on every (re)connect. */
|
|
12
|
+
export type SSEEndpoint = string | (() => string);
|
|
11
13
|
export declare const createBrowserSSEFactory: (init?: EventSourceInit) => SSEClientFactory;
|
|
12
14
|
export declare abstract class BaseSSEConnection {
|
|
13
|
-
protected url:
|
|
15
|
+
protected url: SSEEndpoint;
|
|
14
16
|
private sseClientFactory;
|
|
15
17
|
private reportError?;
|
|
16
18
|
private eventSource;
|
|
17
19
|
private reconnectTimer;
|
|
18
20
|
private stopped;
|
|
19
|
-
constructor(url:
|
|
21
|
+
constructor(url: SSEEndpoint, sseClientFactory?: SSEClientFactory, reportError?: SSEErrorReporter | undefined);
|
|
20
22
|
connect(): void;
|
|
21
23
|
disconnect(): void;
|
|
22
24
|
reconnect(): void;
|
|
23
25
|
get isConnected(): boolean;
|
|
26
|
+
/** Resolve the endpoint to a concrete string. Subclasses building dynamic
|
|
27
|
+
* URLs (e.g. appending query params) must read through this instead of
|
|
28
|
+
* `this.url` directly so a thunk endpoint is re-evaluated on every connect. */
|
|
29
|
+
protected resolveUrl(): string;
|
|
24
30
|
protected buildUrl(): string;
|
|
25
31
|
protected abstract onMessage(data: string): void;
|
|
26
32
|
protected onReconnect(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toError } from "./types";
|
|
1
|
+
import { toError } from "./types.js";
|
|
2
2
|
export const createBrowserSSEFactory = (init) => (url) => new EventSource(url, init);
|
|
3
3
|
export class BaseSSEConnection {
|
|
4
4
|
constructor(url, sseClientFactory = createBrowserSSEFactory(), reportError) {
|
|
@@ -36,8 +36,14 @@ export class BaseSSEConnection {
|
|
|
36
36
|
get isConnected() {
|
|
37
37
|
return this.eventSource != null;
|
|
38
38
|
}
|
|
39
|
+
/** Resolve the endpoint to a concrete string. Subclasses building dynamic
|
|
40
|
+
* URLs (e.g. appending query params) must read through this instead of
|
|
41
|
+
* `this.url` directly so a thunk endpoint is re-evaluated on every connect. */
|
|
42
|
+
resolveUrl() {
|
|
43
|
+
return typeof this.url === "function" ? this.url() : this.url;
|
|
44
|
+
}
|
|
39
45
|
buildUrl() {
|
|
40
|
-
return this.
|
|
46
|
+
return this.resolveUrl();
|
|
41
47
|
}
|
|
42
48
|
onReconnect() { }
|
|
43
49
|
onOpen() { }
|
|
@@ -51,7 +57,21 @@ export class BaseSSEConnection {
|
|
|
51
57
|
this.eventSource = null;
|
|
52
58
|
this.onClose();
|
|
53
59
|
}
|
|
54
|
-
|
|
60
|
+
// buildUrl() can throw when the endpoint is a thunk (e.g. cursor read
|
|
61
|
+
// crashes). Catch + schedule a reconnect so a transient failure doesn't
|
|
62
|
+
// permanently kill the stream.
|
|
63
|
+
let url;
|
|
64
|
+
try {
|
|
65
|
+
url = this.buildUrl();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
this.reportError?.(toError(err), {
|
|
69
|
+
kind: "sseConstruction",
|
|
70
|
+
url: "<endpoint-thunk-threw>",
|
|
71
|
+
});
|
|
72
|
+
this.scheduleReconnect();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
55
75
|
try {
|
|
56
76
|
this.eventSource = this.sseClientFactory(url);
|
|
57
77
|
this.eventSource.onmessage = (e) => {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* Adopters without server JOIN support leave the flag unset; the engine
|
|
15
15
|
* fans out per-parent (existing behavior).
|
|
16
16
|
*/
|
|
17
|
-
import type { IndexBatchFetcher, IndexQuery } from "./BatchModelLoader";
|
|
18
|
-
import { type ObjectPool } from "./ObjectPool";
|
|
17
|
+
import type { IndexBatchFetcher, IndexQuery } from "./BatchModelLoader.js";
|
|
18
|
+
import { type ObjectPool } from "./ObjectPool.js";
|
|
19
19
|
/** Switch to a compound fetch only when at least this many pending
|
|
20
20
|
* requests share a single parent FK value. Below this, the per-parent
|
|
21
21
|
* fan-out wins because the compound response would over-fetch. */
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* Adopters without server JOIN support leave the flag unset; the engine
|
|
15
15
|
* fans out per-parent (existing behavior).
|
|
16
16
|
*/
|
|
17
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
18
|
-
import { readFk } from "./ObjectPool";
|
|
19
|
-
import { PropertyType } from "./types";
|
|
17
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
18
|
+
import { readFk } from "./ObjectPool.js";
|
|
19
|
+
import { PropertyType } from "./types.js";
|
|
20
20
|
/** Switch to a compound fetch only when at least this many pending
|
|
21
21
|
* requests share a single parent FK value. Below this, the per-parent
|
|
22
22
|
* fan-out wins because the compound response would over-fetch. */
|
package/dist/core/Database.d.ts
CHANGED
|
@@ -201,7 +201,8 @@ export interface StorageAdapter {
|
|
|
201
201
|
close(): Promise<void>;
|
|
202
202
|
/**
|
|
203
203
|
* Close the connection AND permanently delete all persisted data.
|
|
204
|
-
*
|
|
204
|
+
* Called by StoreManager.destroy() for explicit logout / factory-reset
|
|
205
|
+
* flows — NOT for routine teardown.
|
|
205
206
|
*/
|
|
206
207
|
destroy(): Promise<void>;
|
|
207
208
|
get isConnected(): boolean;
|
package/dist/core/Database.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Partial: DB exists with valid data, just need delta since lastSyncId
|
|
16
16
|
* - Local: DB exists, no server contact needed (offline start)
|
|
17
17
|
*/
|
|
18
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
18
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
19
19
|
export var BootstrapType;
|
|
20
20
|
(function (BootstrapType) {
|
|
21
21
|
BootstrapType["Full"] = "full";
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
* // or via hook:
|
|
29
29
|
* const { data, isLoading } = useRelation(team?.issues);
|
|
30
30
|
*/
|
|
31
|
-
import type { BaseModel } from "./BaseModel";
|
|
32
|
-
import type { CoveringPath } from "./types";
|
|
31
|
+
import type { BaseModel } from "./BaseModel.js";
|
|
32
|
+
import type { CoveringPath } from "./types.js";
|
|
33
33
|
export declare enum CollectionState {
|
|
34
34
|
/** Never accessed — hydrate() computed the index values but no load yet. */
|
|
35
35
|
Idle = "idle",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* const { data, isLoading } = useRelation(team?.issues);
|
|
30
30
|
*/
|
|
31
31
|
import { observable, runInAction, makeObservable } from "mobx";
|
|
32
|
-
import { readFk } from "./ObjectPool";
|
|
32
|
+
import { readFk } from "./ObjectPool.js";
|
|
33
33
|
/** Walk a `CoveringPath` from `parent` through the pool, returning the
|
|
34
34
|
* leaf FK value or null if any link is missing. Depth-1 paths are a single
|
|
35
35
|
* `readFk(parent, hops[0].fk)`. Deeper paths use intermediate
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* @OwnedCollection("Issue", { idsField: "issueIds" })
|
|
16
16
|
* public issues: OwnedRefs<Issue>;
|
|
17
17
|
*/
|
|
18
|
-
import type { BaseModel } from "./BaseModel";
|
|
19
|
-
import { LazyCollectionBase } from "./LazyCollection";
|
|
18
|
+
import type { BaseModel } from "./BaseModel.js";
|
|
19
|
+
import { LazyCollectionBase } from "./LazyCollection.js";
|
|
20
20
|
export declare class OwnedRefs<T extends BaseModel = BaseModel> extends LazyCollectionBase<T> {
|
|
21
21
|
/** Live getter — reads the current IDs array from the parent model each call. */
|
|
22
22
|
private idsGetter;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* public issues: OwnedRefs<Issue>;
|
|
17
17
|
*/
|
|
18
18
|
import { runInAction } from "mobx";
|
|
19
|
-
import { LazyCollectionBase, CollectionState } from "./LazyCollection";
|
|
19
|
+
import { LazyCollectionBase, CollectionState } from "./LazyCollection.js";
|
|
20
20
|
export class OwnedRefs extends LazyCollectionBase {
|
|
21
21
|
constructor(referencedModelName, idsGetter) {
|
|
22
22
|
super(referencedModelName);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* storage in a server environment, implement StorageAdapter with your own
|
|
9
9
|
* SQLite / Redis / file-system backend.
|
|
10
10
|
*/
|
|
11
|
-
import { BootstrapType, type DatabaseMeta, type PartialIndexEntry, type StorageAdapter, type SyncActionHeader } from "./Database";
|
|
11
|
+
import { BootstrapType, type DatabaseMeta, type PartialIndexEntry, type StorageAdapter, type SyncActionHeader } from "./Database.js";
|
|
12
12
|
export declare class MemoryAdapter implements StorageAdapter {
|
|
13
13
|
private meta;
|
|
14
14
|
private models;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* storage in a server environment, implement StorageAdapter with your own
|
|
9
9
|
* SQLite / Redis / file-system backend.
|
|
10
10
|
*/
|
|
11
|
-
import { BootstrapType, LoadedModelsTracker, diffModelVersions, currentModelVersions, } from "./Database";
|
|
11
|
+
import { BootstrapType, LoadedModelsTracker, diffModelVersions, currentModelVersions, } from "./Database.js";
|
|
12
12
|
export class MemoryAdapter {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.meta = null;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Also computes a schemaHash — a fingerprint of all models and their properties.
|
|
9
9
|
* If the hash changes between sessions, the local IndexedDB needs a migration.
|
|
10
10
|
*/
|
|
11
|
-
import { type ModelMeta, type PropertyMeta, type CoveringPath } from "./types";
|
|
11
|
+
import { type ModelMeta, type PropertyMeta, type CoveringPath } from "./types.js";
|
|
12
12
|
declare class ModelRegistryImpl {
|
|
13
13
|
private models;
|
|
14
14
|
private cachedHash;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Also computes a schemaHash — a fingerprint of all models and their properties.
|
|
9
9
|
* If the hash changes between sessions, the local IndexedDB needs a migration.
|
|
10
10
|
*/
|
|
11
|
-
import { hashString } from "./hash";
|
|
12
|
-
import { LoadStrategy, PropertyType, } from "./types";
|
|
11
|
+
import { hashString } from "./hash.js";
|
|
12
|
+
import { LoadStrategy, PropertyType, } from "./types.js";
|
|
13
13
|
class ModelRegistryImpl {
|
|
14
14
|
constructor() {
|
|
15
15
|
this.models = new Map();
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Writes to IDB and upserts into the ObjectPool — no sync state management.
|
|
4
4
|
* Ephemeral models skip IDB and are only held in the pool.
|
|
5
5
|
*/
|
|
6
|
-
import type { StorageAdapter } from "./Database";
|
|
7
|
-
import { ObjectPool } from "./ObjectPool";
|
|
8
|
-
import { BaseSSEConnection, type SSEClientFactory, type SSEErrorReporter } from "./BaseSSEConnection";
|
|
6
|
+
import type { StorageAdapter } from "./Database.js";
|
|
7
|
+
import { ObjectPool } from "./ObjectPool.js";
|
|
8
|
+
import { BaseSSEConnection, type SSEClientFactory, type SSEEndpoint, type SSEErrorReporter } from "./BaseSSEConnection.js";
|
|
9
9
|
export interface ModelUpdate {
|
|
10
10
|
modelName: string;
|
|
11
11
|
modelId: string;
|
|
@@ -23,7 +23,7 @@ export declare class ModelStream extends BaseSSEConnection {
|
|
|
23
23
|
private transform?;
|
|
24
24
|
private updateQueue;
|
|
25
25
|
private processing;
|
|
26
|
-
constructor(url:
|
|
26
|
+
constructor(url: SSEEndpoint, database: StorageAdapter, pool: ObjectPool, onStatusChange?: ((connected: boolean) => void) | undefined, sseClientFactory?: SSEClientFactory, transform?: ModelStreamMessageTransform | undefined, reportError?: SSEErrorReporter);
|
|
27
27
|
disconnect(): void;
|
|
28
28
|
protected onOpen(): void;
|
|
29
29
|
protected onClose(): void;
|
package/dist/core/ModelStream.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Writes to IDB and upserts into the ObjectPool — no sync state management.
|
|
4
4
|
* Ephemeral models skip IDB and are only held in the pool.
|
|
5
5
|
*/
|
|
6
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
7
|
-
import { BaseSSEConnection, } from "./BaseSSEConnection";
|
|
8
|
-
import { LoadStrategy } from "./types";
|
|
6
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
7
|
+
import { BaseSSEConnection, } from "./BaseSSEConnection.js";
|
|
8
|
+
import { LoadStrategy } from "./types.js";
|
|
9
9
|
export class ModelStream extends BaseSSEConnection {
|
|
10
10
|
constructor(url, database, pool, onStatusChange, sseClientFactory, transform, reportError) {
|
|
11
11
|
super(url, sseClientFactory, reportError);
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* adds, updates, or removes an instance of that type, all subscribers
|
|
12
12
|
* are notified and their components re-render.
|
|
13
13
|
*/
|
|
14
|
-
import type { BaseModel } from "./BaseModel";
|
|
15
|
-
import { type ModelMeta } from "./types";
|
|
14
|
+
import type { BaseModel } from "./BaseModel.js";
|
|
15
|
+
import { type ModelMeta } from "./types.js";
|
|
16
16
|
type Listener = () => void;
|
|
17
17
|
/**
|
|
18
18
|
* Read a dynamic property off a model instance. The single bridge between
|
package/dist/core/ObjectPool.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* are notified and their components re-render.
|
|
13
13
|
*/
|
|
14
14
|
import { createAtom, runInAction } from "mobx";
|
|
15
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
16
|
-
import { PropertyType } from "./types";
|
|
15
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
16
|
+
import { PropertyType } from "./types.js";
|
|
17
17
|
/**
|
|
18
18
|
* Read a dynamic property off a model instance. The single bridge between
|
|
19
19
|
* the typed `BaseModel` shape and the index-string access we need for
|
package/dist/core/Store.d.ts
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* - PartialStore for partial models (on demand)
|
|
7
7
|
* - EphemeralStore for ephemeral models (pool-only, never persisted)
|
|
8
8
|
*/
|
|
9
|
-
import type { BaseModel } from "./BaseModel";
|
|
10
|
-
import type { StorageAdapter } from "./Database";
|
|
11
|
-
import { ObjectPool } from "./ObjectPool";
|
|
12
|
-
import { type ModelMeta } from "./types";
|
|
9
|
+
import type { BaseModel } from "./BaseModel.js";
|
|
10
|
+
import type { StorageAdapter } from "./Database.js";
|
|
11
|
+
import { ObjectPool } from "./ObjectPool.js";
|
|
12
|
+
import { type ModelMeta } from "./types.js";
|
|
13
13
|
export declare abstract class ModelStore {
|
|
14
14
|
protected meta: ModelMeta;
|
|
15
15
|
protected database: StorageAdapter;
|
package/dist/core/Store.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - PartialStore for partial models (on demand)
|
|
7
7
|
* - EphemeralStore for ephemeral models (pool-only, never persisted)
|
|
8
8
|
*/
|
|
9
|
-
import { LoadStrategy } from "./types";
|
|
9
|
+
import { LoadStrategy } from "./types.js";
|
|
10
10
|
export class ModelStore {
|
|
11
11
|
constructor(meta, database, pool) {
|
|
12
12
|
this.meta = meta;
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
* storeManager.getOrLoadCollection("Issue", "teamId", teamId)
|
|
19
19
|
* storeManager.getOrLoadById("DocumentContent", docId)
|
|
20
20
|
*/
|
|
21
|
-
import { ObjectPool } from "./ObjectPool";
|
|
22
|
-
import { BootstrapType, type StorageAdapter, type DatabaseMeta, type PartialIndexEntry } from "./Database";
|
|
23
|
-
import { TransactionQueue, type TransactionSender, type UndoableActionHandlers } from "./TransactionQueue";
|
|
24
|
-
import { type DeltaPacket, type SSEClientFactory, type SyncMessageTransform } from "./SyncConnection";
|
|
25
|
-
import { type ModelStreamMessageTransform } from "./ModelStream";
|
|
26
|
-
import { type IndexBatchFetcher } from "./BatchModelLoader";
|
|
27
|
-
import { BaseModel } from "./BaseModel";
|
|
28
|
-
import { BootstrapPhase, type ModelMeta, type PropertyMeta, type PropertyChange, type FieldTransform, type EngineErrorContext, type EngineErrorHandler, type CommitRouteHandler, type OnModelTouchedHandler } from "./types";
|
|
21
|
+
import { ObjectPool } from "./ObjectPool.js";
|
|
22
|
+
import { BootstrapType, type StorageAdapter, type DatabaseMeta, type PartialIndexEntry } from "./Database.js";
|
|
23
|
+
import { TransactionQueue, type TransactionSender, type UndoableActionHandlers } from "./TransactionQueue.js";
|
|
24
|
+
import { type DeltaPacket, type SSEClientFactory, type SSEEndpoint, type SyncMessageTransform } from "./SyncConnection.js";
|
|
25
|
+
import { type ModelStreamMessageTransform } from "./ModelStream.js";
|
|
26
|
+
import { type IndexBatchFetcher } from "./BatchModelLoader.js";
|
|
27
|
+
import { BaseModel } from "./BaseModel.js";
|
|
28
|
+
import { BootstrapPhase, type ModelMeta, type PropertyMeta, type PropertyChange, type FieldTransform, type EngineErrorContext, type EngineErrorHandler, type CommitRouteHandler, type OnModelTouchedHandler } from "./types.js";
|
|
29
29
|
/**
|
|
30
30
|
* Thrown when a delete/archive is blocked by an onDelete: "restrict" relationship.
|
|
31
31
|
*
|
|
@@ -68,7 +68,7 @@ export interface BootstrapFetcherOptions extends FetcherContext {
|
|
|
68
68
|
}
|
|
69
69
|
export type BootstrapFetcher = (type: BootstrapType.Full | BootstrapType.Partial, options?: BootstrapFetcherOptions) => Promise<BootstrapResponse>;
|
|
70
70
|
export interface ModelStreamConfig {
|
|
71
|
-
url:
|
|
71
|
+
url: SSEEndpoint;
|
|
72
72
|
onStatusChange?: (connected: boolean) => void;
|
|
73
73
|
/**
|
|
74
74
|
* Use when the backend sends a different envelope than the engine's
|
|
@@ -107,7 +107,10 @@ export type OnDemandConfig = {
|
|
|
107
107
|
export interface TransportConfig {
|
|
108
108
|
bootstrapFetcher: BootstrapFetcher;
|
|
109
109
|
transactionSender?: TransactionSender;
|
|
110
|
-
|
|
110
|
+
/** SSE endpoint for live deltas — a static string, or a thunk evaluated
|
|
111
|
+
* on every (re)connect (so callers can fold in cursors from localStorage,
|
|
112
|
+
* auth tokens in the path, etc., without rebuilding the engine). */
|
|
113
|
+
syncUrl?: SSEEndpoint;
|
|
111
114
|
/**
|
|
112
115
|
* Optional async hook that returns the user's sync-group memberships
|
|
113
116
|
* before any bootstrap fetch runs. The returned groups are append-only
|
|
@@ -624,8 +627,8 @@ export declare class StoreManager<TContext = unknown> {
|
|
|
624
627
|
* draft target's `assign()` doesn't re-trigger a user-facing "first edit".
|
|
625
628
|
* BaseModel guards on `hasModelTouchedHandler` before calling. */
|
|
626
629
|
fireModelTouched(model: BaseModel, modelName: string): void;
|
|
627
|
-
undo(): Promise<import("./TransactionQueue").UndoResult | null>;
|
|
628
|
-
redo(): Promise<import("./TransactionQueue").UndoResult | null>;
|
|
630
|
+
undo(): Promise<import("./TransactionQueue.js").UndoResult | null>;
|
|
631
|
+
redo(): Promise<import("./TransactionQueue.js").UndoResult | null>;
|
|
629
632
|
/**
|
|
630
633
|
* Run a remote side-effect that returns a `changeLogId`, and record it on
|
|
631
634
|
* the undo stack so the next `undo()` invokes the consumer's
|
|
@@ -829,7 +832,15 @@ export declare class StoreManager<TContext = unknown> {
|
|
|
829
832
|
* cleared here because the schema-mismatch path keeps coverage entries;
|
|
830
833
|
* the `fullyLoadedModels` mirror stays consistent with that. */
|
|
831
834
|
private resetPoolState;
|
|
835
|
+
/** Routine cleanup (e.g. React unmount): stop sync, clear the object pool,
|
|
836
|
+
* and `close()` the persistence layer — persisted data is preserved so the
|
|
837
|
+
* next load can do a fast partial/local bootstrap. Not a logout. */
|
|
832
838
|
teardown(): Promise<void>;
|
|
839
|
+
/** Logout / account switch: stop sync, clear the object pool, and `destroy()`
|
|
840
|
+
* the persistence layer — permanently wiping its data, whether persistence is
|
|
841
|
+
* IndexedDB or in-memory. Unlike {@link teardown}, nothing survives. */
|
|
842
|
+
destroy(): Promise<void>;
|
|
843
|
+
private shutdown;
|
|
833
844
|
/** Debounced reconnect for SSE when `loadedModels` mutates. A burst of
|
|
834
845
|
* transitions in the same tick (or across awaited writes in the same
|
|
835
846
|
* async chain) coalesces into a single reconnect. setTimeout — not
|
|
@@ -18,18 +18,18 @@
|
|
|
18
18
|
* storeManager.getOrLoadCollection("Issue", "teamId", teamId)
|
|
19
19
|
* storeManager.getOrLoadById("DocumentContent", docId)
|
|
20
20
|
*/
|
|
21
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
22
|
-
import { DEFAULT_TRANSIENT_INDEX_DEPTH } from "./types";
|
|
23
|
-
import { ObjectPool, prop, readFk } from "./ObjectPool";
|
|
24
|
-
import { Database, BootstrapType, } from "./Database";
|
|
25
|
-
import { FullStore, PartialStore, EphemeralStore, } from "./Store";
|
|
26
|
-
import { TransactionQueue, } from "./TransactionQueue";
|
|
27
|
-
import { SyncConnection, encodeCsvList, createBrowserSSEFactory, } from "./SyncConnection";
|
|
28
|
-
import { ModelStream } from "./ModelStream";
|
|
29
|
-
import { BatchModelLoader } from "./BatchModelLoader";
|
|
30
|
-
import { COMPOUND_FETCH_THRESHOLD, wrapCompoundFetcher, } from "./CompoundIndexFetcher";
|
|
31
|
-
import { BaseModel } from "./BaseModel";
|
|
32
|
-
import { BootstrapPhase, LoadStrategy, PropertyType, toError, } from "./types";
|
|
21
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
22
|
+
import { DEFAULT_TRANSIENT_INDEX_DEPTH } from "./types.js";
|
|
23
|
+
import { ObjectPool, prop, readFk } from "./ObjectPool.js";
|
|
24
|
+
import { Database, BootstrapType, } from "./Database.js";
|
|
25
|
+
import { FullStore, PartialStore, EphemeralStore, } from "./Store.js";
|
|
26
|
+
import { TransactionQueue, } from "./TransactionQueue.js";
|
|
27
|
+
import { SyncConnection, encodeCsvList, createBrowserSSEFactory, } from "./SyncConnection.js";
|
|
28
|
+
import { ModelStream } from "./ModelStream.js";
|
|
29
|
+
import { BatchModelLoader } from "./BatchModelLoader.js";
|
|
30
|
+
import { COMPOUND_FETCH_THRESHOLD, wrapCompoundFetcher, } from "./CompoundIndexFetcher.js";
|
|
31
|
+
import { BaseModel } from "./BaseModel.js";
|
|
32
|
+
import { BootstrapPhase, LoadStrategy, PropertyType, toError, } from "./types.js";
|
|
33
33
|
/**
|
|
34
34
|
* Thrown when a delete/archive is blocked by an onDelete: "restrict" relationship.
|
|
35
35
|
*
|
|
@@ -1980,7 +1980,19 @@ export class StoreManager {
|
|
|
1980
1980
|
this.inflightFullLoads.clear();
|
|
1981
1981
|
this.seededSyncGroups = [];
|
|
1982
1982
|
}
|
|
1983
|
+
/** Routine cleanup (e.g. React unmount): stop sync, clear the object pool,
|
|
1984
|
+
* and `close()` the persistence layer — persisted data is preserved so the
|
|
1985
|
+
* next load can do a fast partial/local bootstrap. Not a logout. */
|
|
1983
1986
|
async teardown() {
|
|
1987
|
+
await this.shutdown(false);
|
|
1988
|
+
}
|
|
1989
|
+
/** Logout / account switch: stop sync, clear the object pool, and `destroy()`
|
|
1990
|
+
* the persistence layer — permanently wiping its data, whether persistence is
|
|
1991
|
+
* IndexedDB or in-memory. Unlike {@link teardown}, nothing survives. */
|
|
1992
|
+
async destroy() {
|
|
1993
|
+
await this.shutdown(true);
|
|
1994
|
+
}
|
|
1995
|
+
async shutdown(destroyData) {
|
|
1984
1996
|
this.stopped = true;
|
|
1985
1997
|
BaseModel.storeManager = null;
|
|
1986
1998
|
this.loadedModelsUnsub?.();
|
|
@@ -1998,7 +2010,7 @@ export class StoreManager {
|
|
|
1998
2010
|
this.transactionQueue.destroy();
|
|
1999
2011
|
this.indexBatchLoader?.dispose();
|
|
2000
2012
|
this.indexBatchLoader = null;
|
|
2001
|
-
await this.database.close();
|
|
2013
|
+
await (destroyData ? this.database.destroy() : this.database.close());
|
|
2002
2014
|
this.objectPool.clear();
|
|
2003
2015
|
this.stores.clear();
|
|
2004
2016
|
this.partialIndexCoverage.clear();
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
* `BaseModel.hydrate` dispatches FK changes for in-pool models. SyncConnection
|
|
22
22
|
* only has to mutate the pool; parent collections track changes themselves.
|
|
23
23
|
*/
|
|
24
|
-
import type { StorageAdapter } from "./Database";
|
|
25
|
-
import { ObjectPool } from "./ObjectPool";
|
|
26
|
-
import { TransactionQueue } from "./TransactionQueue";
|
|
27
|
-
import { BaseSSEConnection, type SSEClientFactory, type SSEErrorReporter } from "./BaseSSEConnection";
|
|
28
|
-
export { type SSEClient, type SSEClientFactory, type SSEErrorReporter, createBrowserSSEFactory, } from "./BaseSSEConnection";
|
|
24
|
+
import type { StorageAdapter } from "./Database.js";
|
|
25
|
+
import { ObjectPool } from "./ObjectPool.js";
|
|
26
|
+
import { TransactionQueue } from "./TransactionQueue.js";
|
|
27
|
+
import { BaseSSEConnection, type SSEClientFactory, type SSEEndpoint, type SSEErrorReporter } from "./BaseSSEConnection.js";
|
|
28
|
+
export { type SSEClient, type SSEClientFactory, type SSEEndpoint, type SSEErrorReporter, createBrowserSSEFactory, } from "./BaseSSEConnection.js";
|
|
29
29
|
/**
|
|
30
30
|
* Encode each element then comma-join — the right shape for a list-of-
|
|
31
31
|
* strings inside a URL query parameter or a stable cache key. Commas
|
|
@@ -92,7 +92,7 @@ export declare class SyncConnection extends BaseSSEConnection {
|
|
|
92
92
|
* gate, which doesn't see `getOrLoadAll`'s sentinel coverage. */
|
|
93
93
|
private isModelFullyLoaded?;
|
|
94
94
|
private recordInflightDelete?;
|
|
95
|
-
constructor(url:
|
|
95
|
+
constructor(url: SSEEndpoint, database: StorageAdapter, pool: ObjectPool, queue: TransactionQueue, opts?: SyncConnectionOptions);
|
|
96
96
|
protected buildUrl(): string;
|
|
97
97
|
protected onMessage(data: string): void;
|
|
98
98
|
protected onReconnect(): void;
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
* `BaseModel.hydrate` dispatches FK changes for in-pool models. SyncConnection
|
|
22
22
|
* only has to mutate the pool; parent collections track changes themselves.
|
|
23
23
|
*/
|
|
24
|
-
import { ModelRegistry } from "./ModelRegistry";
|
|
25
|
-
import { LoadStrategy, PropertyType } from "./types";
|
|
26
|
-
import { BaseSSEConnection, } from "./BaseSSEConnection";
|
|
24
|
+
import { ModelRegistry } from "./ModelRegistry.js";
|
|
25
|
+
import { LoadStrategy, PropertyType } from "./types.js";
|
|
26
|
+
import { BaseSSEConnection, } from "./BaseSSEConnection.js";
|
|
27
27
|
// Re-export so existing imports from "@zerodrift/SyncConnection" keep working.
|
|
28
|
-
export { createBrowserSSEFactory, } from "./BaseSSEConnection";
|
|
28
|
+
export { createBrowserSSEFactory, } from "./BaseSSEConnection.js";
|
|
29
29
|
/** How many syncIds back to retain in the SyncAction store before pruning.
|
|
30
30
|
* Covers short offline gaps where a persisted pending tx asks "was my
|
|
31
31
|
* target deleted while I was away?" on next reconnect. */
|
|
@@ -67,11 +67,8 @@ export class SyncConnection extends BaseSSEConnection {
|
|
|
67
67
|
const meta = this.database.currentMeta;
|
|
68
68
|
const lastSyncId = meta?.lastSyncId ?? 0;
|
|
69
69
|
const syncGroups = encodeCsvList(meta?.subscribedSyncGroups ?? []);
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// Ephemeral, see ModelRegistry) with adapter-tracked loadedModels.
|
|
73
|
-
// Sort for a stable URL — equivalent sets must produce identical URLs
|
|
74
|
-
// so the engine doesn't churn reconnects when iteration order shifts.
|
|
70
|
+
// Sort the union — equivalent sets must produce identical URLs so the
|
|
71
|
+
// engine doesn't churn reconnects when iteration order shifts.
|
|
75
72
|
const subscribed = [
|
|
76
73
|
...new Set([
|
|
77
74
|
...ModelRegistry.alwaysSubscribedModelNames(),
|
|
@@ -79,7 +76,11 @@ export class SyncConnection extends BaseSSEConnection {
|
|
|
79
76
|
]),
|
|
80
77
|
].sort();
|
|
81
78
|
const onlyModels = subscribed.length > 0 ? `&onlyModels=${encodeCsvList(subscribed)}` : "";
|
|
82
|
-
|
|
79
|
+
const base = this.resolveUrl();
|
|
80
|
+
// Thunk endpoints often already carry query params (tenant, cursor, …);
|
|
81
|
+
// pick `&` when the base is already in query-string mode.
|
|
82
|
+
const sep = base.includes("?") ? "&" : "?";
|
|
83
|
+
return `${base}${sep}lastSyncId=${lastSyncId}&syncGroups=${syncGroups}${onlyModels}`;
|
|
83
84
|
}
|
|
84
85
|
onMessage(data) {
|
|
85
86
|
const raw = JSON.parse(data);
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Rebasing (UpdateTransaction): when a delta packet conflicts with our local
|
|
8
8
|
* change, the server value becomes our new baseline and our value is re-applied.
|
|
9
9
|
*/
|
|
10
|
-
import type { BaseModel } from "./BaseModel";
|
|
11
|
-
import { TransactionState, type PropertyChange } from "./types";
|
|
10
|
+
import type { BaseModel } from "./BaseModel.js";
|
|
11
|
+
import { TransactionState, type PropertyChange } from "./types.js";
|
|
12
12
|
export declare abstract class BaseTransaction {
|
|
13
13
|
readonly id: `${string}-${string}-${string}-${string}-${string}`;
|
|
14
14
|
readonly modelId: string;
|
package/dist/core/Transaction.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Rebasing (UpdateTransaction): when a delta packet conflicts with our local
|
|
8
8
|
* change, the server value becomes our new baseline and our value is re-applied.
|
|
9
9
|
*/
|
|
10
|
-
import { TransactionState } from "./types";
|
|
10
|
+
import { TransactionState } from "./types.js";
|
|
11
11
|
export class BaseTransaction {
|
|
12
12
|
constructor(modelId, modelName) {
|
|
13
13
|
this.id = crypto.randomUUID();
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
*
|
|
13
13
|
* The undo stack stores "entries" — either a single tx or a batch of txs.
|
|
14
14
|
*/
|
|
15
|
-
import type { StorageAdapter } from "./Database";
|
|
16
|
-
import { ObjectPool } from "./ObjectPool";
|
|
17
|
-
import { type EngineErrorContext } from "./types";
|
|
18
|
-
import { BaseTransaction, UpdateTransaction, type UndoableAction } from "./Transaction";
|
|
19
|
-
import { type PropertyChange } from "./types";
|
|
20
|
-
import type { BaseModel } from "./BaseModel";
|
|
15
|
+
import type { StorageAdapter } from "./Database.js";
|
|
16
|
+
import { ObjectPool } from "./ObjectPool.js";
|
|
17
|
+
import { type EngineErrorContext } from "./types.js";
|
|
18
|
+
import { BaseTransaction, UpdateTransaction, type UndoableAction } from "./Transaction.js";
|
|
19
|
+
import { type PropertyChange } from "./types.js";
|
|
20
|
+
import type { BaseModel } from "./BaseModel.js";
|
|
21
21
|
export interface BatchResponse {
|
|
22
22
|
success: boolean;
|
|
23
23
|
lastSyncId: number;
|