zerodrift 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +358 -0
  3. package/dist/core/BaseModel.d.ts +76 -0
  4. package/dist/core/BaseModel.js +505 -0
  5. package/dist/core/BaseSSEConnection.d.ts +31 -0
  6. package/dist/core/BaseSSEConnection.js +91 -0
  7. package/dist/core/BatchModelLoader.d.ts +27 -0
  8. package/dist/core/BatchModelLoader.js +70 -0
  9. package/dist/core/CompoundIndexFetcher.d.ts +46 -0
  10. package/dist/core/CompoundIndexFetcher.js +177 -0
  11. package/dist/core/Database.d.ts +303 -0
  12. package/dist/core/Database.js +837 -0
  13. package/dist/core/LazyCollection.d.ts +168 -0
  14. package/dist/core/LazyCollection.js +403 -0
  15. package/dist/core/LazyOwnedCollection.d.ts +35 -0
  16. package/dist/core/LazyOwnedCollection.js +66 -0
  17. package/dist/core/MemoryAdapter.d.ts +67 -0
  18. package/dist/core/MemoryAdapter.js +243 -0
  19. package/dist/core/ModelRegistry.d.ts +64 -0
  20. package/dist/core/ModelRegistry.js +217 -0
  21. package/dist/core/ModelStream.d.ts +33 -0
  22. package/dist/core/ModelStream.js +68 -0
  23. package/dist/core/ObjectPool.d.ts +113 -0
  24. package/dist/core/ObjectPool.js +339 -0
  25. package/dist/core/Store.d.ts +40 -0
  26. package/dist/core/Store.js +73 -0
  27. package/dist/core/StoreManager.d.ts +839 -0
  28. package/dist/core/StoreManager.js +2034 -0
  29. package/dist/core/SyncConnection.d.ts +105 -0
  30. package/dist/core/SyncConnection.js +348 -0
  31. package/dist/core/Transaction.d.ts +114 -0
  32. package/dist/core/Transaction.js +147 -0
  33. package/dist/core/TransactionQueue.d.ts +110 -0
  34. package/dist/core/TransactionQueue.js +601 -0
  35. package/dist/core/decorators.d.ts +66 -0
  36. package/dist/core/decorators.js +278 -0
  37. package/dist/core/hash.d.ts +6 -0
  38. package/dist/core/hash.js +12 -0
  39. package/dist/core/index.d.ts +16 -0
  40. package/dist/core/index.js +18 -0
  41. package/dist/core/internal.d.ts +27 -0
  42. package/dist/core/internal.js +25 -0
  43. package/dist/core/observability.d.ts +21 -0
  44. package/dist/core/observability.js +66 -0
  45. package/dist/core/refAccessors.d.ts +43 -0
  46. package/dist/core/refAccessors.js +80 -0
  47. package/dist/core/serializers.d.ts +2 -0
  48. package/dist/core/serializers.js +2 -0
  49. package/dist/core/types.d.ts +320 -0
  50. package/dist/core/types.js +84 -0
  51. package/dist/react/index.d.ts +82 -0
  52. package/dist/react/index.js +373 -0
  53. package/dist/schema/builders.d.ts +29 -0
  54. package/dist/schema/builders.js +81 -0
  55. package/dist/schema/compile.d.ts +28 -0
  56. package/dist/schema/compile.js +334 -0
  57. package/dist/schema/createStore.d.ts +235 -0
  58. package/dist/schema/createStore.js +264 -0
  59. package/dist/schema/extend.d.ts +46 -0
  60. package/dist/schema/extend.js +6 -0
  61. package/dist/schema/index.d.ts +13 -0
  62. package/dist/schema/index.js +8 -0
  63. package/dist/schema/infer.d.ts +102 -0
  64. package/dist/schema/infer.js +1 -0
  65. package/dist/schema/types.d.ts +76 -0
  66. package/dist/schema/types.js +1 -0
  67. package/dist/schema/zod.d.ts +90 -0
  68. package/dist/schema/zod.js +101 -0
  69. package/package.json +99 -0
@@ -0,0 +1,320 @@
1
+ import type { BaseModel } from "./BaseModel";
2
+ /** How a model is loaded into the client. Choose by *when* you need its rows. */
3
+ export declare enum LoadStrategy {
4
+ /** Loaded during bootstrap, fully resident, kept current by SSE. The
5
+ * default — pick this unless a model is large or rarely needed. */
6
+ Eager = "eager",
7
+ /** Not in bootstrap. The whole table is fetched the first time any of it
8
+ * is requested, then kept current by SSE. For rarely-opened tables. */
9
+ Lazy = "lazy",
10
+ /** Not in bootstrap. Only the subset reached via an index/relation is
11
+ * fetched on demand and tracked by partial-index coverage. For large
12
+ * tables you only ever view a slice of (e.g. comments per issue). */
13
+ Partial = "partial",
14
+ /** Persisted to IDB but never synced — local-only state (drafts, settings)
15
+ * that must survive reload but never leaves the device. */
16
+ LocalOnly = "localOnly",
17
+ /** Pool-only — never persisted to IDB. Fed purely by SSE / ModelStream.
18
+ * For transient data like live metrics or computed results. */
19
+ Ephemeral = "ephemeral"
20
+ }
21
+ /** The kind of data a property holds. Determines how it's stored and observed. */
22
+ export declare enum PropertyType {
23
+ /** A regular persisted property owned by the model (e.g. Issue.title). */
24
+ Property = "property",
25
+ /** Like Property but NOT saved to IndexedDB (e.g. User.lastInteraction). */
26
+ EphemeralProperty = "ephemeralProperty",
27
+ /** A foreign key ID pointing to another model (e.g. Issue.teamId). Persisted and indexed. */
28
+ Reference = "reference",
29
+ /** A virtual getter/setter that resolves a Reference ID to the actual model instance. Not persisted. */
30
+ ReferenceModel = "referenceModel",
31
+ /** A one-to-many relationship from the parent side (e.g. Team.templates). */
32
+ ReferenceCollection = "referenceCollection",
33
+ /** Inverse of a Reference. Deleted when the referenced model is deleted. */
34
+ BackReference = "backReference",
35
+ /** A many-to-many relationship stored as an array of IDs (e.g. Project.memberIds). */
36
+ ReferenceArray = "referenceArray",
37
+ /** A collection where the parent owns an array of IDs (e.g. Team.issueIds → issues). */
38
+ OwnedCollection = "ownedCollection"
39
+ }
40
+ /** Progress phases during the bootstrap pipeline. Used for loading indicators. */
41
+ export declare enum BootstrapPhase {
42
+ Idle = "idle",
43
+ CreatingStores = "creatingStores",
44
+ ConnectingDatabase = "connectingDatabase",
45
+ DeterminingBootstrapType = "determiningBootstrapType",
46
+ Fetching = "fetching",
47
+ WritingToDatabase = "writingToDatabase",
48
+ Hydrating = "hydrating",
49
+ ConnectingSync = "connectingSync",
50
+ Ready = "ready",
51
+ Error = "error"
52
+ }
53
+ /** Transaction lifecycle states. */
54
+ export declare enum TransactionState {
55
+ /** Created but not yet sent to the server. */
56
+ Pending = "pending",
57
+ /** Sent to the server, waiting for response. */
58
+ Executing = "executing",
59
+ /** Server acknowledged, but the matching delta packet hasn't arrived yet. */
60
+ CompletedButUnsynced = "completedButUnsynced",
61
+ /** Delta packet received. Fully done. */
62
+ Completed = "completed",
63
+ /** Server rejected the transaction. */
64
+ Failed = "failed"
65
+ }
66
+ /**
67
+ * Default `transientIndexDepth` — how deep `RefCollection`s walk the parent's
68
+ * outgoing FK chain to auto-derive covering axes when no explicit value is
69
+ * passed via `StoreManagerConfig.transientIndexDepth`. See that field for
70
+ * the trade-offs. Lives in types.ts so both the StoreManager getter and the
71
+ * BaseModel fallback can reference the same constant without a cycle.
72
+ */
73
+ export declare const DEFAULT_TRANSIENT_INDEX_DEPTH = 3;
74
+ /**
75
+ * One auto-derived covering axis for a `RefCollection`. Each path encodes
76
+ * how to reach a value on the parent (or a deeper ancestor) that matches an
77
+ * indexed FK on the child. Resolved at `RefCollection.hydrate()`:
78
+ *
79
+ * walk the `hops` chain through the pool; the last hop's `fk` value is
80
+ * used as the covering query — `Comment[axis = resolvedValue]`.
81
+ *
82
+ * For depth 1 (`hops.length === 1`), resolution is `readFk(parent, hops[0].fk)`.
83
+ * For depth 2+, intermediate hops resolve through `pool.getById(throughModel, id)`.
84
+ */
85
+ export interface CoveringPath {
86
+ /** The FK name on the child model — also `hops[hops.length - 1].fk`. */
87
+ axis: string;
88
+ /** Chain of FK lookups starting from the parent. The last hop is the
89
+ * leaf; preceding hops are pool look-ups that resolve to the next model. */
90
+ hops: {
91
+ fk: string;
92
+ throughModel: string;
93
+ }[];
94
+ }
95
+ /** Metadata about a single property, stored in the ModelRegistry. */
96
+ export interface PropertyMeta {
97
+ name: string;
98
+ type: PropertyType;
99
+ lazy?: boolean;
100
+ nullable?: boolean;
101
+ indexed?: boolean;
102
+ serializer?: (value: unknown) => unknown;
103
+ deserializer?: (value: unknown) => unknown;
104
+ referenceTo?: string;
105
+ inverseOf?: string;
106
+ idField?: string;
107
+ idsField?: string;
108
+ onDelete?: OnDelete;
109
+ /**
110
+ * Additional FK axes a `@*ReferenceCollection` covers beyond `inverseOf`.
111
+ * Each entry names a property on the *parent* model whose value is also a
112
+ * partial-index key on the child. At hydrate time the collection reads
113
+ * `parent[axis]` and emits an extra query, so the load fetches the union of
114
+ * (inverseOf == parent.id) plus each covering axis. Used for sync-group
115
+ * scoping and similar multi-axis lazy queries.
116
+ */
117
+ coveringIndexes?: string[];
118
+ }
119
+ /** Metadata about a model class, stored in the ModelRegistry. */
120
+ export interface ModelMeta {
121
+ name: string;
122
+ loadStrategy: LoadStrategy;
123
+ usedForPartialIndexes: boolean;
124
+ properties: Map<string, PropertyMeta>;
125
+ actions: Set<string>;
126
+ computedProps: Set<string>;
127
+ ctor: new () => BaseModel;
128
+ schemaVersion: number;
129
+ }
130
+ /**
131
+ * Transforms a value at the moment it's assigned via the property setter.
132
+ * Receives the model instance so it can read sibling fields, plus the live
133
+ * `StoreManager` context for tenant/user-scoped rewrites. Output replaces
134
+ * the original assignment value before the MobX box is updated.
135
+ */
136
+ export type FieldTransform<TContext = unknown> = (value: unknown, instance: BaseModel, ctx: TContext | undefined) => unknown;
137
+ /** Tracks what changed on a property: old value and new value. */
138
+ export interface PropertyChange {
139
+ oldValue: unknown;
140
+ newValue: unknown;
141
+ }
142
+ /**
143
+ * User-initiated commit intent passed to `StoreManagerConfig.routeCommit`.
144
+ * Discriminates on `kind` so adopters can route different ops.
145
+ */
146
+ export type CommitIntent = {
147
+ kind: "create";
148
+ model: BaseModel;
149
+ modelName: string;
150
+ } | {
151
+ kind: "update";
152
+ model: BaseModel;
153
+ modelName: string;
154
+ changes: Record<string, PropertyChange>;
155
+ /**
156
+ * The model's serialized state *before* this edit's setters ran (the
157
+ * live instance is already mutated by the time `routeCommit` fires).
158
+ * Lazy — serialization only happens if you call it. Memoized per
159
+ * intent, so repeated calls are free. Use it to seed a redirect
160
+ * target with the pre-edit baseline.
161
+ */
162
+ previousData: () => Record<string, unknown>;
163
+ };
164
+ /**
165
+ * Return value from `routeCommit`. Returning nothing (`void`) lets the engine
166
+ * commit the original intent. `"skip"` suppresses it. A `redirect` object asks
167
+ * the engine to apply/enqueue the intent against a different model id.
168
+ */
169
+ export type CommitRouteResult = "skip" | {
170
+ action: "redirect";
171
+ modelName?: string;
172
+ modelId: string;
173
+ restoreOriginal?: boolean;
174
+ };
175
+ export type CommitRouteHandler = (intent: CommitIntent) => CommitRouteResult | void;
176
+ /**
177
+ * Fired the instant a clean model becomes dirty — its first pending change
178
+ * since the last save/discard. Runs synchronously inside the property setter,
179
+ * before any `save()`. Use it for eager side-effects that should not wait for
180
+ * a commit (e.g. materializing a draft-layer scaffold so the UI can switch
181
+ * immediately). The actual write is still routed at `save()` via
182
+ * `routeCommit`. NOT fired during the engine's own redirect replay.
183
+ */
184
+ export type OnModelTouchedHandler = (model: BaseModel, modelName: string) => void;
185
+ /** Behavior when the parent of a `Reference` / `BackReference` is deleted. */
186
+ export type OnDelete = "cascade" | "nullify" | "restrict";
187
+ /** Object pool interface as seen from BaseModel. Avoids importing ObjectPool directly. */
188
+ export interface IObjectPool {
189
+ getById<T extends BaseModel = BaseModel>(modelName: string, id: string): T | undefined;
190
+ put(modelName: string, instance: BaseModel): void;
191
+ /**
192
+ * Notify the pool that a child's foreign-key property changed so it can
193
+ * detach from the old parent's RefCollection / BackRef and attach to the
194
+ * new one. Called by BaseModel from `propertyChanged` and from `hydrate`
195
+ * when an in-pool model receives a delta-driven box update.
196
+ */
197
+ notifyReferenceChange(child: BaseModel, childModelName: string, fkName: string, oldId: string | null, newId: string | null): void;
198
+ /**
199
+ * Register a tracked MobX dependency on the pool entry for `(modelName, id)`.
200
+ * The pool fires the corresponding atom on insert / removal / identity swap,
201
+ * so observers reading the entry through `@Reference` re-evaluate even when
202
+ * the holder's foreign key didn't change.
203
+ */
204
+ trackModel(modelName: string, id: string): void;
205
+ }
206
+ /** Store manager interface as seen from BaseModel. Avoids importing StoreManager directly. */
207
+ export interface IStoreManager {
208
+ readonly objectPool: IObjectPool;
209
+ /** How deep `RefCollection` walks the parent FK graph to auto-derive
210
+ * covering axes. Configurable via `StoreManagerConfig.transientIndexDepth`
211
+ * (default 3). Capped at the registry-walk implementation's max depth. */
212
+ readonly transientIndexDepth: number;
213
+ commitCreate(model: BaseModel): void;
214
+ commitUpdate(modelId: string, modelName: string, changes: Record<string, PropertyChange>): void;
215
+ getOrLoadCollection(modelName: string, key: string, value: string): Promise<BaseModel[]>;
216
+ getOrLoadByIds(modelName: string, ids: string[]): Promise<BaseModel[]>;
217
+ getOrLoadById(modelName: string, id: string): Promise<BaseModel | null>;
218
+ emitError(err: unknown, context: EngineErrorContext): void;
219
+ /** Mint a fresh id for a newly-constructed client-side model. Honors
220
+ * `StoreManagerConfig.identifierFn` if configured; otherwise falls back
221
+ * to `crypto.randomUUID()`. */
222
+ mintId(instance: BaseModel): string;
223
+ /** True iff `applyFieldTransforms` registered at least one transform.
224
+ * Setters check this before calling `applyTransform` so the no-config
225
+ * path stays a single boolean read on the assignment hot path. */
226
+ readonly hasFieldTransforms: boolean;
227
+ /** Run any registered field transform for `(instance, propName)` against
228
+ * `value` and return the result (or the value unchanged when no rule
229
+ * applies). The StoreManager owns the cache key shape and the context
230
+ * read so setters don't need to. */
231
+ applyTransform(instance: BaseModel, propName: string, value: unknown): unknown;
232
+ /** @internal Notify the StoreManager that `model` was mutated. Inside
233
+ * an open `atomic()` scope this records the model so the scope can
234
+ * call `save()` on commit or `discardUnsavedChanges()` on rollback.
235
+ * No-op when no atomic scope is active. Called from
236
+ * `BaseModel.propertyChanged`; not part of the public adopter surface. */
237
+ registerAtomicTouch(model: BaseModel): void;
238
+ /** True iff `StoreManagerConfig.onModelTouched` is configured.
239
+ * `BaseModel.propertyChanged` checks this before calling
240
+ * `fireModelTouched` so the no-config path stays a single boolean read
241
+ * on the clean→dirty transition. */
242
+ readonly hasModelTouchedHandler: boolean;
243
+ /** @internal Notify the StoreManager that `model` went from clean to
244
+ * dirty (its first pending change since the last save/discard). Fires
245
+ * `onModelTouched`; suppressed during the engine's own redirect replay.
246
+ * Called from `BaseModel.propertyChanged`. */
247
+ fireModelTouched(model: BaseModel, modelName: string): void;
248
+ }
249
+ /**
250
+ * Tagged union describing where in the engine an error originated. Passed to
251
+ * `StoreManagerConfig.onError` so adopters can route into Sentry/Datadog/console
252
+ * with the right metadata. Each tag carries fields specific to its failure site.
253
+ */
254
+ export type EngineErrorContext = {
255
+ kind: "eagerReferenceLoad";
256
+ modelName: string;
257
+ id: string;
258
+ } | {
259
+ kind: "eagerCollectionLoad";
260
+ modelName: string;
261
+ parentModelName: string;
262
+ parentId: string;
263
+ } | {
264
+ kind: "lazyCollectionLoad";
265
+ modelName: string;
266
+ parentModelName: string;
267
+ parentId: string;
268
+ } | {
269
+ kind: "lazyOwnedCollectionLoad";
270
+ modelName: string;
271
+ } | {
272
+ kind: "lazyBackRefLoad";
273
+ modelName: string;
274
+ parentId: string;
275
+ } | {
276
+ kind: "deferredBootstrap";
277
+ modelNames: string[];
278
+ } | {
279
+ kind: "newModelsBootstrap";
280
+ modelNames: string[];
281
+ } | {
282
+ kind: "transactionDiscarded";
283
+ modelName: string;
284
+ modelId: string;
285
+ action: string;
286
+ reason: "target-deleted";
287
+ } | {
288
+ kind: "syncGroupFetch";
289
+ groups: string[];
290
+ } | {
291
+ kind: "ssePacketParse";
292
+ url: string;
293
+ raw: string;
294
+ } | {
295
+ kind: "sseConstruction";
296
+ url: string;
297
+ } | {
298
+ kind: "transactionSend";
299
+ batchSize: number;
300
+ } | {
301
+ kind: "onSyncGroupDelete";
302
+ groupId: string;
303
+ } | {
304
+ kind: "undoableAction";
305
+ phase: "undo" | "redo";
306
+ changeLogId: string;
307
+ actionType?: string;
308
+ } | {
309
+ kind: "beforeCommit";
310
+ opKind: "create" | "update";
311
+ modelName: string;
312
+ modelId: string;
313
+ } | {
314
+ kind: "onModelTouched";
315
+ modelName: string;
316
+ modelId: string;
317
+ };
318
+ export type EngineErrorHandler = (err: Error, context: EngineErrorContext) => void;
319
+ /** Coerce an `unknown` from a `catch` clause into a real `Error` instance. */
320
+ export declare const toError: (err: unknown) => Error;
@@ -0,0 +1,84 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Enums
3
+ // ---------------------------------------------------------------------------
4
+ /** How a model is loaded into the client. Choose by *when* you need its rows. */
5
+ export var LoadStrategy;
6
+ (function (LoadStrategy) {
7
+ /** Loaded during bootstrap, fully resident, kept current by SSE. The
8
+ * default — pick this unless a model is large or rarely needed. */
9
+ LoadStrategy["Eager"] = "eager";
10
+ /** Not in bootstrap. The whole table is fetched the first time any of it
11
+ * is requested, then kept current by SSE. For rarely-opened tables. */
12
+ LoadStrategy["Lazy"] = "lazy";
13
+ /** Not in bootstrap. Only the subset reached via an index/relation is
14
+ * fetched on demand and tracked by partial-index coverage. For large
15
+ * tables you only ever view a slice of (e.g. comments per issue). */
16
+ LoadStrategy["Partial"] = "partial";
17
+ /** Persisted to IDB but never synced — local-only state (drafts, settings)
18
+ * that must survive reload but never leaves the device. */
19
+ LoadStrategy["LocalOnly"] = "localOnly";
20
+ /** Pool-only — never persisted to IDB. Fed purely by SSE / ModelStream.
21
+ * For transient data like live metrics or computed results. */
22
+ LoadStrategy["Ephemeral"] = "ephemeral";
23
+ })(LoadStrategy || (LoadStrategy = {}));
24
+ /** The kind of data a property holds. Determines how it's stored and observed. */
25
+ export var PropertyType;
26
+ (function (PropertyType) {
27
+ /** A regular persisted property owned by the model (e.g. Issue.title). */
28
+ PropertyType["Property"] = "property";
29
+ /** Like Property but NOT saved to IndexedDB (e.g. User.lastInteraction). */
30
+ PropertyType["EphemeralProperty"] = "ephemeralProperty";
31
+ /** A foreign key ID pointing to another model (e.g. Issue.teamId). Persisted and indexed. */
32
+ PropertyType["Reference"] = "reference";
33
+ /** A virtual getter/setter that resolves a Reference ID to the actual model instance. Not persisted. */
34
+ PropertyType["ReferenceModel"] = "referenceModel";
35
+ /** A one-to-many relationship from the parent side (e.g. Team.templates). */
36
+ PropertyType["ReferenceCollection"] = "referenceCollection";
37
+ /** Inverse of a Reference. Deleted when the referenced model is deleted. */
38
+ PropertyType["BackReference"] = "backReference";
39
+ /** A many-to-many relationship stored as an array of IDs (e.g. Project.memberIds). */
40
+ PropertyType["ReferenceArray"] = "referenceArray";
41
+ /** A collection where the parent owns an array of IDs (e.g. Team.issueIds → issues). */
42
+ PropertyType["OwnedCollection"] = "ownedCollection";
43
+ })(PropertyType || (PropertyType = {}));
44
+ /** Progress phases during the bootstrap pipeline. Used for loading indicators. */
45
+ export var BootstrapPhase;
46
+ (function (BootstrapPhase) {
47
+ BootstrapPhase["Idle"] = "idle";
48
+ BootstrapPhase["CreatingStores"] = "creatingStores";
49
+ BootstrapPhase["ConnectingDatabase"] = "connectingDatabase";
50
+ BootstrapPhase["DeterminingBootstrapType"] = "determiningBootstrapType";
51
+ BootstrapPhase["Fetching"] = "fetching";
52
+ BootstrapPhase["WritingToDatabase"] = "writingToDatabase";
53
+ BootstrapPhase["Hydrating"] = "hydrating";
54
+ BootstrapPhase["ConnectingSync"] = "connectingSync";
55
+ BootstrapPhase["Ready"] = "ready";
56
+ BootstrapPhase["Error"] = "error";
57
+ })(BootstrapPhase || (BootstrapPhase = {}));
58
+ /** Transaction lifecycle states. */
59
+ export var TransactionState;
60
+ (function (TransactionState) {
61
+ /** Created but not yet sent to the server. */
62
+ TransactionState["Pending"] = "pending";
63
+ /** Sent to the server, waiting for response. */
64
+ TransactionState["Executing"] = "executing";
65
+ /** Server acknowledged, but the matching delta packet hasn't arrived yet. */
66
+ TransactionState["CompletedButUnsynced"] = "completedButUnsynced";
67
+ /** Delta packet received. Fully done. */
68
+ TransactionState["Completed"] = "completed";
69
+ /** Server rejected the transaction. */
70
+ TransactionState["Failed"] = "failed";
71
+ })(TransactionState || (TransactionState = {}));
72
+ // ---------------------------------------------------------------------------
73
+ // Constants
74
+ // ---------------------------------------------------------------------------
75
+ /**
76
+ * Default `transientIndexDepth` — how deep `RefCollection`s walk the parent's
77
+ * outgoing FK chain to auto-derive covering axes when no explicit value is
78
+ * passed via `StoreManagerConfig.transientIndexDepth`. See that field for
79
+ * the trade-offs. Lives in types.ts so both the StoreManager getter and the
80
+ * BaseModel fallback can reference the same constant without a cycle.
81
+ */
82
+ export const DEFAULT_TRANSIENT_INDEX_DEPTH = 3;
83
+ /** Coerce an `unknown` from a `catch` clause into a real `Error` instance. */
84
+ export const toError = (err) => err instanceof Error ? err : new Error(String(err));
@@ -0,0 +1,82 @@
1
+ /**
2
+ * React integration for the Sync Engine.
3
+ *
4
+ * Hooks subscribe to ObjectPool change notifications via useSyncExternalStore,
5
+ * so a delta packet that adds, updates, or removes a model automatically
6
+ * re-renders any component reading it through `useRecord` / `useRecords` /
7
+ * `useRecordsByIndex` (or a relation via `useRelation`).
8
+ */
9
+ import React from "react";
10
+ import { StoreManager, type StoreManagerConfig } from "../core/StoreManager";
11
+ import { BootstrapPhase } from "../core/types";
12
+ import { LazyCollectionBase, BackRef } from "../core/LazyCollection";
13
+ import type { BaseModel } from "../core/BaseModel";
14
+ export interface SyncStatus {
15
+ phase: BootstrapPhase;
16
+ detail?: string;
17
+ error?: string;
18
+ }
19
+ export declare function SyncProvider<TContext = unknown>({ config, context, children, fallback, }: {
20
+ config: StoreManagerConfig<TContext>;
21
+ /** Live context forwarded to `StoreManager.setContext` — consumed by
22
+ * `identifierFn` when minting ids for client-side models. Pushed
23
+ * synchronously so handlers fired in the same commit see the update. */
24
+ context?: TContext;
25
+ children: React.ReactNode;
26
+ /** Shown while bootstrap is in progress. */
27
+ fallback?: React.ReactNode;
28
+ }): import("react/jsx-runtime").JSX.Element | null;
29
+ export declare function useSyncEngine(): {
30
+ sm: StoreManager<any>;
31
+ status: SyncStatus;
32
+ };
33
+ export declare function useBootstrapStatus(): SyncStatus;
34
+ /**
35
+ * Uniform async-resource shape for every load-aware hook (`useRecord`,
36
+ * `useRecords`, `useRecordsByIndex`, `useRelation`). `data` is the payload
37
+ * (`T | null` for a single record, `T[]` for a list); the rest is the
38
+ * lifecycle bag. `isLoaded` is true once the first resolve settled without
39
+ * error (a pool hit counts as resolved from frame zero).
40
+ */
41
+ export interface AsyncResource<T> {
42
+ data: T;
43
+ isLoading: boolean;
44
+ isLoaded: boolean;
45
+ error: Error | null;
46
+ reload: () => Promise<void>;
47
+ }
48
+ /** Returns `store.batch` — the sync overload yields the `batchId` string,
49
+ * the async overload a `Promise<string>`. */
50
+ export declare function useBatch(): StoreManager["batch"];
51
+ export declare function useUndoRedo(): {
52
+ undo: () => Promise<import("../core").UndoResult | null>;
53
+ redo: () => Promise<import("../core").UndoResult | null>;
54
+ canUndo: boolean;
55
+ canRedo: boolean;
56
+ };
57
+ export declare function useRelation<T extends BaseModel = BaseModel>(relation: LazyCollectionBase<T> | null | undefined): AsyncResource<T[]>;
58
+ export declare function useRelation<T extends BaseModel = BaseModel>(relation: BackRef<T> | null | undefined): AsyncResource<T | null>;
59
+ import { type EntityNamespace, type RecordWithExtensions } from "../schema/createStore";
60
+ import type { ExtensionDescriptor } from "../schema/extend";
61
+ import type { EntityKey, IndexedFieldKeys } from "../schema/infer";
62
+ import type { SchemaDef } from "../schema/types";
63
+ type AnyNamespace = EntityNamespace<any, any, any>;
64
+ type ModelCtor<T extends BaseModel = BaseModel> = abstract new (...args: any[]) => T;
65
+ type Handle = AnyNamespace | ModelCtor;
66
+ type RecordOfNamespace<NS> = NS extends EntityNamespace<infer S, infer K, infer Exts> ? S extends SchemaDef ? K extends EntityKey<S> ? Exts extends readonly ExtensionDescriptor<S>[] ? RecordWithExtensions<S, K, Exts> : never : never : never : never;
67
+ type IndexKeyOfNamespace<NS> = NS extends EntityNamespace<infer S, infer K, infer _Exts> ? S extends SchemaDef ? K extends EntityKey<S> ? IndexedFieldKeys<S, K> : never : never : never;
68
+ /** Record type for a handle: the class instance type, or the schema's typed
69
+ * projection for a namespace. Ctor is checked first — a namespace is a plain
70
+ * object and never matches `ModelCtor`. */
71
+ type RecordOf<H> = H extends ModelCtor<infer T> ? T : RecordOfNamespace<H>;
72
+ /** Index-key constraint for a handle: schema `.indexed()` fields for a
73
+ * namespace, unconstrained `string` for a decorator class. */
74
+ type IndexKeyOf<H> = H extends ModelCtor ? string : IndexKeyOfNamespace<H>;
75
+ /** Reactive single record by id. Pool-first sync read; async backfill on miss. */
76
+ export declare function useRecord<H extends Handle>(handle: H, id: string | null | undefined): AsyncResource<RecordOf<H> | null>;
77
+ /** Reactive list of records, optionally filtered to (and ordered by) `ids`. */
78
+ export declare function useRecords<H extends Handle>(handle: H, ids?: string[] | null): AsyncResource<RecordOf<H>[]>;
79
+ /** Reactive list of records matching one value, or any of several, on a
80
+ * foreign-key index. */
81
+ export declare function useRecordsByIndex<H extends Handle>(handle: H, indexKey: IndexKeyOf<H>, value: string | readonly string[] | null | undefined): AsyncResource<RecordOf<H>[]>;
82
+ export {};