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,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
+ }
@@ -0,0 +1,2 @@
1
+ export declare const dateSerializer: (value: unknown) => unknown;
2
+ export declare const dateDeserializer: (raw: unknown) => unknown;
@@ -0,0 +1,2 @@
1
+ export const dateSerializer = (value) => value instanceof Date ? value.toISOString() : value;
2
+ export const dateDeserializer = (raw) => typeof raw === "string" ? new Date(raw) : raw;