doxum 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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) => {
@@ -3368,13 +3404,14 @@ const createPublishedCollection = () => {
3368
3404
  },
3369
3405
  publish: (staged, nextIds, reset) => {
3370
3406
  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
3407
  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);
3408
+ for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
3409
+ index = PersistentKeyedIndex.from(values);
3410
+ } else for (const [key, entry] of staged) {
3411
+ index = entry.present ? index.set(key, entry.value) : index.remove(key);
3412
+ if (entry.present) values.set(key, entry.value);
3413
+ else values.delete(key);
3414
+ }
3378
3415
  ids = nextIds;
3379
3416
  },
3380
3417
  release: () => {
@@ -3387,7 +3424,7 @@ const createPublishedCollection = () => {
3387
3424
  /** Owns staged keyed intent, exact transitions and published collection state. */
3388
3425
  const createCollectionOutput = (binding) => {
3389
3426
  const published = createPublishedCollection();
3390
- let staged = /* @__PURE__ */ new Map();
3427
+ const staged = /* @__PURE__ */ new Map();
3391
3428
  let nextIds = published.ids();
3392
3429
  let change;
3393
3430
  let initialized = false;
@@ -3403,12 +3440,15 @@ const createCollectionOutput = (binding) => {
3403
3440
  return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : published.get(key);
3404
3441
  };
3405
3442
  const deriveIds = () => {
3406
- const result = cleared ? [] : published.ids().filter(hasNext);
3443
+ const result = [];
3444
+ if (!cleared) {
3445
+ for (const key of published.ids()) if (hasNext(key)) result.push(key);
3446
+ }
3407
3447
  for (const [key, entry] of staged) if (entry.present && (cleared || !published.has(key))) result.push(key);
3408
3448
  return result;
3409
3449
  };
3410
3450
  const begin = (active, initialize) => {
3411
- staged = /* @__PURE__ */ new Map();
3451
+ staged.clear();
3412
3452
  nextIds = published.ids();
3413
3453
  change = void 0;
3414
3454
  explicitOrder = void 0;
@@ -3425,7 +3465,9 @@ const createCollectionOutput = (binding) => {
3425
3465
  },
3426
3466
  ids: () => {
3427
3467
  require_transform.assertScope(active);
3428
- return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
3468
+ if (!next) return published.ids();
3469
+ if (explicitOrder) return explicitOrder;
3470
+ return Object.freeze(deriveIds());
3429
3471
  }
3430
3472
  });
3431
3473
  return {
@@ -3446,7 +3488,7 @@ const createCollectionOutput = (binding) => {
3446
3488
  },
3447
3489
  order: (order) => {
3448
3490
  require_transform.assertScope(active);
3449
- explicitOrder = Object.freeze([...order]);
3491
+ explicitOrder = require_transform.snapshotArray(order);
3450
3492
  }
3451
3493
  }
3452
3494
  };
@@ -3456,8 +3498,6 @@ const createCollectionOutput = (binding) => {
3456
3498
  for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
3457
3499
  }
3458
3500
  require_transform.profile.projection("touchedKeys", staged.size);
3459
- const added = /* @__PURE__ */ new Set();
3460
- const removed = /* @__PURE__ */ new Set();
3461
3501
  const addedEntries = [];
3462
3502
  const updatedEntries = [];
3463
3503
  const removedEntries = [];
@@ -3465,7 +3505,6 @@ const createCollectionOutput = (binding) => {
3465
3505
  const existed = published.has(key);
3466
3506
  if (entry.present) {
3467
3507
  if (!existed) {
3468
- added.add(key);
3469
3508
  addedEntries.push({
3470
3509
  key,
3471
3510
  after: entry.value
@@ -3481,19 +3520,28 @@ const createCollectionOutput = (binding) => {
3481
3520
  else if (!nextReset) staged.delete(key);
3482
3521
  continue;
3483
3522
  }
3484
- if (existed) {
3485
- removed.add(key);
3486
- removedEntries.push({
3487
- key,
3488
- before: published.get(key)
3489
- });
3490
- } else staged.delete(key);
3523
+ if (existed) removedEntries.push({
3524
+ key,
3525
+ before: published.get(key)
3526
+ });
3527
+ else staged.delete(key);
3491
3528
  }
3492
3529
  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]);
3530
+ if (explicitOrder) {
3531
+ const expectedLength = published.size() + addedEntries.length - removedEntries.length;
3532
+ const seen = /* @__PURE__ */ new Set();
3533
+ for (const key of explicitOrder) {
3534
+ if (seen.has(key) || !hasNext(key)) throw new TypeError("Projection order must contain every key exactly once.");
3535
+ seen.add(key);
3536
+ }
3537
+ if (seen.size !== expectedLength) throw new TypeError("Projection order must contain every key exactly once.");
3538
+ nextIds = require_transform.sameArray(ids, explicitOrder) ? ids : explicitOrder;
3539
+ } else if (addedEntries.length || removedEntries.length) {
3540
+ const order = new Array(published.size() + addedEntries.length - removedEntries.length);
3541
+ let index = 0;
3542
+ for (const key of ids) if (hasNext(key)) order[index++] = key;
3543
+ for (const entry of addedEntries) order[index++] = entry.key;
3544
+ nextIds = require_transform.sameArray(ids, order) ? ids : Object.freeze(order);
3497
3545
  } else nextIds = ids;
3498
3546
  require_transform.profile.projection("changedKeys", addedEntries.length + updatedEntries.length + removedEntries.length);
3499
3547
  change = nextReset ? Object.freeze({ kind: "reset" }) : require_transform.createCollectionChange({
@@ -3810,18 +3858,19 @@ const createProcessor = (scheduler, definition, dependencies) => {
3810
3858
  };
3811
3859
  //#endregion
3812
3860
  //#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 {
3861
+ /** Exhaustively notifies an already-stable projection listener snapshot. */
3862
+ const notifyProjectionListenerSnapshot = (listeners, message) => {
3863
+ let failures;
3864
+ for (const listener of listeners) try {
3818
3865
  listener();
3819
3866
  } catch (error) {
3820
- failures.push(error);
3867
+ (failures ??= []).push(error);
3821
3868
  }
3822
- if (failures.length === 1) throw failures[0];
3823
- if (failures.length) throw new AggregateError(failures, message);
3869
+ if (failures?.length === 1) throw failures[0];
3870
+ if (failures?.length) throw new AggregateError(failures, message);
3824
3871
  };
3872
+ /** Snapshots a mutable listener collection before exhaustive notification. */
3873
+ const notifyProjectionListeners = (listeners, message) => notifyProjectionListenerSnapshot([...listeners], message);
3825
3874
  //#endregion
3826
3875
  //#region core/src/projection/readable/selection.ts
3827
3876
  let activeTracker;
@@ -3876,8 +3925,7 @@ const selectionAffects = (selection, change) => {
3876
3925
  if (!change || change.kind === "reset") return true;
3877
3926
  if (selection.all) return true;
3878
3927
  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;
3928
+ return require_transform.collectionChangeIntersects(change, selection.keys);
3881
3929
  };
3882
3930
  const createDirectReadable = (source) => Object.freeze({
3883
3931
  current: source.current,
@@ -3919,7 +3967,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
3919
3967
  activeTracker = previousTracker;
3920
3968
  }
3921
3969
  const nextSelection = Object.freeze({
3922
- keys: new Set(tracker.keys),
3970
+ keys: tracker.keys,
3923
3971
  all: tracker.all || isMapLike(current) && tracker.keys.size === 0 && !tracker.structure,
3924
3972
  structure: tracker.structure
3925
3973
  });
@@ -4161,10 +4209,11 @@ const clearDocumentDirty = (dirty) => {
4161
4209
  dirty.treeNodes.clear();
4162
4210
  dirty.children.clear();
4163
4211
  };
4164
- const locate = (dirty, path) => {
4212
+ const locateRange = (dirty, path, start, tail) => {
4165
4213
  let current = dirty;
4166
- for (const key of path) {
4214
+ for (let index = start; index < path.length; index++) {
4167
4215
  if (current.replace) return void 0;
4216
+ const key = path[index];
4168
4217
  let child = current.children.get(key);
4169
4218
  if (!child) {
4170
4219
  child = createDocumentDirty();
@@ -4172,10 +4221,17 @@ const locate = (dirty, path) => {
4172
4221
  }
4173
4222
  current = child;
4174
4223
  }
4175
- return current;
4224
+ if (tail === void 0) return current;
4225
+ if (current.replace) return void 0;
4226
+ let child = current.children.get(tail);
4227
+ if (!child) {
4228
+ child = createDocumentDirty();
4229
+ current.children.set(tail, child);
4230
+ }
4231
+ return child;
4176
4232
  };
4177
- const markDocumentReplace = (dirty, path) => {
4178
- const current = locate(dirty, path);
4233
+ const replaceAt = (dirty, path, start, tail) => {
4234
+ const current = locateRange(dirty, path, start, tail);
4179
4235
  if (!current) return;
4180
4236
  current.replace = true;
4181
4237
  current.order = false;
@@ -4183,15 +4239,18 @@ const markDocumentReplace = (dirty, path) => {
4183
4239
  current.treeNodes.clear();
4184
4240
  current.children.clear();
4185
4241
  };
4186
- const markDocumentOrder = (dirty, path) => {
4187
- const current = locate(dirty, path);
4242
+ const markDocumentReplace = (dirty, path) => {
4243
+ replaceAt(dirty, path, 0);
4244
+ };
4245
+ const markDocumentOrderRange = (dirty, path, start) => {
4246
+ const current = locateRange(dirty, path, start);
4188
4247
  if (current && !current.replace) current.order = true;
4189
4248
  };
4190
- const markDocumentTree = (dirty, path, root, nodes) => {
4191
- const current = locate(dirty, path);
4249
+ const markDocumentTree = (dirty, path, start, root, nodes) => {
4250
+ const current = locateRange(dirty, path, start);
4192
4251
  if (!current || current.replace) return;
4193
4252
  current.treeRoot ||= root;
4194
- for (const id of nodes) current.treeNodes.add(id);
4253
+ for (const node of nodes) current.treeNodes.add(node.id);
4195
4254
  };
4196
4255
  const createCollectionPending = () => ({
4197
4256
  reset: false,
@@ -4221,19 +4280,19 @@ const mergeValueChanges = (selector, dirty, changes) => {
4221
4280
  const at = selector.address;
4222
4281
  for (const change of changes.changes) {
4223
4282
  if (change.kind === "reset") {
4224
- markDocumentReplace(dirty, []);
4283
+ replaceAt(dirty, at, at.length);
4225
4284
  return;
4226
4285
  }
4227
4286
  if (change.kind === "members") {
4228
4287
  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));
4288
+ const compared = compareExtended(change.at, member.key, at);
4289
+ if (compared === "ancestor" || compared === "equal") replaceAt(dirty, at, at.length);
4290
+ else if (compared === "descendant") replaceAt(dirty, change.at, at.length, member.key);
4232
4291
  }
4233
- if (change.order && contains(at, change.at)) markDocumentOrder(dirty, change.at.slice(at.length));
4292
+ if (change.order && contains(at, change.at)) markDocumentOrderRange(dirty, change.at, at.length);
4234
4293
  continue;
4235
4294
  }
4236
- if (contains(at, change.at)) markDocumentTree(dirty, change.at.slice(at.length), change.before !== change.after, change.nodes.map((node) => node.id));
4295
+ if (contains(at, change.at)) markDocumentTree(dirty, change.at, at.length, change.before !== change.after, change.nodes);
4237
4296
  }
4238
4297
  };
4239
4298
  const mergeCollectionChanges = (selector, pending, changes) => {
@@ -4245,13 +4304,15 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4245
4304
  return;
4246
4305
  }
4247
4306
  if (selector.tree?.kind === "nodes") {
4248
- if (change.kind === "members") {
4249
- for (const member of change.members) if (contains([...change.at, member.key], at)) {
4307
+ if (change.kind === "members") for (const member of change.members) {
4308
+ const compared = compareExtended(change.at, member.key, at);
4309
+ if (compared === "ancestor" || compared === "equal") {
4250
4310
  resetCollectionPending(pending);
4251
4311
  return;
4252
4312
  }
4253
- } else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
4254
- markDocumentReplace(dirtyEntry(pending, node.id), []);
4313
+ }
4314
+ else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
4315
+ replaceAt(dirtyEntry(pending, node.id), at, at.length);
4255
4316
  pending.order ||= node.kind !== "updated";
4256
4317
  }
4257
4318
  else if (contains(change.at, at)) {
@@ -4262,34 +4323,27 @@ const mergeCollectionChanges = (selector, pending, changes) => {
4262
4323
  }
4263
4324
  if (change.kind === "members") {
4264
4325
  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) {
4326
+ const compared = compareExtended(change.at, member.key, at);
4327
+ if (compared === "ancestor" || compared === "equal") {
4273
4328
  resetCollectionPending(pending);
4274
4329
  return;
4275
4330
  }
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));
4331
+ if (compared !== "descendant") continue;
4332
+ if (at.length === change.at.length) {
4333
+ replaceAt(dirtyEntry(pending, member.key), change.at, change.at.length);
4334
+ if (member.kind !== "updated") pending.order = true;
4335
+ } else replaceAt(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1, member.key);
4283
4336
  }
4337
+ if (change.order && contains(at, change.at)) if (change.at.length === at.length) pending.order = true;
4338
+ else markDocumentOrderRange(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1);
4284
4339
  continue;
4285
4340
  }
4286
4341
  if (!contains(at, change.at)) continue;
4287
- const relative = change.at.slice(at.length);
4288
- if (!relative.length) {
4342
+ if (change.at.length === at.length) {
4289
4343
  resetCollectionPending(pending);
4290
4344
  return;
4291
4345
  }
4292
- markDocumentTree(dirtyEntry(pending, relative[0]), relative.slice(1), change.before !== change.after, change.nodes.map((node) => node.id));
4346
+ markDocumentTree(dirtyEntry(pending, change.at[at.length]), change.at, at.length + 1, change.before !== change.after, change.nodes);
4293
4347
  }
4294
4348
  };
4295
4349
  //#endregion
@@ -4418,16 +4472,29 @@ const stageCollectionRead = (state, read, active, options) => {
4418
4472
  evaluation.output.order(ids);
4419
4473
  } else {
4420
4474
  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)) {
4475
+ let currentIds;
4476
+ const ids = () => currentIds ??= read.ids();
4477
+ if (options.candidates) for (const key of options.candidates) if (read.has(key)) {
4424
4478
  require_transform.profile.collectionView.mapped();
4425
4479
  evaluation.output.set(key, read.get(key));
4426
4480
  } else evaluation.output.remove(key);
4481
+ else {
4482
+ const previousIds = previous.ids();
4483
+ const nextIds = ids();
4484
+ require_transform.profile.collectionView.idsScanned(previousIds.length + nextIds.length);
4485
+ for (const key of previousIds) if (read.has(key)) {
4486
+ require_transform.profile.collectionView.mapped();
4487
+ evaluation.output.set(key, read.get(key));
4488
+ } else evaluation.output.remove(key);
4489
+ for (const key of nextIds) if (!previous.has(key)) {
4490
+ require_transform.profile.collectionView.mapped();
4491
+ evaluation.output.set(key, read.get(key));
4492
+ }
4493
+ }
4427
4494
  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);
4495
+ const nextIds = ids();
4496
+ if (options.candidates) require_transform.profile.collectionView.idsScanned(nextIds.length);
4497
+ evaluation.output.order(nextIds);
4431
4498
  }
4432
4499
  }
4433
4500
  return state.seal(options.reset, options.isEqual ?? Object.is);
@@ -4721,13 +4788,15 @@ const createDocumentSourceRegistry = (scheduler) => {
4721
4788
  const snapshots = /* @__PURE__ */ new Map();
4722
4789
  const replacement = createDocumentDirty();
4723
4790
  if (pending.reset) markDocumentReplace(replacement, []);
4791
+ let cachedIds;
4724
4792
  const ids = () => {
4725
4793
  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([]);
4794
+ if (cachedIds) return cachedIds;
4795
+ if (treeNodes) return cachedIds = node?.kind === "tree" && require_record.isRecord(value) && require_record.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
4796
+ if (node?.kind === "map" && require_record.isRecord(value)) return cachedIds = Object.freeze(Object.keys(value));
4797
+ if (node?.kind === "table" && require_record.isRecord(value) && Array.isArray(value.ids)) return cachedIds = Object.freeze([...value.ids]);
4798
+ if (node?.kind === "list" && Array.isArray(value)) return cachedIds = Object.freeze(toArray(indexedKeys(value, node.keyOf)));
4799
+ return cachedIds = Object.freeze([]);
4731
4800
  };
4732
4801
  const has = (key) => {
4733
4802
  require_transform.assertScope(active);
@@ -5215,7 +5284,6 @@ const createSourceRegistry = (scheduler) => {
5215
5284
  };
5216
5285
  //#endregion
5217
5286
  //#region core/src/projection/readable/keyed.ts
5218
- const sameKeys = (previous, next) => previous.length === next.length && previous.every((key, index) => key === next[index]);
5219
5287
  /** Runtime-owned keyed consumer family over one materialized collection output. */
5220
5288
  const createProjectionItems = (source, checkOwner) => {
5221
5289
  if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
@@ -5235,7 +5303,11 @@ const createProjectionItems = (source, checkOwner) => {
5235
5303
  for (const listener of listeners) notifications.push(listener);
5236
5304
  };
5237
5305
  const flushNotifications = () => {
5238
- notifyProjectionListeners(notifications.splice(0), "Projection item listeners failed.");
5306
+ try {
5307
+ notifyProjectionListenerSnapshot(notifications, "Projection item listeners failed.");
5308
+ } finally {
5309
+ notifications.length = 0;
5310
+ }
5239
5311
  };
5240
5312
  const unregisterWaiting = (state) => {
5241
5313
  const states = waiting.get(state.key);
@@ -5278,16 +5350,11 @@ const createProjectionItems = (source, checkOwner) => {
5278
5350
  publishState(state, void 0);
5279
5351
  state.ended = true;
5280
5352
  };
5281
- const statesFor = (key) => {
5282
- const result = [];
5353
+ const terminateKey = (key) => {
5283
5354
  const canonical = active.get(key);
5284
- if (canonical) result.push(canonical);
5355
+ if (canonical) terminateState(canonical);
5285
5356
  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);
5357
+ if (extra) for (const state of extra) terminateState(state);
5291
5358
  active.delete(key);
5292
5359
  followers.delete(key);
5293
5360
  };
@@ -5298,12 +5365,14 @@ const createProjectionItems = (source, checkOwner) => {
5298
5365
  for (const state of states) attachActive(state, value);
5299
5366
  };
5300
5367
  const updateKey = (key, value) => {
5301
- for (const state of statesFor(key)) publishState(state, value);
5368
+ const canonical = active.get(key);
5369
+ if (canonical) publishState(canonical, value);
5370
+ const extra = followers.get(key);
5371
+ if (extra) for (const state of extra) publishState(state, value);
5302
5372
  };
5303
5373
  const updateKeys = (current) => {
5304
- const next = Object.freeze([...current.keys()]);
5305
- if (sameKeys(keysValue, next)) return;
5306
- keysValue = next;
5374
+ if (require_transform.iterableMatchesArray(current.keys(), keysValue)) return;
5375
+ keysValue = Object.freeze([...current.keys()]);
5307
5376
  keysRevision++;
5308
5377
  notify(keyListeners);
5309
5378
  };
@@ -5346,9 +5415,9 @@ const createProjectionItems = (source, checkOwner) => {
5346
5415
  return state;
5347
5416
  };
5348
5417
  const reconcile = (current) => {
5349
- for (const key of [...active.keys()]) if (!current.has(key)) terminateKey(key);
5418
+ for (const key of active.keys()) if (!current.has(key)) terminateKey(key);
5350
5419
  else updateKey(key, current.get(key));
5351
- for (const key of [...waiting.keys()]) if (current.has(key)) activateWaiting(key, current.get(key));
5420
+ for (const key of waiting.keys()) if (current.has(key)) activateWaiting(key, current.get(key));
5352
5421
  updateKeys(current);
5353
5422
  };
5354
5423
  const onChange = (change) => {