doxum 0.1.28 → 0.1.30

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 (39) hide show
  1. package/README.md +3 -2
  2. package/dist/advanced.cjs +1 -1
  3. package/dist/advanced.js +1 -1
  4. package/dist/{driver-B5vdPohL.js → driver-B7RR-VfX.js} +31 -12
  5. package/dist/driver-B7RR-VfX.js.map +1 -0
  6. package/dist/{driver-BL91gn7_.cjs → driver-CN3dGxQI.cjs} +31 -12
  7. package/dist/driver-CN3dGxQI.cjs.map +1 -0
  8. package/dist/index.cjs +206 -133
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +206 -133
  11. package/dist/index.js.map +1 -1
  12. package/dist/local-sync.cjs +1 -1
  13. package/dist/local-sync.js +1 -1
  14. package/dist/{transform-DUleAsG0.cjs → transform-B6YxfR0R.cjs} +176 -50
  15. package/dist/transform-B6YxfR0R.cjs.map +1 -0
  16. package/dist/{transform-B4T3AcJr.js → transform-BQY9I6il.js} +147 -51
  17. package/dist/transform-BQY9I6il.js.map +1 -0
  18. package/package.json +3 -2
  19. package/skills/doxum-runtime/SKILL.md +64 -82
  20. package/skills/doxum-runtime/agents/openai.yaml +2 -2
  21. package/skills/doxum-runtime/references/document-runtime.md +370 -0
  22. package/skills/doxum-runtime/references/integrations.md +215 -0
  23. package/skills/doxum-runtime/references/{invariants.en.md → invariants.md} +5 -2
  24. package/skills/doxum-runtime/references/projections.md +467 -0
  25. package/skills/doxum-runtime/references/public-api.md +623 -0
  26. package/skills/doxum-runtime/references/recipes.md +493 -0
  27. package/dist/driver-B5vdPohL.js.map +0 -1
  28. package/dist/driver-BL91gn7_.cjs.map +0 -1
  29. package/dist/transform-B4T3AcJr.js.map +0 -1
  30. package/dist/transform-DUleAsG0.cjs.map +0 -1
  31. package/skills/doxum-runtime/references/api.en.md +0 -283
  32. package/skills/doxum-runtime/references/api.zh-CN.md +0 -276
  33. package/skills/doxum-runtime/references/guide.en.md +0 -138
  34. package/skills/doxum-runtime/references/guide.zh-CN.md +0 -116
  35. package/skills/doxum-runtime/references/invariants.zh-CN.md +0 -53
  36. package/skills/doxum-runtime/references/patterns.en.md +0 -162
  37. package/skills/doxum-runtime/references/patterns.zh-CN.md +0 -159
  38. package/skills/doxum-runtime/references/projections.en.md +0 -313
  39. package/skills/doxum-runtime/references/projections.zh-CN.md +0 -306
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_transform = require("./transform-DUleAsG0.cjs");
2
+ const require_transform = require("./transform-B6YxfR0R.cjs");
3
3
  const require_record = require("./record-ChYUi8PP.cjs");
4
- const require_driver = require("./driver-BL91gn7_.cjs");
4
+ const require_driver = require("./driver-CN3dGxQI.cjs");
5
5
  //#region core/src/schema/model.ts
6
6
  const schemaNodeOf = (schema) => schema;
7
7
  const collectionEntryNode = (node) => node.kind === "map" || node.kind === "table" || node.kind === "list" ? node.value : void 0;
@@ -569,18 +569,44 @@ const moveSelection = (current, selection, position) => {
569
569
  selected.add(key);
570
570
  }
571
571
  const moved = [];
572
- const remaining = [];
573
- for (const key of current) (selected.has(key) ? moved : remaining).push(key);
572
+ let remainingLength = 0;
573
+ let anchorIndex = -1;
574
+ const anchoredKey = position && ("before" in position ? position.before : "after" in position ? position.after : void 0);
575
+ for (const key of current) {
576
+ if (selected.has(key)) {
577
+ moved.push(key);
578
+ continue;
579
+ }
580
+ if (key === anchoredKey) anchorIndex = remainingLength;
581
+ remainingLength++;
582
+ }
574
583
  if (moved.length !== selected.size) return { kind: "missing" };
575
- if (!valid(remaining, position)) return { kind: "invalid-anchor" };
576
- const insertion = index(remaining, position);
584
+ let insertion;
585
+ if (!position || "at" in position && position.at === "end") insertion = remainingLength;
586
+ else if ("at" in position) {
587
+ if (position.at !== "start") return { kind: "invalid-anchor" };
588
+ insertion = 0;
589
+ } else {
590
+ if (anchorIndex < 0) return { kind: "invalid-anchor" };
591
+ insertion = "before" in position ? anchorIndex : anchorIndex + 1;
592
+ }
593
+ const order = new Array(current.length);
594
+ let outputIndex = 0;
595
+ let remainingIndex = 0;
596
+ let inserted = false;
597
+ for (const key of current) {
598
+ if (selected.has(key)) continue;
599
+ if (!inserted && remainingIndex === insertion) {
600
+ for (const movedKey of moved) order[outputIndex++] = movedKey;
601
+ inserted = true;
602
+ }
603
+ order[outputIndex++] = key;
604
+ remainingIndex++;
605
+ }
606
+ if (!inserted) for (const movedKey of moved) order[outputIndex++] = movedKey;
577
607
  return {
578
608
  kind: "ok",
579
- order: [
580
- ...remaining.slice(0, insertion),
581
- ...moved,
582
- ...remaining.slice(insertion)
583
- ]
609
+ order
584
610
  };
585
611
  };
586
612
  const index = (keys, anchor) => {
@@ -1373,6 +1399,17 @@ const contains = (parent, child) => {
1373
1399
  }
1374
1400
  return true;
1375
1401
  };
1402
+ /** Compare a virtual `[...base, segment]` address with an existing address without allocating it. */
1403
+ const compareExtended = (base, segment, other) => {
1404
+ require_transform.profile.address.prefixComparison();
1405
+ const length = base.length + 1;
1406
+ const common = Math.min(length, other.length);
1407
+ for (let index = 0; index < common; index += 1) {
1408
+ require_transform.profile.address.segmentCompared();
1409
+ if ((index < base.length ? base[index] : segment) !== other[index]) return "disjoint";
1410
+ }
1411
+ return length === other.length ? "equal" : length < other.length ? "ancestor" : "descendant";
1412
+ };
1376
1413
  //#endregion
1377
1414
  //#region core/src/impact.ts
1378
1415
  const createImpact = (schema, changes) => {
@@ -1799,7 +1836,7 @@ const reconstructBefore = (node, current, records, offset) => {
1799
1836
  };
1800
1837
  const sealMembers = (fact, changes) => {
1801
1838
  const members = [];
1802
- for (const member of fact.members?.values() ?? []) {
1839
+ if (fact.members) for (const member of fact.members.values()) {
1803
1840
  if (!member) continue;
1804
1841
  const key = memberKey(fact, member.key);
1805
1842
  const present = Object.hasOwn(fact.parent, key);
@@ -1883,7 +1920,7 @@ var ChangeRecorder = class {
1883
1920
  absorb(at, node, value, present) {
1884
1921
  const index = this.indexGroups();
1885
1922
  const children = [];
1886
- index.query((fact) => {
1923
+ index.forEachOverlap(at, (fact) => {
1887
1924
  if (fact.kind === "members") {
1888
1925
  if (fact.at.length < at.length) {
1889
1926
  if (fact.at.length + 1 === at.length) {
@@ -1902,18 +1939,19 @@ var ChangeRecorder = class {
1902
1939
  at: fact.at,
1903
1940
  group: fact
1904
1941
  });
1905
- for (const member of fact.members?.values() ?? []) {
1942
+ if (fact.members) for (const member of fact.members.values()) {
1906
1943
  if (!member) continue;
1907
- const memberAt = fact.at.concat(member.key);
1908
- if (contains(at, memberAt)) children.push({
1944
+ const compared = compareExtended(fact.at, member.key, at);
1945
+ if (compared !== "equal" && compared !== "descendant") continue;
1946
+ children.push({
1909
1947
  kind: "member",
1910
- at: memberAt,
1948
+ at: fact.at.concat(member.key),
1911
1949
  group: fact,
1912
1950
  member
1913
1951
  });
1914
1952
  }
1915
1953
  } else if (contains(at, fact.at)) children.push(fact);
1916
- })(at);
1954
+ });
1917
1955
  if (present) value = reconstructBefore(node, value, children, at.length);
1918
1956
  this.releaseCovered(children);
1919
1957
  return value;
@@ -1931,7 +1969,9 @@ var ChangeRecorder = class {
1931
1969
  (changedGroups ??= /* @__PURE__ */ new Set()).add(child.group);
1932
1970
  } else this.unregister(child);
1933
1971
  }
1934
- for (const group of changedGroups ?? []) if (emptyGroup(group)) this.unregister(group);
1972
+ if (changedGroups) {
1973
+ for (const group of changedGroups) if (emptyGroup(group)) this.unregister(group);
1974
+ }
1935
1975
  }
1936
1976
  register(fact) {
1937
1977
  this.facts.add(fact);
@@ -2880,12 +2920,7 @@ const createKeyedGet = (source, key, equality = Object.is) => {
2880
2920
  } else if (sourceRevision !== collection.revision) {
2881
2921
  const change = collection.change;
2882
2922
  if (!change || change.kind === "reset") affected = true;
2883
- else if (selected !== void 0) {
2884
- for (const changedKey of require_transform.collectionChange.keys(change)) if (changedKey === selected) {
2885
- affected = true;
2886
- break;
2887
- }
2888
- }
2923
+ else if (selected !== void 0) affected = require_transform.collectionChangeTouchesKey(change, selected);
2889
2924
  }
2890
2925
  if (affected) output.output.set(selected === void 0 ? void 0 : collection.read.get(selected));
2891
2926
  sourceRevision = collection.revision;
@@ -3049,9 +3084,7 @@ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3049
3084
  outputs: [{
3050
3085
  kind: "collection",
3051
3086
  equality: (previous, next) => {
3052
- const left = previous;
3053
- const right = next;
3054
- return left.length === right.length && left.every((key, index) => key === right[index]);
3087
+ return require_transform.sameArray(previous, next);
3055
3088
  }
3056
3089
  }],
3057
3090
  create: () => {
@@ -3095,11 +3128,14 @@ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3095
3128
  output.output.set(group, Object.freeze(members));
3096
3129
  };
3097
3130
  const publishGroupOrder = (output) => {
3098
- const order = [...groupRanks.entries()].sort(([leftKey, left], [rightKey, right]) => {
3131
+ const order = [...groupRanks.keys()];
3132
+ order.sort((leftKey, rightKey) => {
3133
+ const left = groupRanks.get(leftKey);
3134
+ const right = groupRanks.get(rightKey);
3099
3135
  if (left.source !== right.source) return left.source - right.source;
3100
3136
  if (left.selector !== right.selector) return left.selector - right.selector;
3101
3137
  return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
3102
- }).map(([group]) => group);
3138
+ });
3103
3139
  output.output.order(order);
3104
3140
  };
3105
3141
  const rebuild = (driver, sources, output) => {
@@ -3151,7 +3187,8 @@ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3151
3187
  return;
3152
3188
  } else {
3153
3189
  for (const entry of change.removed) {
3154
- orderDirty ||= removeSourceKey(entry.key, affected);
3190
+ const changed = removeSourceKey(entry.key, affected);
3191
+ orderDirty ||= changed;
3155
3192
  dependencies.remove(entry.key);
3156
3193
  dirty.delete(entry.key);
3157
3194
  }
@@ -3164,7 +3201,10 @@ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3164
3201
  }
3165
3202
  all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
3166
3203
  if (all) for (const key of driver.read.ids()) dirty.add(key);
3167
- for (const key of dirty) orderDirty ||= selectGroups(driver, evaluation.sources, key, affected);
3204
+ for (const key of dirty) {
3205
+ const changed = selectGroups(driver, evaluation.sources, key, affected);
3206
+ orderDirty ||= changed;
3207
+ }
3168
3208
  if (structural) markAllGroups(output, affected);
3169
3209
  publishAffected(affected, output, orderDirty);
3170
3210
  dependencies.remember(evaluation.sources);
@@ -3368,13 +3408,14 @@ const createPublishedCollection = () => {
3368
3408
  },
3369
3409
  publish: (staged, nextIds, reset) => {
3370
3410
  if (reset) {
3371
- const entries = [];
3372
- for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
3373
- index = PersistentKeyedIndex.from(entries);
3374
3411
  values.clear();
3375
- } else for (const [key, entry] of staged) index = entry.present ? index.set(key, entry.value) : index.remove(key);
3376
- for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3377
- else values.delete(key);
3412
+ for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3413
+ index = PersistentKeyedIndex.from(values);
3414
+ } else for (const [key, entry] of staged) {
3415
+ index = entry.present ? index.set(key, entry.value) : index.remove(key);
3416
+ if (entry.present) values.set(key, entry.value);
3417
+ else values.delete(key);
3418
+ }
3378
3419
  ids = nextIds;
3379
3420
  },
3380
3421
  release: () => {
@@ -3387,7 +3428,7 @@ const createPublishedCollection = () => {
3387
3428
  /** Owns staged keyed intent, exact transitions and published collection state. */
3388
3429
  const createCollectionOutput = (binding) => {
3389
3430
  const published = createPublishedCollection();
3390
- let staged = /* @__PURE__ */ new Map();
3431
+ const staged = /* @__PURE__ */ new Map();
3391
3432
  let nextIds = published.ids();
3392
3433
  let change;
3393
3434
  let initialized = false;
@@ -3403,12 +3444,15 @@ const createCollectionOutput = (binding) => {
3403
3444
  return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : published.get(key);
3404
3445
  };
3405
3446
  const deriveIds = () => {
3406
- const result = cleared ? [] : published.ids().filter(hasNext);
3447
+ const result = [];
3448
+ if (!cleared) {
3449
+ for (const key of published.ids()) if (hasNext(key)) result.push(key);
3450
+ }
3407
3451
  for (const [key, entry] of staged) if (entry.present && (cleared || !published.has(key))) result.push(key);
3408
3452
  return result;
3409
3453
  };
3410
3454
  const begin = (active, initialize) => {
3411
- staged = /* @__PURE__ */ new Map();
3455
+ staged.clear();
3412
3456
  nextIds = published.ids();
3413
3457
  change = void 0;
3414
3458
  explicitOrder = void 0;
@@ -3425,7 +3469,9 @@ const createCollectionOutput = (binding) => {
3425
3469
  },
3426
3470
  ids: () => {
3427
3471
  require_transform.assertScope(active);
3428
- return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
3472
+ if (!next) return published.ids();
3473
+ if (explicitOrder) return explicitOrder;
3474
+ return Object.freeze(deriveIds());
3429
3475
  }
3430
3476
  });
3431
3477
  return {
@@ -3446,7 +3492,7 @@ const createCollectionOutput = (binding) => {
3446
3492
  },
3447
3493
  order: (order) => {
3448
3494
  require_transform.assertScope(active);
3449
- explicitOrder = Object.freeze([...order]);
3495
+ explicitOrder = require_transform.snapshotArray(order);
3450
3496
  }
3451
3497
  }
3452
3498
  };
@@ -3456,8 +3502,6 @@ const createCollectionOutput = (binding) => {
3456
3502
  for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3457
3503
  }
3458
3504
  require_transform.profile.projection("touchedKeys", staged.size);
3459
- const added = /* @__PURE__ */ new Set();
3460
- const removed = /* @__PURE__ */ new Set();
3461
3505
  const addedEntries = [];
3462
3506
  const updatedEntries = [];
3463
3507
  const removedEntries = [];
@@ -3465,7 +3509,6 @@ const createCollectionOutput = (binding) => {
3465
3509
  const existed = published.has(key);
3466
3510
  if (entry.present) {
3467
3511
  if (!existed) {
3468
- added.add(key);
3469
3512
  addedEntries.push({
3470
3513
  key,
3471
3514
  after: entry.value
@@ -3481,19 +3524,28 @@ const createCollectionOutput = (binding) => {
3481
3524
  else if (!nextReset) staged.delete(key);
3482
3525
  continue;
3483
3526
  }
3484
- if (existed) {
3485
- removed.add(key);
3486
- removedEntries.push({
3487
- key,
3488
- before: published.get(key)
3489
- });
3490
- } else staged.delete(key);
3527
+ if (existed) removedEntries.push({
3528
+ key,
3529
+ before: published.get(key)
3530
+ });
3531
+ else staged.delete(key);
3491
3532
  }
3492
3533
  const ids = published.ids();
3493
- if (explicitOrder || added.size || removed.size) {
3494
- const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
3495
- if (new Set(order).size !== order.length || order.length !== published.size() + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
3496
- nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
3534
+ if (explicitOrder) {
3535
+ const expectedLength = published.size() + addedEntries.length - removedEntries.length;
3536
+ const seen = /* @__PURE__ */ new Set();
3537
+ for (const key of explicitOrder) {
3538
+ if (seen.has(key) || !hasNext(key)) throw new TypeError("Projection order must contain every key exactly once.");
3539
+ seen.add(key);
3540
+ }
3541
+ if (seen.size !== expectedLength) throw new TypeError("Projection order must contain every key exactly once.");
3542
+ nextIds = require_transform.sameArray(ids, explicitOrder) ? ids : explicitOrder;
3543
+ } else if (addedEntries.length || removedEntries.length) {
3544
+ const order = new Array(published.size() + addedEntries.length - removedEntries.length);
3545
+ let index = 0;
3546
+ for (const key of ids) if (hasNext(key)) order[index++] = key;
3547
+ for (const entry of addedEntries) order[index++] = entry.key;
3548
+ nextIds = require_transform.sameArray(ids, order) ? ids : Object.freeze(order);
3497
3549
  } else nextIds = ids;
3498
3550
  require_transform.profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
3499
3551
  change = nextReset ? Object.freeze({ kind: "reset" }) : require_transform.createCollectionChange({
@@ -3810,18 +3862,19 @@ const createProcessor = (scheduler, definition, dependencies) => {
3810
3862
  };
3811
3863
  //#endregion
3812
3864
  //#region core/src/projection/readable/listeners.ts
3813
- /** Exhaustively notifies one projection-readable listener snapshot. */
3814
- const notifyProjectionListeners = (listeners, message) => {
3815
- const snapshot = [...listeners];
3816
- const failures = [];
3817
- for (const listener of snapshot) try {
3865
+ /** Exhaustively notifies an already-stable projection listener snapshot. */
3866
+ const notifyProjectionListenerSnapshot = (listeners, message) => {
3867
+ let failures;
3868
+ for (const listener of listeners) try {
3818
3869
  listener();
3819
3870
  } catch (error) {
3820
- failures.push(error);
3871
+ (failures ??= []).push(error);
3821
3872
  }
3822
- if (failures.length === 1) throw failures[0];
3823
- if (failures.length) throw new AggregateError(failures, message);
3873
+ if (failures?.length === 1) throw failures[0];
3874
+ if (failures?.length) throw new AggregateError(failures, message);
3824
3875
  };
3876
+ /** Snapshots a mutable listener collection before exhaustive notification. */
3877
+ const notifyProjectionListeners = (listeners, message) => notifyProjectionListenerSnapshot([...listeners], message);
3825
3878
  //#endregion
3826
3879
  //#region core/src/projection/readable/selection.ts
3827
3880
  let activeTracker;
@@ -3876,8 +3929,7 @@ const selectionAffects = (selection, change) => {
3876
3929
  if (!change || change.kind === "reset") return true;
3877
3930
  if (selection.all) return true;
3878
3931
  if (selection.structure && require_transform.collectionHasStructuralChange(change)) return true;
3879
- for (const key of require_transform.collectionChange.keys(change)) if (selection.keys.has(key)) return true;
3880
- return false;
3932
+ return require_transform.collectionChangeIntersects(change, selection.keys);
3881
3933
  };
3882
3934
  const createDirectReadable = (source) => Object.freeze({
3883
3935
  current: source.current,
@@ -3919,7 +3971,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3919
3971
  activeTracker = previousTracker;
3920
3972
  }
3921
3973
  const nextSelection = Object.freeze({
3922
- keys: new Set(tracker.keys),
3974
+ keys: tracker.keys,
3923
3975
  all: tracker.all || isMapLike(current) && tracker.keys.size === 0 && !tracker.structure,
3924
3976
  structure: tracker.structure
3925
3977
  });
@@ -4161,10 +4213,11 @@ const clearDocumentDirty = (dirty) => {
4161
4213
  dirty.treeNodes.clear();
4162
4214
  dirty.children.clear();
4163
4215
  };
4164
- const locate = (dirty, path) => {
4216
+ const locateRange = (dirty, path, start, tail) => {
4165
4217
  let current = dirty;
4166
- for (const key of path) {
4218
+ for (let index = start; index < path.length; index++) {
4167
4219
  if (current.replace) return void 0;
4220
+ const key = path[index];
4168
4221
  let child = current.children.get(key);
4169
4222
  if (!child) {
4170
4223
  child = createDocumentDirty();
@@ -4172,10 +4225,17 @@ const locate = (dirty, path) => {
4172
4225
  }
4173
4226
  current = child;
4174
4227
  }
4175
- return current;
4228
+ if (tail === void 0) return current;
4229
+ if (current.replace) return void 0;
4230
+ let child = current.children.get(tail);
4231
+ if (!child) {
4232
+ child = createDocumentDirty();
4233
+ current.children.set(tail, child);
4234
+ }
4235
+ return child;
4176
4236
  };
4177
- const markDocumentReplace = (dirty, path) => {
4178
- const current = locate(dirty, path);
4237
+ const replaceAt = (dirty, path, start, tail) => {
4238
+ const current = locateRange(dirty, path, start, tail);
4179
4239
  if (!current) return;
4180
4240
  current.replace = true;
4181
4241
  current.order = false;
@@ -4183,15 +4243,18 @@ const markDocumentReplace = (dirty, path) => {
4183
4243
  current.treeNodes.clear();
4184
4244
  current.children.clear();
4185
4245
  };
4186
- const markDocumentOrder = (dirty, path) => {
4187
- const current = locate(dirty, path);
4246
+ const markDocumentReplace = (dirty, path) => {
4247
+ replaceAt(dirty, path, 0);
4248
+ };
4249
+ const markDocumentOrderRange = (dirty, path, start) => {
4250
+ const current = locateRange(dirty, path, start);
4188
4251
  if (current && !current.replace) current.order = true;
4189
4252
  };
4190
- const markDocumentTree = (dirty, path, root, nodes) => {
4191
- const current = locate(dirty, path);
4253
+ const markDocumentTree = (dirty, path, start, root, nodes) => {
4254
+ const current = locateRange(dirty, path, start);
4192
4255
  if (!current || current.replace) return;
4193
4256
  current.treeRoot ||= root;
4194
- for (const id of nodes) current.treeNodes.add(id);
4257
+ for (const node of nodes) current.treeNodes.add(node.id);
4195
4258
  };
4196
4259
  const createCollectionPending = () => ({
4197
4260
  reset: false,
@@ -4221,19 +4284,19 @@ const mergeValueChanges = (selector, dirty, changes) => {
4221
4284
  const at = selector.address;
4222
4285
  for (const change of changes.changes) {
4223
4286
  if (change.kind === "reset") {
4224
- markDocumentReplace(dirty, []);
4287
+ replaceAt(dirty, at, at.length);
4225
4288
  return;
4226
4289
  }
4227
4290
  if (change.kind === "members") {
4228
4291
  for (const member of change.members) {
4229
- const changed = [...change.at, member.key];
4230
- if (contains(changed, at)) markDocumentReplace(dirty, []);
4231
- else if (contains(at, changed)) markDocumentReplace(dirty, changed.slice(at.length));
4292
+ const compared = compareExtended(change.at, member.key, at);
4293
+ if (compared === "ancestor" || compared === "equal") replaceAt(dirty, at, at.length);
4294
+ else if (compared === "descendant") replaceAt(dirty, change.at, at.length, member.key);
4232
4295
  }
4233
- if (change.order && contains(at, change.at)) markDocumentOrder(dirty, change.at.slice(at.length));
4296
+ if (change.order && contains(at, change.at)) markDocumentOrderRange(dirty, change.at, at.length);
4234
4297
  continue;
4235
4298
  }
4236
- if (contains(at, change.at)) markDocumentTree(dirty, change.at.slice(at.length), change.before !== change.after, change.nodes.map((node) => node.id));
4299
+ if (contains(at, change.at)) markDocumentTree(dirty, change.at, at.length, change.before !== change.after, change.nodes);
4237
4300
  }
4238
4301
  };
4239
4302
  const mergeCollectionChanges = (selector, pending, changes) => {
@@ -4245,13 +4308,15 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4245
4308
  return;
4246
4309
  }
4247
4310
  if (selector.tree?.kind === "nodes") {
4248
- if (change.kind === "members") {
4249
- for (const member of change.members) if (contains([...change.at, member.key], at)) {
4311
+ if (change.kind === "members") for (const member of change.members) {
4312
+ const compared = compareExtended(change.at, member.key, at);
4313
+ if (compared === "ancestor" || compared === "equal") {
4250
4314
  resetCollectionPending(pending);
4251
4315
  return;
4252
4316
  }
4253
- } else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
4254
- markDocumentReplace(dirtyEntry(pending, node.id), []);
4317
+ }
4318
+ else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
4319
+ replaceAt(dirtyEntry(pending, node.id), at, at.length);
4255
4320
  pending.order ||= node.kind !== "updated";
4256
4321
  }
4257
4322
  else if (contains(change.at, at)) {
@@ -4262,34 +4327,27 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4262
4327
  }
4263
4328
  if (change.kind === "members") {
4264
4329
  for (const member of change.members) {
4265
- const changed = [...change.at, member.key];
4266
- if (contains(changed, at)) {
4267
- resetCollectionPending(pending);
4268
- return;
4269
- }
4270
- if (!contains(at, changed)) continue;
4271
- const relative = changed.slice(at.length);
4272
- if (!relative.length) {
4330
+ const compared = compareExtended(change.at, member.key, at);
4331
+ if (compared === "ancestor" || compared === "equal") {
4273
4332
  resetCollectionPending(pending);
4274
4333
  return;
4275
4334
  }
4276
- markDocumentReplace(dirtyEntry(pending, relative[0]), relative.slice(1));
4277
- if (relative.length === 1 && member.kind !== "updated") pending.order = true;
4278
- }
4279
- if (change.order && contains(at, change.at)) {
4280
- const relative = change.at.slice(at.length);
4281
- if (!relative.length) pending.order = true;
4282
- else markDocumentOrder(dirtyEntry(pending, relative[0]), relative.slice(1));
4335
+ if (compared !== "descendant") continue;
4336
+ if (at.length === change.at.length) {
4337
+ replaceAt(dirtyEntry(pending, member.key), change.at, change.at.length);
4338
+ if (member.kind !== "updated") pending.order = true;
4339
+ } else replaceAt(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1, member.key);
4283
4340
  }
4341
+ if (change.order && contains(at, change.at)) if (change.at.length === at.length) pending.order = true;
4342
+ else markDocumentOrderRange(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1);
4284
4343
  continue;
4285
4344
  }
4286
4345
  if (!contains(at, change.at)) continue;
4287
- const relative = change.at.slice(at.length);
4288
- if (!relative.length) {
4346
+ if (change.at.length === at.length) {
4289
4347
  resetCollectionPending(pending);
4290
4348
  return;
4291
4349
  }
4292
- markDocumentTree(dirtyEntry(pending, relative[0]), relative.slice(1), change.before !== change.after, change.nodes.map((node) => node.id));
4350
+ markDocumentTree(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1, change.before !== change.after, change.nodes);
4293
4351
  }
4294
4352
  };
4295
4353
  //#endregion
@@ -4418,16 +4476,29 @@ const stageCollectionRead = (state, read, active, options) => {
4418
4476
  evaluation.output.order(ids);
4419
4477
  } else {
4420
4478
  const previous = state.current();
4421
- const keys = options.candidates ? [...options.candidates] : [...new Set([...previous.ids(), ...read.ids()])];
4422
- if (!options.candidates) require_transform.profile.collectionView.idsScanned(keys.length);
4423
- for (const key of keys) if (read.has(key)) {
4479
+ let currentIds;
4480
+ const ids = () => currentIds ??= read.ids();
4481
+ if (options.candidates) for (const key of options.candidates) if (read.has(key)) {
4424
4482
  require_transform.profile.collectionView.mapped();
4425
4483
  evaluation.output.set(key, read.get(key));
4426
4484
  } else evaluation.output.remove(key);
4485
+ else {
4486
+ const previousIds = previous.ids();
4487
+ const nextIds = ids();
4488
+ require_transform.profile.collectionView.idsScanned(previousIds.length + nextIds.length);
4489
+ for (const key of previousIds) if (read.has(key)) {
4490
+ require_transform.profile.collectionView.mapped();
4491
+ evaluation.output.set(key, read.get(key));
4492
+ } else evaluation.output.remove(key);
4493
+ for (const key of nextIds) if (!previous.has(key)) {
4494
+ require_transform.profile.collectionView.mapped();
4495
+ evaluation.output.set(key, read.get(key));
4496
+ }
4497
+ }
4427
4498
  if (options.orderMayChange || !options.candidates) {
4428
- const ids = read.ids();
4429
- if (options.candidates) require_transform.profile.collectionView.idsScanned(ids.length);
4430
- evaluation.output.order(ids);
4499
+ const nextIds = ids();
4500
+ if (options.candidates) require_transform.profile.collectionView.idsScanned(nextIds.length);
4501
+ evaluation.output.order(nextIds);
4431
4502
  }
4432
4503
  }
4433
4504
  return state.seal(options.reset, options.isEqual ?? Object.is);
@@ -4721,13 +4792,15 @@ const createDocumentSourceRegistry = (scheduler) => {
4721
4792
  const snapshots = /* @__PURE__ */ new Map();
4722
4793
  const replacement = createDocumentDirty();
4723
4794
  if (pending.reset) markDocumentReplace(replacement, []);
4795
+ let cachedIds;
4724
4796
  const ids = () => {
4725
4797
  require_transform.assertScope(active);
4726
- if (treeNodes) return node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4727
- if (node?.kind === "map" && require_record.isRecord(value)) return Object.freeze(Object.keys(value));
4728
- if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return Object.freeze([...value.ids]);
4729
- if (node?.kind === "list" && Array.isArray(value)) return Object.freeze(toArray(indexedKeys(value, node.keyOf)));
4730
- return Object.freeze([]);
4798
+ if (cachedIds) return cachedIds;
4799
+ if (treeNodes) return cachedIds = node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4800
+ if (node?.kind === "map" && require_record.isRecord(value)) return cachedIds = Object.freeze(Object.keys(value));
4801
+ if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return cachedIds = Object.freeze([...value.ids]);
4802
+ if (node?.kind === "list" && Array.isArray(value)) return cachedIds = Object.freeze(toArray(indexedKeys(value, node.keyOf)));
4803
+ return cachedIds = Object.freeze([]);
4731
4804
  };
4732
4805
  const has = (key) => {
4733
4806
  require_transform.assertScope(active);
@@ -5215,7 +5288,6 @@ const createSourceRegistry = (scheduler) => {
5215
5288
  };
5216
5289
  //#endregion
5217
5290
  //#region core/src/projection/readable/keyed.ts
5218
- const sameKeys = (previous, next) => previous.length === next.length && previous.every((key, index) => key === next[index]);
5219
5291
  /** Runtime-owned keyed consumer family over one materialized collection output. */
5220
5292
  const createProjectionItems = (source, checkOwner) => {
5221
5293
  if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
@@ -5235,7 +5307,11 @@ const createProjectionItems = (source, checkOwner) => {
5235
5307
  for (const listener of listeners) notifications.push(listener);
5236
5308
  };
5237
5309
  const flushNotifications = () => {
5238
- notifyProjectionListeners(notifications.splice(0), "Projection item listeners failed.");
5310
+ try {
5311
+ notifyProjectionListenerSnapshot(notifications, "Projection item listeners failed.");
5312
+ } finally {
5313
+ notifications.length = 0;
5314
+ }
5239
5315
  };
5240
5316
  const unregisterWaiting = (state) => {
5241
5317
  const states = waiting.get(state.key);
@@ -5278,16 +5354,11 @@ const createProjectionItems = (source, checkOwner) => {
5278
5354
  publishState(state, void 0);
5279
5355
  state.ended = true;
5280
5356
  };
5281
- const statesFor = (key) => {
5282
- const result = [];
5357
+ const terminateKey = (key) => {
5283
5358
  const canonical = active.get(key);
5284
- if (canonical) result.push(canonical);
5359
+ if (canonical) terminateState(canonical);
5285
5360
  const extra = followers.get(key);
5286
- if (extra) result.push(...extra);
5287
- return result;
5288
- };
5289
- const terminateKey = (key) => {
5290
- for (const state of statesFor(key)) terminateState(state);
5361
+ if (extra) for (const state of extra) terminateState(state);
5291
5362
  active.delete(key);
5292
5363
  followers.delete(key);
5293
5364
  };
@@ -5298,12 +5369,14 @@ const createProjectionItems = (source, checkOwner) => {
5298
5369
  for (const state of states) attachActive(state, value);
5299
5370
  };
5300
5371
  const updateKey = (key, value) => {
5301
- for (const state of statesFor(key)) publishState(state, value);
5372
+ const canonical = active.get(key);
5373
+ if (canonical) publishState(canonical, value);
5374
+ const extra = followers.get(key);
5375
+ if (extra) for (const state of extra) publishState(state, value);
5302
5376
  };
5303
5377
  const updateKeys = (current) => {
5304
- const next = Object.freeze([...current.keys()]);
5305
- if (sameKeys(keysValue, next)) return;
5306
- keysValue = next;
5378
+ if (require_transform.iterableMatchesArray(current.keys(), keysValue)) return;
5379
+ keysValue = Object.freeze([...current.keys()]);
5307
5380
  keysRevision++;
5308
5381
  notify(keyListeners);
5309
5382
  };
@@ -5346,9 +5419,9 @@ const createProjectionItems = (source, checkOwner) => {
5346
5419
  return state;
5347
5420
  };
5348
5421
  const reconcile = (current) => {
5349
- for (const key of [...active.keys()]) if (!current.has(key)) terminateKey(key);
5422
+ for (const key of active.keys()) if (!current.has(key)) terminateKey(key);
5350
5423
  else updateKey(key, current.get(key));
5351
- for (const key of [...waiting.keys()]) if (current.has(key)) activateWaiting(key, current.get(key));
5424
+ for (const key of waiting.keys()) if (current.has(key)) activateWaiting(key, current.get(key));
5352
5425
  updateKeys(current);
5353
5426
  };
5354
5427
  const onChange = (change) => {