doxum 0.1.2 → 0.1.4

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 (48) hide show
  1. package/README.md +59 -27
  2. package/dist/{contract-DGkZcTiP.d.cts → contract-BsY1XLiG.d.ts} +2 -1
  3. package/dist/{contract-Dh3az8aN.d.ts → contract-D8kjdfqT.d.cts} +2 -1
  4. package/dist/{access-5D4KKFm7.js → dependency-C5_R1tvQ.js} +40 -262
  5. package/dist/dependency-C5_R1tvQ.js.map +1 -0
  6. package/dist/{access-z56fVqZ2.cjs → dependency-Dfzs3khk.cjs} +62 -344
  7. package/dist/dependency-Dfzs3khk.cjs.map +1 -0
  8. package/dist/driver-CNxqMVFH.cjs +69 -0
  9. package/dist/driver-CNxqMVFH.cjs.map +1 -0
  10. package/dist/driver-CbzfW5MR.js +46 -0
  11. package/dist/driver-CbzfW5MR.js.map +1 -0
  12. package/dist/index.cjs +2636 -46
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +2594 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/integration.cjs +2 -3
  19. package/dist/integration.cjs.map +1 -1
  20. package/dist/integration.d.cts +1 -1
  21. package/dist/integration.d.ts +1 -1
  22. package/dist/integration.js +1 -2
  23. package/dist/integration.js.map +1 -1
  24. package/dist/local-sync.cjs +154 -329
  25. package/dist/local-sync.cjs.map +1 -1
  26. package/dist/local-sync.d.cts +17 -22
  27. package/dist/local-sync.d.ts +17 -22
  28. package/dist/local-sync.js +149 -326
  29. package/dist/local-sync.js.map +1 -1
  30. package/dist/ownership-B-VAPcce.js +242 -0
  31. package/dist/ownership-B-VAPcce.js.map +1 -0
  32. package/dist/ownership-CU-9DKJQ.cjs +301 -0
  33. package/dist/ownership-CU-9DKJQ.cjs.map +1 -0
  34. package/package.json +1 -1
  35. package/skills/doxum-runtime/references/guide.en.md +52 -1
  36. package/skills/doxum-runtime/references/guide.zh-CN.md +46 -1
  37. package/skills/doxum-runtime/references/invariants.en.md +14 -4
  38. package/skills/doxum-runtime/references/invariants.zh-CN.md +1 -1
  39. package/dist/access-5D4KKFm7.js.map +0 -1
  40. package/dist/access-z56fVqZ2.cjs.map +0 -1
  41. package/dist/dependency-BRn1TRDi.js +0 -20
  42. package/dist/dependency-BRn1TRDi.js.map +0 -1
  43. package/dist/dependency-DmQXyj7q.cjs +0 -25
  44. package/dist/dependency-DmQXyj7q.cjs.map +0 -1
  45. package/dist/readable-B8E3FMq6.cjs +0 -2614
  46. package/dist/readable-B8E3FMq6.cjs.map +0 -1
  47. package/dist/readable-CIj02gIj.js +0 -2579
  48. package/dist/readable-CIj02gIj.js.map +0 -1
package/README.md CHANGED
@@ -7,16 +7,17 @@ undo/redo, precise change notifications, and incremental derived data.
7
7
  Licensed under the [MIT License](LICENSE).
8
8
 
9
9
  The `doxum` core entry is a local in-memory runtime. The optional
10
- `doxum/local-sync` browser adapter adds durable local editing and same-origin
11
- cross-tab synchronization; network collaboration, authorization, and conflict
12
- resolution remain separate application concerns.
10
+ `doxum/local-sync` browser attachment adds IndexedDB persistence and
11
+ same-origin cross-tab synchronization with one writable tab at a time; network
12
+ collaboration, authorization, and conflict resolution remain separate
13
+ application concerns.
13
14
 
14
15
  ## Packages
15
16
 
16
17
  - `doxum` defines schemas, mutations, history, subscriptions, and views.
17
- - `doxum/local-sync` provides IndexedDB persistence, Web Lock write
18
- serialization, BroadcastChannel catch-up notifications, and durable local
19
- undo/redo for one browser origin.
18
+ - `doxum/local-sync` lets one Web-Lock leader write a runtime synchronously,
19
+ persists its commands asynchronously to IndexedDB, and makes other tabs
20
+ ordered read-only mirrors.
20
21
  - `doxum/react` binds Doxum read models to React 18+ with fine-grained external
21
22
  store subscriptions.
22
23
 
@@ -152,19 +153,37 @@ already settled.
152
153
 
153
154
  ## Local Persistence And Cross-Tab Sync
154
155
 
155
- `doxum/local-sync` is an optional browser adapter for a document that only
156
- needs offline persistence and same-origin, cross-tab consistency. It uses
157
- IndexedDB as the durable checkpoint and ordered commit log, Web Locks to give a
158
- document one writer, and BroadcastChannel only to notify other tabs to catch up
159
- from IndexedDB. It does not need a server or Yjs.
156
+ `doxum/local-sync` is an optional browser attachment for a document that needs
157
+ offline persistence and same-origin, cross-tab convergence. It uses IndexedDB
158
+ as the ordered checkpoint and commit log, Web Locks to serialize background
159
+ confirmation, and BroadcastChannel only to notify other tabs to catch up from
160
+ IndexedDB. It does not need a server or Yjs.
161
+
162
+ The attachment deliberately uses a simple single-writer model. It hydrates the
163
+ runtime from the IndexedDB checkpoint and ordered command log, then attempts to
164
+ hold a Web Lock for that document. The lock holder is the `leader` and may use
165
+ the normal synchronous runtime APIs. All other attached tabs are `follower`
166
+ mirrors: they read the durable tail in order after a BroadcastChannel hint, and
167
+ their direct writes throw `LocalSyncReadOnlyError`. When the leader disposes,
168
+ a follower catches up and becomes the next leader.
169
+
170
+ The leader's `runtime.update`, normal `runtime.apply`, and
171
+ `runtime.history.undo()` / `redo()` stay synchronous and immediately visible.
172
+ Local-sync observes the resulting local, system, and history commits and writes
173
+ their JSON operation batches to IndexedDB in the background. It deliberately
174
+ rejects `runtime.replace()` and an externally supplied `apply(..., {
175
+ source: 'remote' })` while attached: neither is a local operation command that
176
+ the log can faithfully append. `flush()` is the explicit point that waits for
177
+ commits observed before the call to persist (or, in a follower, waits to catch
178
+ up to the durable head). A browser crash, quota failure, or malformed JSON
179
+ payload can therefore leave an already visible leader commit unpersisted;
180
+ inspect `state()` or use `onError` to surface that condition.
160
181
 
161
182
  ```ts
162
- import { select } from 'doxum';
163
- import { openLocalDocument } from 'doxum/local-sync';
183
+ import { createDocument, select } from 'doxum';
184
+ import { attachLocalSync } from 'doxum/local-sync';
164
185
 
165
- const local = await openLocalDocument({
166
- database: 'my-app',
167
- documentId: 'project-1',
186
+ const runtime = createDocument({
168
187
  schema: taskSchema,
169
188
  initial: {
170
189
  title: 'Launch',
@@ -172,21 +191,34 @@ const local = await openLocalDocument({
172
191
  },
173
192
  });
174
193
 
175
- await local.update(tx => {
176
- tx.write.title.set('Ship Doxum');
194
+ const localSync = await attachLocalSync({
195
+ runtime,
196
+ database: 'my-app',
197
+ documentId: 'project-1',
177
198
  });
178
199
 
179
- select(local.document, read => read.title.get());
180
- await local.undo();
181
- await local.dispose();
200
+ if (localSync.state().status === 'leader') {
201
+ runtime.update(tx => {
202
+ tx.write.title.set('Ship Doxum');
203
+ });
204
+
205
+ runtime.history.undo();
206
+ }
207
+
208
+ select(runtime, read => read.title.get()); // leader write or follower replay
209
+ await localSync.flush(); // persist observed leader commands / catch up a follower
210
+
211
+ await localSync.dispose();
182
212
  ```
183
213
 
184
- `local.document` is a read-only runtime capability, so selectors, views, and
185
- `doxum/react` can observe it but application code cannot bypass the durable
186
- write session. `local.update`, `undo`, and `redo` are asynchronous because they
187
- first acquire the cross-tab lock and commit to IndexedDB. Local-sync data must
188
- be JSON: non-JSON initial values or operation payloads are rejected before a
189
- document change becomes visible.
214
+ `attachLocalSync` first hydrates the passed runtime from IndexedDB; await it
215
+ before allowing reads or edits. It does not make runtime mutation asynchronous,
216
+ does not own `runtime.dispose()`, and does not expose a second undo API. Runtime
217
+ history is in-memory only: hydration and remote tail application invalidate it,
218
+ so an undo stack never transfers to a new leader or survives reopening a tab.
219
+ Local-sync data must be JSON. Because command validation happens after the
220
+ runtime commit, a non-JSON payload is reported as a post-commit attachment error
221
+ rather than rolling back an already observed document change.
190
222
 
191
223
  ## React
192
224
 
@@ -517,6 +517,7 @@ type DocumentReadable<TSchema extends DocumentSchema> = {
517
517
  subscribe(target: ImpactTarget<unknown> | readonly [ImpactTarget<unknown>, ...ImpactTarget<unknown>[]], listener: CommitListener<TSchema>): Unsubscribe;
518
518
  };
519
519
  type DocumentRuntime<TSchema extends DocumentSchema> = DocumentReadable<TSchema> & {
520
+ /** Schema configuration captured when this runtime was created. */readonly schema: TSchema;
520
521
  update<TResult>(run: (transaction: DocumentTransaction<TSchema>) => TResult, options?: {
521
522
  readonly source?: Extract<CommitSource, 'local' | 'system'>;
522
523
  readonly history?: boolean;
@@ -535,4 +536,4 @@ type DocumentRuntime<TSchema extends DocumentSchema> = DocumentReadable<TSchema>
535
536
  };
536
537
  //#endregion
537
538
  export { DocumentOperationUnion as $, variant as $t, MutationIssue as A, ObjectNode as At, WriterOfNode as B, VariantNode as Bt, contains as C, DocumentTreeValue as Ct, resolveAddress as D, ImpactTarget as Dt, read as E, FieldNode as Et, EntityWriter as F, SchemaPath as Ft, ListReader as G, map as Gt, DocumentReader as H, dict as Ht, FieldWriter as I, SingleNode as It, DictDeleteOperation as J, record as Jt, ReaderOfNode as K, object as Kt, ListWriter as L, TableNode as Lt, CollectionWriter as M, OptionalNode as Mt, DictionaryWriter as N, ReadonlyDocument as Nt, CollectionImpact as O, ListNode as Ot, DocumentWriter as P, RecordNode as Pt, DocumentOperation as Q, tree as Qt, MapWriter as R, TreeNode as Rt, AddressRef as S, DocumentTreeNode as St, overlaps as T, DocumentValueOfShape as Tt, EntityReader as U, field as Ut, CollectionReader as V, VariantShape as Vt, FieldReader as W, list as Wt, DictSetOperation as X, single as Xt, DictReplaceOperation as Y, schema as Yt, DocumentAnchor as Z, table as Zt, CommandFootprint as _, DictNode as _t, DocumentProblem as a, FieldSetOperation as at, decodeCommandFootprint as b, DocumentNode as bt, DocumentRuntime as c, ListRemoveOperation as ct, LocalHistory as d, TreeMoveOperation as dt, EntityCreateOperation as et, ObserverError as f, TreeRemoveOperation as ft, Unsubscribe as g, CollectionSelector as gt, TransactionResult as h, VariantReplaceOperation as ht, DocumentDisposedError as i, FieldClearOperation as it, MutationIssueCode as j, ObjectShape as jt, DocumentImpact as k, MapNode as kt, DocumentTransaction as l, ListReplaceOperation as lt, PreparedUpdateResult as m, TreeSetOperation as mt, DocumentCommit as n, EntityMoveOperation as nt, DocumentReadable as o, ListInsertOperation as ot, OperationResult as p, TreeReplaceOperation as pt, TreeReader as q, optional as qt, DocumentDiagnostic as r, EntityRemoveOperation as rt, DocumentReentrancyError as s, ListMoveOperation as st, CommitSource as t, EntityEntry as tt, HistoryState as u, TreeInsertOperation as ut, CommandFootprintTarget as v, DocumentAddress as vt, debugKey as w, DocumentValueOfNode as wt, footprintsOverlap as x, DocumentSchema as xt, commandFootprint as y, DocumentListConfig as yt, TreeWriter as z, ValueSelector as zt };
538
- //# sourceMappingURL=contract-DGkZcTiP.d.cts.map
539
+ //# sourceMappingURL=contract-BsY1XLiG.d.ts.map
@@ -517,6 +517,7 @@ type DocumentReadable<TSchema extends DocumentSchema> = {
517
517
  subscribe(target: ImpactTarget<unknown> | readonly [ImpactTarget<unknown>, ...ImpactTarget<unknown>[]], listener: CommitListener<TSchema>): Unsubscribe;
518
518
  };
519
519
  type DocumentRuntime<TSchema extends DocumentSchema> = DocumentReadable<TSchema> & {
520
+ /** Schema configuration captured when this runtime was created. */readonly schema: TSchema;
520
521
  update<TResult>(run: (transaction: DocumentTransaction<TSchema>) => TResult, options?: {
521
522
  readonly source?: Extract<CommitSource, 'local' | 'system'>;
522
523
  readonly history?: boolean;
@@ -535,4 +536,4 @@ type DocumentRuntime<TSchema extends DocumentSchema> = DocumentReadable<TSchema>
535
536
  };
536
537
  //#endregion
537
538
  export { DocumentOperationUnion as $, variant as $t, MutationIssue as A, ObjectNode as At, WriterOfNode as B, VariantNode as Bt, contains as C, DocumentTreeValue as Ct, resolveAddress as D, ImpactTarget as Dt, read as E, FieldNode as Et, EntityWriter as F, SchemaPath as Ft, ListReader as G, map as Gt, DocumentReader as H, dict as Ht, FieldWriter as I, SingleNode as It, DictDeleteOperation as J, record as Jt, ReaderOfNode as K, object as Kt, ListWriter as L, TableNode as Lt, CollectionWriter as M, OptionalNode as Mt, DictionaryWriter as N, ReadonlyDocument as Nt, CollectionImpact as O, ListNode as Ot, DocumentWriter as P, RecordNode as Pt, DocumentOperation as Q, tree as Qt, MapWriter as R, TreeNode as Rt, AddressRef as S, DocumentTreeNode as St, overlaps as T, DocumentValueOfShape as Tt, EntityReader as U, field as Ut, CollectionReader as V, VariantShape as Vt, FieldReader as W, list as Wt, DictSetOperation as X, single as Xt, DictReplaceOperation as Y, schema as Yt, DocumentAnchor as Z, table as Zt, CommandFootprint as _, DictNode as _t, DocumentProblem as a, FieldSetOperation as at, decodeCommandFootprint as b, DocumentNode as bt, DocumentRuntime as c, ListRemoveOperation as ct, LocalHistory as d, TreeMoveOperation as dt, EntityCreateOperation as et, ObserverError as f, TreeRemoveOperation as ft, Unsubscribe as g, CollectionSelector as gt, TransactionResult as h, VariantReplaceOperation as ht, DocumentDisposedError as i, FieldClearOperation as it, MutationIssueCode as j, ObjectShape as jt, DocumentImpact as k, MapNode as kt, DocumentTransaction as l, ListReplaceOperation as lt, PreparedUpdateResult as m, TreeSetOperation as mt, DocumentCommit as n, EntityMoveOperation as nt, DocumentReadable as o, ListInsertOperation as ot, OperationResult as p, TreeReplaceOperation as pt, TreeReader as q, optional as qt, DocumentDiagnostic as r, EntityRemoveOperation as rt, DocumentReentrancyError as s, ListMoveOperation as st, CommitSource as t, EntityEntry as tt, HistoryState as u, TreeInsertOperation as ut, CommandFootprintTarget as v, DocumentAddress as vt, debugKey as w, DocumentValueOfNode as wt, footprintsOverlap as x, DocumentSchema as xt, commandFootprint as y, DocumentListConfig as yt, TreeWriter as z, ValueSelector as zt };
538
- //# sourceMappingURL=contract-Dh3az8aN.d.ts.map
539
+ //# sourceMappingURL=contract-D8kjdfqT.d.cts.map
@@ -1,4 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-pbuEa-1d.js";
2
+ import { i as isRecord$1, t as cloneValue, u as profile } from "./ownership-B-VAPcce.js";
2
3
  //#region core/src/schema.ts
3
4
  const node = (value) => Object.freeze(value);
4
5
  const field = () => node({ kind: "field" });
@@ -58,186 +59,6 @@ const schema = (shape) => {
58
59
  return Object.freeze(result);
59
60
  };
60
61
  //#endregion
61
- //#region core/src/profile.ts
62
- const active = { current: void 0 };
63
- const profile = {
64
- clone: {
65
- call: () => {
66
- const value = active.current;
67
- if (value) value.clone.calls += 1;
68
- },
69
- node: (reason) => {
70
- const value = active.current;
71
- if (!value) return;
72
- value.clone.nodes[reason] += 1;
73
- },
74
- container: () => {
75
- const value = active.current;
76
- if (value) value.clone.containers += 1;
77
- },
78
- deepEqual: () => {
79
- const value = active.current;
80
- if (value) value.clone.deepEqual.calls += 1;
81
- },
82
- deepEqualContainer: () => {
83
- const value = active.current;
84
- if (value) value.clone.deepEqual.containers += 1;
85
- },
86
- initialDocument: () => {
87
- const value = active.current;
88
- if (value) value.clone.documents.initial += 1;
89
- }
90
- },
91
- mutation: {
92
- normalized: () => {
93
- const value = active.current;
94
- if (value) value.mutation.normalized += 1;
95
- },
96
- executed: () => {
97
- const value = active.current;
98
- if (value) value.mutation.executed += 1;
99
- },
100
- inverse: () => {
101
- const value = active.current;
102
- if (value) value.mutation.inverseCreated += 1;
103
- }
104
- },
105
- batch: {
106
- operation: () => {
107
- const value = active.current;
108
- if (value) value.batch.operations += 1;
109
- },
110
- entry: (amount = 1) => {
111
- const value = active.current;
112
- if (value) value.batch.entries += amount;
113
- },
114
- collectionResolved: () => {
115
- const value = active.current;
116
- if (value) value.batch.collectionsResolved += 1;
117
- },
118
- journalRecord: () => {
119
- const value = active.current;
120
- if (value) value.batch.journalRecords += 1;
121
- },
122
- rejected: () => {
123
- const value = active.current;
124
- if (value) value.batch.rejected += 1;
125
- },
126
- inverse: () => {
127
- const value = active.current;
128
- if (value) value.batch.inverseOperations += 1;
129
- },
130
- payloadTransferred: () => {
131
- const value = active.current;
132
- if (value) value.batch.payloadTransferred += 1;
133
- },
134
- payloadSnapshot: () => {
135
- const value = active.current;
136
- if (value) value.batch.payloadSnapshots += 1;
137
- }
138
- },
139
- journal: {
140
- subject: () => {
141
- const value = active.current;
142
- if (value) value.journal.subjects += 1;
143
- },
144
- comparison: () => {
145
- const value = active.current;
146
- if (value) value.journal.comparisons += 1;
147
- },
148
- absorbed: () => {
149
- const value = active.current;
150
- if (value) value.journal.absorbed += 1;
151
- },
152
- orderSnapshot: (items) => {
153
- const value = active.current;
154
- if (!value) return;
155
- value.journal.orderSnapshots += 1;
156
- value.journal.orderItems += items;
157
- }
158
- },
159
- address: {
160
- schemaStep: () => {
161
- const value = active.current;
162
- if (value) value.address.schemaSteps += 1;
163
- },
164
- documentStep: () => {
165
- const value = active.current;
166
- if (value) value.address.documentSteps += 1;
167
- },
168
- arrayCopied: () => {
169
- const value = active.current;
170
- if (value) value.address.arraysCopied += 1;
171
- },
172
- prefixComparison: () => {
173
- const value = active.current;
174
- if (value) value.address.prefixComparisons += 1;
175
- },
176
- segmentCompared: () => {
177
- const value = active.current;
178
- if (value) value.address.segmentsCompared += 1;
179
- }
180
- },
181
- impact: { affects: () => {
182
- const value = active.current;
183
- if (value) value.impact.affectsChecks += 1;
184
- } },
185
- reader: {
186
- session: () => {
187
- const value = active.current;
188
- if (value) value.reader.sessions += 1;
189
- },
190
- lookup: () => {
191
- const value = active.current;
192
- if (value) value.reader.nodeLookups += 1;
193
- },
194
- collectionIds: (amount = 1) => {
195
- const value = active.current;
196
- if (value) value.reader.collectionIds += amount;
197
- },
198
- structuralSnapshot: () => {
199
- const value = active.current;
200
- if (value) value.reader.structuralSnapshots += 1;
201
- }
202
- },
203
- collectionView: {
204
- mapped: () => {
205
- const value = active.current;
206
- if (value) value.collectionView.mappedItems += 1;
207
- },
208
- idsScanned: (amount = 1) => {
209
- const value = active.current;
210
- if (value) value.collectionView.idsScanned += amount;
211
- },
212
- arrayCopied: () => {
213
- const value = active.current;
214
- if (value) value.collectionView.arraysCopied += 1;
215
- }
216
- },
217
- materialized: {
218
- updated: () => {
219
- const value = active.current;
220
- if (value) value.materialized.updated += 1;
221
- },
222
- skipped: () => {
223
- const value = active.current;
224
- if (value) value.materialized.skipped += 1;
225
- },
226
- rebuilt: () => {
227
- const value = active.current;
228
- if (value) value.materialized.rebuilt += 1;
229
- },
230
- sourceChanged: () => {
231
- const value = active.current;
232
- if (value) value.materialized.sourceChanges += 1;
233
- },
234
- notification: () => {
235
- const value = active.current;
236
- if (value) value.materialized.notifications += 1;
237
- }
238
- }
239
- };
240
- //#endregion
241
62
  //#region core/src/address.ts
242
63
  const registries = /* @__PURE__ */ new WeakMap();
243
64
  const registryNode = () => ({ static: /* @__PURE__ */ new Map() });
@@ -257,14 +78,14 @@ const hashText = (value, seed) => {
257
78
  return hash >>> 0;
258
79
  };
259
80
  const appendAddressHash = (hash, segment) => hashText(segment, hashText("/", hash));
260
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
81
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
261
82
  const unwrap = (node) => {
262
83
  let current = node;
263
84
  while (current?.kind === "single") current = current.value;
264
85
  return current;
265
86
  };
266
87
  const variantNode = (node, value) => {
267
- const tag = isRecord$1(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
88
+ const tag = isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
268
89
  return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
269
90
  };
270
91
  const schemaRoot$1 = (schema) => ({
@@ -344,8 +165,8 @@ const nodeAt = (schema, address, document) => {
344
165
  return unwrap(node);
345
166
  };
346
167
  const readSegment = (value, segment) => {
347
- if (!isRecord$1(value) && !Array.isArray(value)) return void 0;
348
- if (isRecord$1(value) && "byId" in value && isRecord$1(value.byId)) {
168
+ if (!isRecord(value) && !Array.isArray(value)) return void 0;
169
+ if (isRecord(value) && "byId" in value && isRecord(value.byId)) {
349
170
  const entity = value.byId[segment];
350
171
  if (entity !== void 0 || Object.prototype.hasOwnProperty.call(value.byId, segment)) return entity;
351
172
  }
@@ -386,7 +207,7 @@ const resolveLocated = (schema, root, address) => {
386
207
  profile.address.documentStep();
387
208
  const last = address[address.length - 1];
388
209
  const resolved = step(node, current, last);
389
- if (!resolved.node || !isRecord$1(current) && !Array.isArray(current)) return void 0;
210
+ if (!resolved.node || !isRecord(current) && !Array.isArray(current)) return void 0;
390
211
  if (node?.kind === "table" || node?.kind === "map") collection = {
391
212
  address: address.slice(0, -1),
392
213
  addressHash,
@@ -394,7 +215,7 @@ const resolveLocated = (schema, root, address) => {
394
215
  ...collection ? { parent: collection } : {}
395
216
  };
396
217
  addressHash = appendAddressHash(addressHash, last);
397
- if (isRecord$1(current) && "byId" in current && isRecord$1(current.byId) && last in current.byId) return {
218
+ if (isRecord(current) && "byId" in current && isRecord(current.byId) && last in current.byId) return {
398
219
  addressHash,
399
220
  node: resolved.node,
400
221
  parent: current.byId,
@@ -415,9 +236,9 @@ const locate = (root, address) => {
415
236
  if (address.length === 0) return void 0;
416
237
  let current = root;
417
238
  for (let index = 0; index < address.length - 1; index += 1) current = readSegment(current, address[index]);
418
- if (!isRecord$1(current) && !Array.isArray(current)) return void 0;
239
+ if (!isRecord(current) && !Array.isArray(current)) return void 0;
419
240
  const last = address[address.length - 1];
420
- if (isRecord$1(current) && "byId" in current && isRecord$1(current.byId) && last in current.byId) return {
241
+ if (isRecord(current) && "byId" in current && isRecord(current.byId) && last in current.byId) return {
421
242
  parent: current.byId,
422
243
  key: last,
423
244
  value: current.byId[last]
@@ -475,65 +296,6 @@ const same = (left, right) => {
475
296
  };
476
297
  const bucket = (target) => address(target)[0];
477
298
  //#endregion
478
- //#region core/src/value/ownership.ts
479
- const stablePayloads = /* @__PURE__ */ new WeakSet();
480
- const markStablePayload = (value) => {
481
- if (Array.isArray(value) || isPlainObject(value)) stablePayloads.add(value);
482
- return value;
483
- };
484
- const isStablePayload = (value) => (Array.isArray(value) || isPlainObject(value)) && stablePayloads.has(value);
485
- const isPlainObject = (value) => {
486
- if (value === null || typeof value !== "object") return false;
487
- const prototype = Object.getPrototypeOf(value);
488
- return prototype === Object.prototype || prototype === null;
489
- };
490
- const countCloneReason = (reason) => {
491
- if (reason) profile.clone.node(reason);
492
- };
493
- const cloneValue = (value, reason) => {
494
- profile.clone.call();
495
- countCloneReason(reason);
496
- if (Array.isArray(value)) {
497
- profile.clone.container();
498
- const result = new Array(value.length);
499
- for (let index = 0; index < value.length; index += 1) result[index] = cloneValue(value[index], reason);
500
- return reason === "commit" || reason === "inverse" || reason === "snapshot" ? markStablePayload(Object.freeze(result)) : result;
501
- }
502
- if (isPlainObject(value)) {
503
- profile.clone.container();
504
- const result = Object.create(Object.getPrototypeOf(value));
505
- for (const key in value) if (Object.prototype.hasOwnProperty.call(value, key)) result[key] = cloneValue(value[key], reason);
506
- return reason === "commit" || reason === "inverse" || reason === "snapshot" ? markStablePayload(Object.freeze(result)) : result;
507
- }
508
- return value;
509
- };
510
- const ownPayload = (value, reason = "canonical") => Array.isArray(value) || isPlainObject(value) ? cloneValue(value, reason) : value;
511
- const transferPayload = (value) => {
512
- profile.batch.payloadTransferred();
513
- return value;
514
- };
515
- const snapshotPayload = (value, reason = "commit") => (profile.batch.payloadSnapshot(), ownPayload(value, reason));
516
- const deepEqual = (left, right) => {
517
- profile.clone.deepEqual();
518
- if (Object.is(left, right)) return true;
519
- if (Array.isArray(left) && Array.isArray(right)) {
520
- profile.clone.deepEqualContainer();
521
- if (left.length !== right.length) return false;
522
- for (let index = 0; index < left.length; index += 1) if (!deepEqual(left[index], right[index])) return false;
523
- return true;
524
- }
525
- if (isPlainObject(left) && isPlainObject(right)) {
526
- profile.clone.deepEqualContainer();
527
- const leftKeys = Object.keys(left);
528
- if (leftKeys.length !== Object.keys(right).length) return false;
529
- for (const key of leftKeys) if (!Object.prototype.hasOwnProperty.call(right, key) || !deepEqual(left[key], right[key])) return false;
530
- return true;
531
- }
532
- return false;
533
- };
534
- const sameStructuralValue = (left, right) => Object.is(left, right) || (Array.isArray(left) || isPlainObject(left)) && deepEqual(left, right);
535
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
536
- //#endregion
537
299
  //#region core/src/access/reader.ts
538
300
  const schemaRoot = (schema) => object(schema.shape);
539
301
  const assertActive = (context) => {
@@ -591,30 +353,30 @@ const readerFor = (node, context, address = []) => {
591
353
  rootId: () => {
592
354
  collectValue(context, address);
593
355
  const value = readAt(context, address);
594
- return isRecord(value) && typeof value.rootId === "string" ? value.rootId : void 0;
356
+ return isRecord$1(value) && typeof value.rootId === "string" ? value.rootId : void 0;
595
357
  },
596
358
  has: (id) => {
597
359
  collectValue(context, address);
598
360
  const value = readAt(context, address);
599
- return isRecord(value) && isRecord(value.nodes) && Object.prototype.hasOwnProperty.call(value.nodes, id);
361
+ return isRecord$1(value) && isRecord$1(value.nodes) && Object.prototype.hasOwnProperty.call(value.nodes, id);
600
362
  },
601
363
  value: (id) => {
602
364
  collectValue(context, address);
603
365
  const value = readAt(context, address);
604
366
  profile.reader.structuralSnapshot();
605
- return cloneValue(isRecord(value) && isRecord(value.nodes) && isRecord(value.nodes[id]) ? value.nodes[id].value : void 0, "reader");
367
+ return cloneValue(isRecord$1(value) && isRecord$1(value.nodes) && isRecord$1(value.nodes[id]) ? value.nodes[id].value : void 0, "reader");
606
368
  },
607
369
  parent: (id) => {
608
370
  collectValue(context, address);
609
371
  const value = readAt(context, address);
610
- const entry = isRecord(value) && isRecord(value.nodes) ? value.nodes[id] : void 0;
611
- return isRecord(entry) && typeof entry.parentId === "string" ? entry.parentId : void 0;
372
+ const entry = isRecord$1(value) && isRecord$1(value.nodes) ? value.nodes[id] : void 0;
373
+ return isRecord$1(entry) && typeof entry.parentId === "string" ? entry.parentId : void 0;
612
374
  },
613
375
  children: (id) => {
614
376
  collectValue(context, address);
615
377
  const value = readAt(context, address);
616
- const entry = isRecord(value) && isRecord(value.nodes) ? value.nodes[id] : void 0;
617
- return isRecord(entry) && Array.isArray(entry.children) ? [...entry.children] : [];
378
+ const entry = isRecord$1(value) && isRecord$1(value.nodes) ? value.nodes[id] : void 0;
379
+ return isRecord$1(entry) && Array.isArray(entry.children) ? [...entry.children] : [];
618
380
  }
619
381
  };
620
382
  if (node.kind === "table" || node.kind === "map") {
@@ -623,21 +385,21 @@ const readerFor = (node, context, address = []) => {
623
385
  ids: () => {
624
386
  collectCollection(context, address);
625
387
  const value = readAt(context, address);
626
- const ids = node.kind === "table" && isRecord(value) && Array.isArray(value.ids) ? [...value.ids] : isRecord(value) ? Object.keys(value) : [];
388
+ const ids = node.kind === "table" && isRecord$1(value) && Array.isArray(value.ids) ? [...value.ids] : isRecord$1(value) ? Object.keys(value) : [];
627
389
  profile.reader.collectionIds(ids.length);
628
390
  return ids;
629
391
  },
630
392
  has: (id) => {
631
393
  collectCollection(context, address, id);
632
394
  const value = readAt(context, address);
633
- const byId = node.kind === "table" && isRecord(value) && isRecord(value.byId) ? value.byId : value;
634
- return isRecord(byId) && Object.prototype.hasOwnProperty.call(byId, id);
395
+ const byId = node.kind === "table" && isRecord$1(value) && isRecord$1(value.byId) ? value.byId : value;
396
+ return isRecord$1(byId) && Object.prototype.hasOwnProperty.call(byId, id);
635
397
  },
636
398
  get: (id) => {
637
399
  collectCollection(context, address, id);
638
400
  const value = readAt(context, address);
639
- const byId = node.kind === "table" && isRecord(value) && isRecord(value.byId) ? value.byId : value;
640
- if (!isRecord(byId) || !Object.prototype.hasOwnProperty.call(byId, id)) return void 0;
401
+ const byId = node.kind === "table" && isRecord$1(value) && isRecord$1(value.byId) ? value.byId : value;
402
+ if (!isRecord$1(byId) || !Object.prototype.hasOwnProperty.call(byId, id)) return void 0;
641
403
  const cached = items?.get(id);
642
404
  if (cached) return cached;
643
405
  const reader = readerFor(node.value, context, [...address, id]);
@@ -650,7 +412,7 @@ const readerFor = (node, context, address = []) => {
650
412
  if (node.kind === "variant") return new Proxy({}, { get: (_target, property) => {
651
413
  if (typeof property === "symbol") return void 0;
652
414
  const value = readAt(context, address);
653
- const tag = isRecord(value) && typeof value[node.tag] === "string" ? String(value[node.tag]) : void 0;
415
+ const tag = isRecord$1(value) && typeof value[node.tag] === "string" ? String(value[node.tag]) : void 0;
654
416
  const child = (tag ? node.variants[tag] : void 0)?.shape[property];
655
417
  return child ? readerFor(child, context, [...address, property]) : void 0;
656
418
  } });
@@ -713,6 +475,22 @@ const readWith = (runtime, run, dependencies) => {
713
475
  }
714
476
  };
715
477
  //#endregion
716
- export { read as A, optional as B, impact_target_exports as C, locate as D, debugKey as E, dict as F, tree as G, schema as H, field as I, variant as K, list as L, resolveLocated as M, set as N, nodeAt as O, profile as P, map as R, id as S, contains as T, single as U, record as V, table as W, snapshotPayload as _, schemaOf as a, belongs as b, documentReader as c, deepEqual as d, isPlainObject as f, sameStructuralValue as g, ownPayload as h, readWith as i, resolveAddress as j, overlaps as k, readerFor as l, isStablePayload as m, bindRuntimeAccess as n, DocumentDisposedError as o, isRecord as p, documentOf as r, DocumentReentrancyError as s, accessOf as t, cloneValue as u, transferPayload as v, same as w, bucket as x, address as y, object as z };
478
+ //#region core/src/access/dependency.ts
479
+ const createDependencyTracker = () => {
480
+ const targets = [];
481
+ return {
482
+ record: (value) => {
483
+ if (!targets.some((entry) => same(entry, value))) targets.push(value);
484
+ },
485
+ clear: () => {
486
+ targets.length = 0;
487
+ },
488
+ size: () => targets.length,
489
+ some: (test) => targets.some(test),
490
+ snapshot: () => Object.freeze(targets.slice())
491
+ };
492
+ };
493
+ //#endregion
494
+ export { optional as A, resolveLocated as C, list as D, field as E, tree as F, variant as I, schema as M, single as N, map as O, table as P, resolveAddress as S, dict as T, debugKey as _, readWith as a, overlaps as b, DocumentReentrancyError as c, address as d, belongs as f, contains as g, impact_target_exports as h, documentOf as i, record as j, object as k, documentReader as l, id as m, accessOf as n, schemaOf as o, bucket as p, bindRuntimeAccess as r, DocumentDisposedError as s, createDependencyTracker as t, readerFor as u, locate as v, set as w, read as x, nodeAt as y };
717
495
 
718
- //# sourceMappingURL=access-5D4KKFm7.js.map
496
+ //# sourceMappingURL=dependency-C5_R1tvQ.js.map