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.
- package/LICENSE +21 -0
- package/README.md +358 -0
- package/dist/core/BaseModel.d.ts +76 -0
- package/dist/core/BaseModel.js +505 -0
- package/dist/core/BaseSSEConnection.d.ts +31 -0
- package/dist/core/BaseSSEConnection.js +91 -0
- package/dist/core/BatchModelLoader.d.ts +27 -0
- package/dist/core/BatchModelLoader.js +70 -0
- package/dist/core/CompoundIndexFetcher.d.ts +46 -0
- package/dist/core/CompoundIndexFetcher.js +177 -0
- package/dist/core/Database.d.ts +303 -0
- package/dist/core/Database.js +837 -0
- package/dist/core/LazyCollection.d.ts +168 -0
- package/dist/core/LazyCollection.js +403 -0
- package/dist/core/LazyOwnedCollection.d.ts +35 -0
- package/dist/core/LazyOwnedCollection.js +66 -0
- package/dist/core/MemoryAdapter.d.ts +67 -0
- package/dist/core/MemoryAdapter.js +243 -0
- package/dist/core/ModelRegistry.d.ts +64 -0
- package/dist/core/ModelRegistry.js +217 -0
- package/dist/core/ModelStream.d.ts +33 -0
- package/dist/core/ModelStream.js +68 -0
- package/dist/core/ObjectPool.d.ts +113 -0
- package/dist/core/ObjectPool.js +339 -0
- package/dist/core/Store.d.ts +40 -0
- package/dist/core/Store.js +73 -0
- package/dist/core/StoreManager.d.ts +839 -0
- package/dist/core/StoreManager.js +2034 -0
- package/dist/core/SyncConnection.d.ts +105 -0
- package/dist/core/SyncConnection.js +348 -0
- package/dist/core/Transaction.d.ts +114 -0
- package/dist/core/Transaction.js +147 -0
- package/dist/core/TransactionQueue.d.ts +110 -0
- package/dist/core/TransactionQueue.js +601 -0
- package/dist/core/decorators.d.ts +66 -0
- package/dist/core/decorators.js +278 -0
- package/dist/core/hash.d.ts +6 -0
- package/dist/core/hash.js +12 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.js +18 -0
- package/dist/core/internal.d.ts +27 -0
- package/dist/core/internal.js +25 -0
- package/dist/core/observability.d.ts +21 -0
- package/dist/core/observability.js +66 -0
- package/dist/core/refAccessors.d.ts +43 -0
- package/dist/core/refAccessors.js +80 -0
- package/dist/core/serializers.d.ts +2 -0
- package/dist/core/serializers.js +2 -0
- package/dist/core/types.d.ts +320 -0
- package/dist/core/types.js +84 -0
- package/dist/react/index.d.ts +82 -0
- package/dist/react/index.js +373 -0
- package/dist/schema/builders.d.ts +29 -0
- package/dist/schema/builders.js +81 -0
- package/dist/schema/compile.d.ts +28 -0
- package/dist/schema/compile.js +334 -0
- package/dist/schema/createStore.d.ts +235 -0
- package/dist/schema/createStore.js +264 -0
- package/dist/schema/extend.d.ts +46 -0
- package/dist/schema/extend.js +6 -0
- package/dist/schema/index.d.ts +13 -0
- package/dist/schema/index.js +8 -0
- package/dist/schema/infer.d.ts +102 -0
- package/dist/schema/infer.js +1 -0
- package/dist/schema/types.d.ts +76 -0
- package/dist/schema/types.js +1 -0
- package/dist/schema/zod.d.ts +90 -0
- package/dist/schema/zod.js +101 -0
- package/package.json +99 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decorators for defining models and their properties.
|
|
3
|
+
*
|
|
4
|
+
* Usage looks like:
|
|
5
|
+
*
|
|
6
|
+
* @ClientModel({ loadStrategy: LoadStrategy.Eager })
|
|
7
|
+
* class Issue extends BaseModel {
|
|
8
|
+
* @Property() title = "";
|
|
9
|
+
* @Reference("User", { nullable: true }) assignee: any;
|
|
10
|
+
* @Action moveToTeam(id: string) { ... }
|
|
11
|
+
* @Computed get identifier() { ... }
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* Each decorator registers metadata in the ModelRegistry at class-definition
|
|
15
|
+
* time. The engine reads that metadata later for serialization, hydration,
|
|
16
|
+
* observability, indexing, and reference resolution.
|
|
17
|
+
*/
|
|
18
|
+
import { ModelRegistry } from "./ModelRegistry";
|
|
19
|
+
import { defineObservableProperty } from "./observability";
|
|
20
|
+
import { PropertyType, } from "./types";
|
|
21
|
+
import { installBackRefAccessor, installCollectionAccessor, installReferenceAccessor, } from "./refAccessors";
|
|
22
|
+
const pendingByClass = new WeakMap();
|
|
23
|
+
function getOrCreatePending(ctor) {
|
|
24
|
+
let entry = pendingByClass.get(ctor);
|
|
25
|
+
if (entry == null) {
|
|
26
|
+
entry = {
|
|
27
|
+
properties: new Map(),
|
|
28
|
+
actions: new Set(),
|
|
29
|
+
computedProps: new Set(),
|
|
30
|
+
};
|
|
31
|
+
pendingByClass.set(ctor, entry);
|
|
32
|
+
}
|
|
33
|
+
return entry;
|
|
34
|
+
}
|
|
35
|
+
function stashProperty(ctor, prop) {
|
|
36
|
+
getOrCreatePending(ctor).properties.set(prop.name, prop);
|
|
37
|
+
}
|
|
38
|
+
function updateStashedProperty(ctor, name, updates) {
|
|
39
|
+
const pending = getOrCreatePending(ctor);
|
|
40
|
+
const existing = pending.properties.get(name);
|
|
41
|
+
if (existing == null) {
|
|
42
|
+
throw new Error(`Property "${name}" not found on model "${ctor.name}". ` +
|
|
43
|
+
`Declare it with @Property() before applying @Reference.`);
|
|
44
|
+
}
|
|
45
|
+
pending.properties.set(name, { ...existing, ...updates });
|
|
46
|
+
}
|
|
47
|
+
function stashAction(ctor, name) {
|
|
48
|
+
getOrCreatePending(ctor).actions.add(name);
|
|
49
|
+
}
|
|
50
|
+
function stashComputed(ctor, name) {
|
|
51
|
+
getOrCreatePending(ctor).computedProps.add(name);
|
|
52
|
+
}
|
|
53
|
+
/** Walk the prototype chain from `ctor`, draining each class's pending
|
|
54
|
+
* metadata into `meta`. Pending entries are NOT deleted on drain so multiple
|
|
55
|
+
* subclasses sharing an abstract base each inherit independently, AND deeper
|
|
56
|
+
* chains (`A extends B extends M`, where B is itself a live model) keep
|
|
57
|
+
* working — when @ClientModel runs on A it re-walks pending(B) which still
|
|
58
|
+
* holds B's declarations.
|
|
59
|
+
*
|
|
60
|
+
* Subclass-declared properties win over ancestor-declared ones with the same
|
|
61
|
+
* name (`Map.set` is no-op-when-present via the `has` guard).
|
|
62
|
+
*/
|
|
63
|
+
function drainPendingChain(ctor, meta) {
|
|
64
|
+
// Walk the *constructor* chain (not the prototype chain): for
|
|
65
|
+
// `class B extends A`, `Object.getPrototypeOf(B)` returns `A`, and the
|
|
66
|
+
// chain terminates at `Function.prototype`. Stopping there is sufficient;
|
|
67
|
+
// no model class extends `Object` directly.
|
|
68
|
+
let current = ctor;
|
|
69
|
+
while (current != null && current !== Function.prototype) {
|
|
70
|
+
const pending = pendingByClass.get(current);
|
|
71
|
+
if (pending != null) {
|
|
72
|
+
for (const [name, prop] of pending.properties) {
|
|
73
|
+
if (!meta.properties.has(name)) {
|
|
74
|
+
meta.properties.set(name, prop);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const action of pending.actions) {
|
|
78
|
+
meta.actions.add(action);
|
|
79
|
+
}
|
|
80
|
+
for (const computed of pending.computedProps) {
|
|
81
|
+
meta.computedProps.add(computed);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
current = Object.getPrototypeOf(current);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Models registered without an explicit `name`, for the one-shot dev warning. */
|
|
88
|
+
const ctorNameFallbacks = new Set();
|
|
89
|
+
export function ClientModel(opts = {}) {
|
|
90
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
return function (ctor) {
|
|
93
|
+
const modelName = opts.name ?? ctor.name;
|
|
94
|
+
if (opts.name == null &&
|
|
95
|
+
typeof process !== "undefined" &&
|
|
96
|
+
process.env?.NODE_ENV !== "production" &&
|
|
97
|
+
!ctorNameFallbacks.has(modelName)) {
|
|
98
|
+
ctorNameFallbacks.add(modelName);
|
|
99
|
+
console.warn(`[zerodrift] @ClientModel on "${modelName}" has no explicit ` +
|
|
100
|
+
`{ name } and is keyed on ctor.name. Minified production builds ` +
|
|
101
|
+
`mangle class names — pass @ClientModel({ name: "${modelName}" }) ` +
|
|
102
|
+
`or set your bundler's keep_classnames.`);
|
|
103
|
+
}
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
+
ctor._modelName = modelName;
|
|
106
|
+
const meta = ModelRegistry.registerModel(modelName, ctor);
|
|
107
|
+
if (opts.loadStrategy != null) {
|
|
108
|
+
meta.loadStrategy = opts.loadStrategy;
|
|
109
|
+
}
|
|
110
|
+
if (opts.usedForPartialIndexes != null) {
|
|
111
|
+
meta.usedForPartialIndexes = opts.usedForPartialIndexes;
|
|
112
|
+
}
|
|
113
|
+
if (opts.schemaVersion != null) {
|
|
114
|
+
meta.schemaVersion = opts.schemaVersion;
|
|
115
|
+
}
|
|
116
|
+
// Drain decorator-stashed metadata for this class and every ancestor up
|
|
117
|
+
// the prototype chain. Abstract bases never registered themselves; their
|
|
118
|
+
// pending entries are read-only from this point on (so siblings sharing
|
|
119
|
+
// a base each inherit independently).
|
|
120
|
+
drainPendingChain(ctor, meta);
|
|
121
|
+
return ctor;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// @Property — persisted, observable property
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
export function Property(opts = {}) {
|
|
128
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
return function (target, key) {
|
|
131
|
+
stashProperty(target.constructor, {
|
|
132
|
+
name: key,
|
|
133
|
+
type: PropertyType.Property,
|
|
134
|
+
indexed: opts.indexed,
|
|
135
|
+
serializer: opts.serializer,
|
|
136
|
+
deserializer: opts.deserializer,
|
|
137
|
+
});
|
|
138
|
+
defineObservableProperty(target, key);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// @EphemeralProperty — observable but NOT persisted to IndexedDB
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
export function EphemeralProperty() {
|
|
145
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
return function (target, key) {
|
|
148
|
+
stashProperty(target.constructor, {
|
|
149
|
+
name: key,
|
|
150
|
+
type: PropertyType.EphemeralProperty,
|
|
151
|
+
});
|
|
152
|
+
defineObservableProperty(target, key);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function defineReference(lazy, referenceTo, opts) {
|
|
156
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
return function (target, key) {
|
|
159
|
+
const idKey = opts.idField ?? key + "Id";
|
|
160
|
+
updateStashedProperty(target.constructor, idKey, {
|
|
161
|
+
type: PropertyType.Reference,
|
|
162
|
+
referenceTo,
|
|
163
|
+
nullable: opts.nullable,
|
|
164
|
+
onDelete: opts.onDelete,
|
|
165
|
+
lazy,
|
|
166
|
+
});
|
|
167
|
+
stashProperty(target.constructor, {
|
|
168
|
+
name: key,
|
|
169
|
+
type: PropertyType.ReferenceModel,
|
|
170
|
+
referenceTo,
|
|
171
|
+
idField: idKey,
|
|
172
|
+
});
|
|
173
|
+
installReferenceAccessor(target, key, idKey, referenceTo);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export function Reference(referenceTo, opts = {}) {
|
|
177
|
+
return defineReference(false, referenceTo, opts);
|
|
178
|
+
}
|
|
179
|
+
export function LazyReference(referenceTo, opts = {}) {
|
|
180
|
+
return defineReference(true, referenceTo, opts);
|
|
181
|
+
}
|
|
182
|
+
function defineReferenceCollection(lazy, referenceTo, opts) {
|
|
183
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
+
return function (target, key) {
|
|
186
|
+
const modelName = target.constructor.name;
|
|
187
|
+
// Derive the foreign key on the child model. Convention: parentModelName
|
|
188
|
+
// (lowercased first char) + "Id". Override with inverseOf when needed.
|
|
189
|
+
const inverseKey = opts.inverseOf ??
|
|
190
|
+
modelName.charAt(0).toLowerCase() + modelName.slice(1) + "Id";
|
|
191
|
+
stashProperty(target.constructor, {
|
|
192
|
+
name: key,
|
|
193
|
+
type: PropertyType.ReferenceCollection,
|
|
194
|
+
referenceTo,
|
|
195
|
+
lazy,
|
|
196
|
+
inverseOf: inverseKey,
|
|
197
|
+
coveringIndexes: opts.coveringIndexes,
|
|
198
|
+
});
|
|
199
|
+
installCollectionAccessor(target, key);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
export function ReferenceCollection(referenceTo, opts = {}) {
|
|
203
|
+
return defineReferenceCollection(false, referenceTo, opts);
|
|
204
|
+
}
|
|
205
|
+
export function LazyReferenceCollection(referenceTo, opts = {}) {
|
|
206
|
+
return defineReferenceCollection(true, referenceTo, opts);
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// @BackReference — inverse of a Reference
|
|
210
|
+
//
|
|
211
|
+
// Metadata-only registration. The runtime BackRef is created in
|
|
212
|
+
// BaseModel.makeModelObservable().
|
|
213
|
+
//
|
|
214
|
+
// Key behavior: a BackReference is "owned" by the referenced model.
|
|
215
|
+
// When the owning model is deleted, the back-referenced model is also removed.
|
|
216
|
+
// This cascade is handled in SyncConnection during delta packet processing.
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
export function BackReference(referenceTo, inverseOf) {
|
|
219
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
+
return function (target, key) {
|
|
222
|
+
stashProperty(target.constructor, {
|
|
223
|
+
name: key,
|
|
224
|
+
type: PropertyType.BackReference,
|
|
225
|
+
referenceTo,
|
|
226
|
+
inverseOf,
|
|
227
|
+
});
|
|
228
|
+
installBackRefAccessor(target, key);
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// @ReferenceArray — many-to-many stored as array of IDs
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
export function ReferenceArray(referenceTo) {
|
|
235
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
236
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
237
|
+
return function (target, key) {
|
|
238
|
+
stashProperty(target.constructor, {
|
|
239
|
+
name: key,
|
|
240
|
+
type: PropertyType.ReferenceArray,
|
|
241
|
+
referenceTo,
|
|
242
|
+
});
|
|
243
|
+
defineObservableProperty(target, key);
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function defineOwnedCollection(lazy, referenceTo, opts) {
|
|
247
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
249
|
+
return function (target, key) {
|
|
250
|
+
stashProperty(target.constructor, {
|
|
251
|
+
name: key,
|
|
252
|
+
type: PropertyType.OwnedCollection,
|
|
253
|
+
referenceTo,
|
|
254
|
+
idsField: opts.idsField,
|
|
255
|
+
lazy,
|
|
256
|
+
});
|
|
257
|
+
installCollectionAccessor(target, key);
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
export function OwnedCollection(referenceTo, opts) {
|
|
261
|
+
return defineOwnedCollection(false, referenceTo, opts);
|
|
262
|
+
}
|
|
263
|
+
export function LazyOwnedCollection(referenceTo, opts) {
|
|
264
|
+
return defineOwnedCollection(true, referenceTo, opts);
|
|
265
|
+
}
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// @Action and @Computed — register method names for MobX wiring
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
|
+
export function Action(target, key, _d) {
|
|
272
|
+
stashAction(target.constructor, key);
|
|
273
|
+
}
|
|
274
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
276
|
+
export function Computed(target, key, _d) {
|
|
277
|
+
stashComputed(target.constructor, key);
|
|
278
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable string-to-int hash (djb2-style). Used by `ModelRegistry.schemaHash`
|
|
3
|
+
* and the schema compiler's per-entity `schemaVersion`. Not cryptographic —
|
|
4
|
+
* just deterministic across runs and cheap. Returns a non-negative integer.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hashString(input: string): number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable string-to-int hash (djb2-style). Used by `ModelRegistry.schemaHash`
|
|
3
|
+
* and the schema compiler's per-entity `schemaVersion`. Not cryptographic —
|
|
4
|
+
* just deterministic across runs and cheap. Returns a non-negative integer.
|
|
5
|
+
*/
|
|
6
|
+
export function hashString(input) {
|
|
7
|
+
let hash = 0;
|
|
8
|
+
for (let i = 0; i < input.length; i++) {
|
|
9
|
+
hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0;
|
|
10
|
+
}
|
|
11
|
+
return Math.abs(hash);
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { LoadStrategy, PropertyType, BootstrapPhase, TransactionState, } from "./types";
|
|
2
|
+
export { dateSerializer, dateDeserializer } from "./serializers";
|
|
3
|
+
export type { PropertyMeta, ModelMeta, FieldTransform, PropertyChange, CommitIntent, CommitRouteResult, CommitRouteHandler, OnModelTouchedHandler, OnDelete, EngineErrorContext, EngineErrorHandler, } from "./types";
|
|
4
|
+
export { ClientModel, Property, EphemeralProperty, Reference, LazyReference, ReferenceCollection, LazyReferenceCollection, OwnedCollection, LazyOwnedCollection, BackReference, ReferenceArray, Action, Computed, } from "./decorators";
|
|
5
|
+
export { BaseModel } from "./BaseModel";
|
|
6
|
+
export { RefCollection, BackRef, CollectionState } from "./LazyCollection";
|
|
7
|
+
export { OwnedRefs } from "./LazyOwnedCollection";
|
|
8
|
+
export { MemoryAdapter } from "./MemoryAdapter";
|
|
9
|
+
export { BootstrapType } from "./Database";
|
|
10
|
+
export type { DatabaseMeta, StorageAdapter } from "./Database";
|
|
11
|
+
export { StoreManager, RestrictDeleteError } from "./StoreManager";
|
|
12
|
+
export type { BootstrapResponse, BootstrapFetcher, BootstrapFetcherOptions, FetcherContext, StoreManagerConfig, TransportConfig, LoadingConfig, PersistenceConfig, HooksConfig, AdvancedConfig, OnDemandConfig, OnDemandFetcher, OnDemandBatchFetcher, ModelStreamConfig, } from "./StoreManager";
|
|
13
|
+
export type { UndoableAction } from "./Transaction";
|
|
14
|
+
export type { TransactionSender, BatchResponse, UndoableActionHandlers, UndoResult, } from "./TransactionQueue";
|
|
15
|
+
export type { SyncAction, DeltaPacket, SyncMessageTransform, } from "./SyncConnection";
|
|
16
|
+
export type { ModelUpdate, ModelStreamMessageTransform } from "./ModelStream";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// `zerodrift` — the curated, stable adopter surface. The engine's runtime
|
|
2
|
+
// machinery lives behind `zerodrift/internal` (see internal.ts).
|
|
3
|
+
// ── Enums & serializers ────────────────────────────────────────────────────
|
|
4
|
+
export { LoadStrategy, PropertyType, BootstrapPhase, TransactionState, } from "./types";
|
|
5
|
+
export { dateSerializer, dateDeserializer } from "./serializers";
|
|
6
|
+
// ── Model definition ───────────────────────────────────────────────────────
|
|
7
|
+
export { ClientModel, Property, EphemeralProperty, Reference, LazyReference, ReferenceCollection, LazyReferenceCollection, OwnedCollection, LazyOwnedCollection, BackReference, ReferenceArray, Action, Computed, } from "./decorators";
|
|
8
|
+
export { BaseModel } from "./BaseModel";
|
|
9
|
+
// Relation field types — adopters annotate model fields with these
|
|
10
|
+
// (`public issues: RefCollection<Issue>`), so they stay on the curated
|
|
11
|
+
// surface even though their construction is engine-internal.
|
|
12
|
+
export { RefCollection, BackRef, CollectionState } from "./LazyCollection";
|
|
13
|
+
export { OwnedRefs } from "./LazyOwnedCollection";
|
|
14
|
+
// ── Storage ────────────────────────────────────────────────────────────────
|
|
15
|
+
export { MemoryAdapter } from "./MemoryAdapter";
|
|
16
|
+
export { BootstrapType } from "./Database";
|
|
17
|
+
// ── Engine ─────────────────────────────────────────────────────────────────
|
|
18
|
+
export { StoreManager, RestrictDeleteError } from "./StoreManager";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `zerodrift/internal` — engine internals with NO stability promise.
|
|
3
|
+
*
|
|
4
|
+
* These are the runtime machinery the curated `zerodrift` entry point
|
|
5
|
+
* deliberately hides: the pool, storage, transaction queue/objects, the SSE
|
|
6
|
+
* connection/stream, the per-strategy stores, the model registry, and the
|
|
7
|
+
* config normalizer. They are exported only for tooling, tests, and the rare
|
|
8
|
+
* adopter that genuinely needs to reach under the engine (e.g. a devtool
|
|
9
|
+
* walking `ModelRegistry`, or a test driving `ObjectPool` directly).
|
|
10
|
+
*
|
|
11
|
+
* Anything here may change shape or vanish between releases without a major
|
|
12
|
+
* bump. If you find yourself importing from here in app code, that's usually
|
|
13
|
+
* a sign the curated surface is missing something — open an issue.
|
|
14
|
+
*/
|
|
15
|
+
export { ObjectPool } from "./ObjectPool";
|
|
16
|
+
export { Database } from "./Database";
|
|
17
|
+
export { ModelRegistry } from "./ModelRegistry";
|
|
18
|
+
export { defineObservableProperty } from "./observability";
|
|
19
|
+
export { FullStore, PartialStore, ModelStore } from "./Store";
|
|
20
|
+
export { BaseTransaction, UpdateTransaction, CreateTransaction, DeleteTransaction, ArchiveTransaction, } from "./Transaction";
|
|
21
|
+
export { TransactionQueue } from "./TransactionQueue";
|
|
22
|
+
export { SyncConnection } from "./SyncConnection";
|
|
23
|
+
export { ModelStream } from "./ModelStream";
|
|
24
|
+
export { normalizeConfig } from "./StoreManager";
|
|
25
|
+
export type { NormalizedConfig } from "./StoreManager";
|
|
26
|
+
export type { IObjectPool, IStoreManager, CoveringPath, } from "./types";
|
|
27
|
+
export { DEFAULT_TRANSIENT_INDEX_DEPTH, toError } from "./types";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `zerodrift/internal` — engine internals with NO stability promise.
|
|
3
|
+
*
|
|
4
|
+
* These are the runtime machinery the curated `zerodrift` entry point
|
|
5
|
+
* deliberately hides: the pool, storage, transaction queue/objects, the SSE
|
|
6
|
+
* connection/stream, the per-strategy stores, the model registry, and the
|
|
7
|
+
* config normalizer. They are exported only for tooling, tests, and the rare
|
|
8
|
+
* adopter that genuinely needs to reach under the engine (e.g. a devtool
|
|
9
|
+
* walking `ModelRegistry`, or a test driving `ObjectPool` directly).
|
|
10
|
+
*
|
|
11
|
+
* Anything here may change shape or vanish between releases without a major
|
|
12
|
+
* bump. If you find yourself importing from here in app code, that's usually
|
|
13
|
+
* a sign the curated surface is missing something — open an issue.
|
|
14
|
+
*/
|
|
15
|
+
export { ObjectPool } from "./ObjectPool";
|
|
16
|
+
export { Database } from "./Database";
|
|
17
|
+
export { ModelRegistry } from "./ModelRegistry";
|
|
18
|
+
export { defineObservableProperty } from "./observability";
|
|
19
|
+
export { FullStore, PartialStore, ModelStore } from "./Store";
|
|
20
|
+
export { BaseTransaction, UpdateTransaction, CreateTransaction, DeleteTransaction, ArchiveTransaction, } from "./Transaction";
|
|
21
|
+
export { TransactionQueue } from "./TransactionQueue";
|
|
22
|
+
export { SyncConnection } from "./SyncConnection";
|
|
23
|
+
export { ModelStream } from "./ModelStream";
|
|
24
|
+
export { normalizeConfig } from "./StoreManager";
|
|
25
|
+
export { DEFAULT_TRANSIENT_INDEX_DEPTH, toError } from "./types";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineObservableProperty
|
|
3
|
+
*
|
|
4
|
+
* Replaces a class property with a getter/setter pair using Object.defineProperty.
|
|
5
|
+
*
|
|
6
|
+
* The setter does three things:
|
|
7
|
+
* 1. Lazily creates a MobX observable box on `this.__mobx[propName]`
|
|
8
|
+
* 2. Stores the value in the box (so MobX can track reads/writes)
|
|
9
|
+
* 3. Calls `this.propertyChanged(name, oldValue, newValue)` to feed
|
|
10
|
+
* the change-tracking system that generates transactions
|
|
11
|
+
*
|
|
12
|
+
* Before any of that, if a wired `StoreManager` has registered field
|
|
13
|
+
* transforms via `applyFieldTransforms`, the assigned value is routed
|
|
14
|
+
* through `sm.applyTransform` so consumers can canonicalize input on the
|
|
15
|
+
* way in. The `hasFieldTransforms` short-circuit keeps the no-config
|
|
16
|
+
* setter path a single boolean read.
|
|
17
|
+
*
|
|
18
|
+
* The getter reads from the MobX box if it exists, otherwise returns
|
|
19
|
+
* the raw stored value (for pre-bootstrap access before observability is on).
|
|
20
|
+
*/
|
|
21
|
+
export declare function defineObservableProperty(target: object, propName: string): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineObservableProperty
|
|
3
|
+
*
|
|
4
|
+
* Replaces a class property with a getter/setter pair using Object.defineProperty.
|
|
5
|
+
*
|
|
6
|
+
* The setter does three things:
|
|
7
|
+
* 1. Lazily creates a MobX observable box on `this.__mobx[propName]`
|
|
8
|
+
* 2. Stores the value in the box (so MobX can track reads/writes)
|
|
9
|
+
* 3. Calls `this.propertyChanged(name, oldValue, newValue)` to feed
|
|
10
|
+
* the change-tracking system that generates transactions
|
|
11
|
+
*
|
|
12
|
+
* Before any of that, if a wired `StoreManager` has registered field
|
|
13
|
+
* transforms via `applyFieldTransforms`, the assigned value is routed
|
|
14
|
+
* through `sm.applyTransform` so consumers can canonicalize input on the
|
|
15
|
+
* way in. The `hasFieldTransforms` short-circuit keeps the no-config
|
|
16
|
+
* setter path a single boolean read.
|
|
17
|
+
*
|
|
18
|
+
* The getter reads from the MobX box if it exists, otherwise returns
|
|
19
|
+
* the raw stored value (for pre-bootstrap access before observability is on).
|
|
20
|
+
*/
|
|
21
|
+
import { observable } from "mobx";
|
|
22
|
+
import { BaseModel } from "./BaseModel";
|
|
23
|
+
export function defineObservableProperty(target, propName) {
|
|
24
|
+
// Raw storage key — holds the value before the MobX box is created
|
|
25
|
+
const rawKey = `__raw_${propName}`;
|
|
26
|
+
Object.defineProperty(target, propName, {
|
|
27
|
+
configurable: true,
|
|
28
|
+
enumerable: true,
|
|
29
|
+
// Legacy decorator target — no better type exists for prototype manipulation
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
get() {
|
|
32
|
+
// If observability is active, read from the MobX box (tracked read)
|
|
33
|
+
if (this.__mobx?.[propName]) {
|
|
34
|
+
return this.__mobx[propName].get();
|
|
35
|
+
}
|
|
36
|
+
// Otherwise return the raw value (pre-bootstrap)
|
|
37
|
+
return this[rawKey];
|
|
38
|
+
},
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
set(newValue) {
|
|
41
|
+
const sm = BaseModel.storeManager;
|
|
42
|
+
if (sm != null && sm.hasFieldTransforms) {
|
|
43
|
+
newValue = sm.applyTransform(this, propName, newValue);
|
|
44
|
+
}
|
|
45
|
+
const oldValue = this[propName]; // read via getter above
|
|
46
|
+
// Create the __mobx container if it doesn't exist yet
|
|
47
|
+
if (!this.__mobx) {
|
|
48
|
+
this.__mobx = {};
|
|
49
|
+
}
|
|
50
|
+
// Create or update the MobX observable box
|
|
51
|
+
if (!this.__mobx[propName]) {
|
|
52
|
+
this.__mobx[propName] = observable.box(newValue, { deep: false });
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.__mobx[propName].set(newValue);
|
|
56
|
+
}
|
|
57
|
+
// Also store raw value for pre-observable access
|
|
58
|
+
this[rawKey] = newValue;
|
|
59
|
+
// Notify the change-tracking system (only after makeModelObservable())
|
|
60
|
+
if (this.__observabilityEnabled &&
|
|
61
|
+
typeof this.propertyChanged === "function") {
|
|
62
|
+
this.propertyChanged(propName, oldValue, newValue);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IObjectPool } from "./types";
|
|
2
|
+
import type { LazyCollectionBase, BackRef } from "./LazyCollection";
|
|
3
|
+
export interface RefHolder {
|
|
4
|
+
store: IObjectPool | null;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface CollectionHolder {
|
|
8
|
+
__collections?: Record<string, LazyCollectionBase>;
|
|
9
|
+
}
|
|
10
|
+
export interface BackRefHolder {
|
|
11
|
+
__backRefs?: Record<string, BackRef>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Install the prototype getter/setter pair that powers a singular relation.
|
|
15
|
+
* The getter resolves the FK via the pool and tracks the entry so observers
|
|
16
|
+
* re-run on identity swaps; the setter writes the FK from the model's id.
|
|
17
|
+
*
|
|
18
|
+
* Used by `@Reference` / `@LazyReference` decorators and by the schema-first
|
|
19
|
+
* compiler so the runtime accessor shape is identical across both authoring
|
|
20
|
+
* paths.
|
|
21
|
+
*/
|
|
22
|
+
export declare function installReferenceAccessor(prototype: object, key: string, idField: string, referenceTo: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Install the prototype getter that exposes the runtime `LazyCollectionBase`
|
|
25
|
+
* stored on `this.__collections[key]`. The collection itself is created
|
|
26
|
+
* during `BaseModel.makeModelObservable()`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function installCollectionAccessor(prototype: object, key: string): void;
|
|
29
|
+
/** Install the prototype getter for a `BackRef` runtime collection. */
|
|
30
|
+
export declare function installBackRefAccessor(prototype: object, key: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Install a prototype getter that calls `fn(this)` whenever the property is
|
|
33
|
+
* read. `BaseModel.makeModelObservable()` later replaces the per-instance
|
|
34
|
+
* binding with a MobX `computed`, so reads are memoized once observability
|
|
35
|
+
* is enabled. Used by the schema-first `extend(...)` pipeline.
|
|
36
|
+
*/
|
|
37
|
+
export declare function installComputedAccessor(prototype: object, key: string, fn: (record: object) => unknown): void;
|
|
38
|
+
/**
|
|
39
|
+
* Install a prototype method that calls `fn(this, ...args)`.
|
|
40
|
+
* `BaseModel.makeModelObservable()` later wraps the per-instance binding
|
|
41
|
+
* with MobX `action` for write batching.
|
|
42
|
+
*/
|
|
43
|
+
export declare function installActionMethod(prototype: object, key: string, fn: (record: object, ...args: never[]) => unknown): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install the prototype getter/setter pair that powers a singular relation.
|
|
3
|
+
* The getter resolves the FK via the pool and tracks the entry so observers
|
|
4
|
+
* re-run on identity swaps; the setter writes the FK from the model's id.
|
|
5
|
+
*
|
|
6
|
+
* Used by `@Reference` / `@LazyReference` decorators and by the schema-first
|
|
7
|
+
* compiler so the runtime accessor shape is identical across both authoring
|
|
8
|
+
* paths.
|
|
9
|
+
*/
|
|
10
|
+
export function installReferenceAccessor(prototype, key, idField, referenceTo) {
|
|
11
|
+
Object.defineProperty(prototype, key, {
|
|
12
|
+
configurable: true,
|
|
13
|
+
enumerable: false,
|
|
14
|
+
get() {
|
|
15
|
+
const id = this[idField];
|
|
16
|
+
if (typeof id !== "string" || id === "") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
this.store?.trackModel(referenceTo, id);
|
|
20
|
+
return this.store?.getById(referenceTo, id) ?? null;
|
|
21
|
+
},
|
|
22
|
+
set(model) {
|
|
23
|
+
this[idField] = model != null ? model.id : null;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Install the prototype getter that exposes the runtime `LazyCollectionBase`
|
|
29
|
+
* stored on `this.__collections[key]`. The collection itself is created
|
|
30
|
+
* during `BaseModel.makeModelObservable()`.
|
|
31
|
+
*/
|
|
32
|
+
export function installCollectionAccessor(prototype, key) {
|
|
33
|
+
Object.defineProperty(prototype, key, {
|
|
34
|
+
configurable: true,
|
|
35
|
+
enumerable: false,
|
|
36
|
+
get() {
|
|
37
|
+
return this.__collections?.[key] ?? null;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Install the prototype getter for a `BackRef` runtime collection. */
|
|
42
|
+
export function installBackRefAccessor(prototype, key) {
|
|
43
|
+
Object.defineProperty(prototype, key, {
|
|
44
|
+
configurable: true,
|
|
45
|
+
enumerable: false,
|
|
46
|
+
get() {
|
|
47
|
+
return this.__backRefs?.[key] ?? null;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Install a prototype getter that calls `fn(this)` whenever the property is
|
|
53
|
+
* read. `BaseModel.makeModelObservable()` later replaces the per-instance
|
|
54
|
+
* binding with a MobX `computed`, so reads are memoized once observability
|
|
55
|
+
* is enabled. Used by the schema-first `extend(...)` pipeline.
|
|
56
|
+
*/
|
|
57
|
+
export function installComputedAccessor(prototype, key, fn) {
|
|
58
|
+
Object.defineProperty(prototype, key, {
|
|
59
|
+
configurable: true,
|
|
60
|
+
enumerable: false,
|
|
61
|
+
get() {
|
|
62
|
+
return fn(this);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Install a prototype method that calls `fn(this, ...args)`.
|
|
68
|
+
* `BaseModel.makeModelObservable()` later wraps the per-instance binding
|
|
69
|
+
* with MobX `action` for write batching.
|
|
70
|
+
*/
|
|
71
|
+
export function installActionMethod(prototype, key, fn) {
|
|
72
|
+
Object.defineProperty(prototype, key, {
|
|
73
|
+
configurable: true,
|
|
74
|
+
enumerable: false,
|
|
75
|
+
writable: true,
|
|
76
|
+
value(...args) {
|
|
77
|
+
return fn(this, ...args);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|