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