doxum 0.1.32 → 0.1.33

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/README.md CHANGED
@@ -280,6 +280,29 @@ exposes `read`, `select`, `items`, `update`, `batch`, `scope` and `dispose`.
280
280
  `ProjectionScope` mirrors `read`, `select`, `items`, `update`, `batch` and `dispose`, plus
281
281
  `own(definitionOrTree)` for lifecycle ownership of lazy projection definitions.
282
282
 
283
+ ### Commands inside a batch
284
+
285
+ Ordinary reads use `runtime.read`. A batch can read the latest accepted input state through its callback while projection readers keep the last published view:
286
+
287
+ ```ts
288
+ const count = input(0);
289
+ const doubled = derive({ count }, ({ count }) => count * 2);
290
+
291
+ function increment() {
292
+ runtime.batch(read => {
293
+ runtime.update(count, read(count) + 1);
294
+ });
295
+ }
296
+
297
+ runtime.batch(() => {
298
+ increment();
299
+ increment();
300
+ });
301
+ runtime.read(doubled); // 4
302
+ ```
303
+
304
+ The borrowed reader supports scalar and collection inputs and expires with its callback. Nested batches settle at the outer boundary. Cross-input commands can read several latest source values without mirrors. Input equality is checked before acceptance; a failed edit leaves earlier successes intact. A batch is not a transaction. See [batch source reads](docs/projections.md#batch-source-reads) for snapshots, errors and lifecycle.
305
+
283
306
  ### Keyed projection
284
307
 
285
308
  `derive.keyed` preserves a keyed driver's membership and order while deriving entries
@@ -400,6 +423,16 @@ key later creates a new generation. A subscribed missing item stays latent and a
400
423
  if the key is later added. Runtime batches follow the net published membership, so a
401
424
  remove/add collapsed to an update does not create a false lifecycle break.
402
425
 
426
+ ### One-to-many keyed derivation
427
+
428
+ ```ts
429
+ const lines = derive.keyed.flatMap(orders, order =>
430
+ order.lines.map(line => [line.id, line] as const)
431
+ );
432
+ ```
433
+
434
+ Each parent returns ordered `[globalKey, value]` tuples, including zero entries. `flatMap` reuses named/dynamic keyed dependencies and only recomputes invalidated parents. Formal output order follows parent order then child order; parent-only reordering runs no selectors. Duplicate final output keys are errors, and present `undefined` is valid. Same-batch transfers preserve a global key's membership identity. Structural changes can rebuild full order; this is not a claim that every operation costs only the changed entries.
435
+
403
436
  ### Runtime-local keyed state
404
437
 
405
438
  Use `input.collection` for selection, expanded state, local overrides and other
package/dist/advanced.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_transform = require("./transform-CENhTaVN.cjs");
2
+ const require_transform = require("./transform-CSPGdBe5.cjs");
3
3
  const require_record = require("./record-ChYUi8PP.cjs");
4
4
  //#region core/src/projection/advanced.ts
5
5
  const resetCollectionChange = Object.freeze({ kind: "reset" });
@@ -1,4 +1,4 @@
1
- import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-B1qIJJBL.cjs";
1
+ import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-Cbf3C8eJ.cjs";
2
2
 
3
3
  //#region core/src/projection/collection/change.d.ts
4
4
  declare const collectionChange: {
@@ -1,4 +1,4 @@
1
- import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-BRgPvyN6.js";
1
+ import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-DzxXGYe1.js";
2
2
 
3
3
  //#region core/src/projection/collection/change.d.ts
4
4
  declare const collectionChange: {
package/dist/advanced.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D as defineProcessor, S as compileProjectionDependencies, l as assertSynchronous, p as collectionChange, t as createKeyedTransform, w as collectionView } from "./transform-gOtqcdHe.js";
1
+ import { C as compileProjectionDependencies, O as defineProcessor, T as collectionView, r as collectionChange, t as createKeyedTransform, v as assertSynchronous } from "./transform-A9Lx5R2l.js";
2
2
  import { n as isPlainObject } from "./record-B2yRhYsc.js";
3
3
  //#region core/src/projection/advanced.ts
4
4
  const resetCollectionChange = Object.freeze({ kind: "reset" });
@@ -134,7 +134,7 @@ type SameKeyConstraint<DriverKey extends string, Dependency> = Dependency extend
134
134
  readonly key: unknown;
135
135
  } | {
136
136
  readonly keys: unknown;
137
- } ? unknown : DriverKey extends SourceKey ? unknown : never : unknown;
137
+ } ? unknown : [DriverKey] extends [SourceKey] ? unknown : never : unknown;
138
138
  type KeyedDependencies<DriverKey extends string, DriverValue, D extends object> = { readonly [P in keyof D]: P extends string ? KeyedDependency<DriverKey, DriverValue> & SameKeyConstraint<DriverKey, D[P]> : never };
139
139
  type KeyedDependencyValues<D extends object> = { readonly [P in keyof D]: D[P] extends {
140
140
  readonly source: KeyedProjection<infer K extends string, infer V>;
@@ -144,4 +144,4 @@ type KeyedDependencyValues<D extends object> = { readonly [P in keyof D]: D[P] e
144
144
  } ? V | undefined : D[P] extends Projection<infer T> ? T : never };
145
145
  //#endregion
146
146
  export { ProjectionDisposedError as _, Input as a, ProjectionChange as c, CollectionRead as d, ExternalCollectionEvent as f, ExternalValueSource as g, ExternalValueEvent as h, CollectionInputDraft as i, CollectionChange as l, ExternalCollectionSource as m, KeyedDependencyValues as n, KeyedProjection as o, ExternalCollectionRead as p, CollectionInput as r, Projection as s, KeyedDependencies as t, CollectionDraft as u, ProjectionError as v };
147
- //# sourceMappingURL=dependency-B1qIJJBL.d.cts.map
147
+ //# sourceMappingURL=dependency-Cbf3C8eJ.d.cts.map
@@ -134,7 +134,7 @@ type SameKeyConstraint<DriverKey extends string, Dependency> = Dependency extend
134
134
  readonly key: unknown;
135
135
  } | {
136
136
  readonly keys: unknown;
137
- } ? unknown : DriverKey extends SourceKey ? unknown : never : unknown;
137
+ } ? unknown : [DriverKey] extends [SourceKey] ? unknown : never : unknown;
138
138
  type KeyedDependencies<DriverKey extends string, DriverValue, D extends object> = { readonly [P in keyof D]: P extends string ? KeyedDependency<DriverKey, DriverValue> & SameKeyConstraint<DriverKey, D[P]> : never };
139
139
  type KeyedDependencyValues<D extends object> = { readonly [P in keyof D]: D[P] extends {
140
140
  readonly source: KeyedProjection<infer K extends string, infer V>;
@@ -144,4 +144,4 @@ type KeyedDependencyValues<D extends object> = { readonly [P in keyof D]: D[P] e
144
144
  } ? V | undefined : D[P] extends Projection<infer T> ? T : never };
145
145
  //#endregion
146
146
  export { ProjectionDisposedError as _, Input as a, ProjectionChange as c, CollectionRead as d, ExternalCollectionEvent as f, ExternalValueSource as g, ExternalValueEvent as h, CollectionInputDraft as i, CollectionChange as l, ExternalCollectionSource as m, KeyedDependencyValues as n, KeyedProjection as o, ExternalCollectionRead as p, CollectionInput as r, Projection as s, KeyedDependencies as t, CollectionDraft as u, ProjectionError as v };
147
- //# sourceMappingURL=dependency-BRgPvyN6.d.ts.map
147
+ //# sourceMappingURL=dependency-DzxXGYe1.d.ts.map
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_transform = require("./transform-CENhTaVN.cjs");
2
+ const require_transform = require("./transform-CSPGdBe5.cjs");
3
3
  const require_record = require("./record-ChYUi8PP.cjs");
4
4
  const require_driver = require("./driver-CN3dGxQI.cjs");
5
5
  //#region core/src/schema/model.ts
@@ -2775,6 +2775,108 @@ const isExternalSource = (source) => {
2775
2775
  return (candidate.kind === "value" || candidate.kind === "collection") && typeof candidate.current === "function" && typeof candidate.revision === "function" && typeof candidate.subscribe === "function";
2776
2776
  };
2777
2777
  //#endregion
2778
+ //#region core/src/projection/keyed/flat-map.ts
2779
+ function createKeyedFlatMap(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
2780
+ const name = "derive.keyed.flatMap";
2781
+ require_transform.assertKeyedProjection(source, `${name} source`);
2782
+ const specs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
2783
+ const select = specs === void 0 ? dependenciesOrSelect : selectOrEquality;
2784
+ const equality = specs === void 0 ? selectOrEquality : maybeEquality;
2785
+ if (typeof select !== "function") throw new TypeError(`${name} requires a selector.`);
2786
+ if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${name} equality must be a function.`);
2787
+ const compiled = require_transform.compileKeyedDependencies(source, specs, name);
2788
+ const [projection] = require_transform.defineProcessor({
2789
+ dependencies: compiled.projections,
2790
+ outputs: [{
2791
+ kind: "collection",
2792
+ equality: equality ?? Object.is
2793
+ }],
2794
+ name,
2795
+ create: () => {
2796
+ const dependencies = require_transform.createKeyedDependencyRuntime(compiled);
2797
+ const childrenByParent = /* @__PURE__ */ new Map();
2798
+ const ownerByOutput = /* @__PURE__ */ new Map();
2799
+ return {
2800
+ evaluate: (evaluation) => {
2801
+ const driver = evaluation.sources[0];
2802
+ const output = evaluation.outputs[0];
2803
+ if (driver.kind !== "collection" || output.kind !== "collection") throw new Error(`${name} requires collection input and output.`);
2804
+ const plan = require_transform.collectKeyedInvalidation(driver, evaluation.sources, dependencies, evaluation.reset);
2805
+ dependencies.prepare(evaluation.sources, plan.rebuild);
2806
+ if (plan.rebuild) {
2807
+ dependencies.clearBindings();
2808
+ childrenByParent.clear();
2809
+ ownerByOutput.clear();
2810
+ }
2811
+ const replaced = new Set(plan.keys);
2812
+ for (const entry of plan.removed) replaced.add(entry.key);
2813
+ const proposals = /* @__PURE__ */ new Map();
2814
+ const segments = /* @__PURE__ */ new Map();
2815
+ let orderDirty = plan.structural;
2816
+ for (const parent of replaced) {
2817
+ if (!driver.read.has(parent)) continue;
2818
+ const value = driver.read.get(parent);
2819
+ const entries = select(value, parent, dependencies.resolve(value, parent, evaluation.sources));
2820
+ require_transform.assertSynchronous(entries);
2821
+ if (!Array.isArray(entries)) throw new TypeError(`${name} selector must return an array of entries.`);
2822
+ const keys = [];
2823
+ for (const entry of entries) {
2824
+ if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") throw new TypeError(`${name} entries must be [string key, value] tuples.`);
2825
+ const [key, child] = entry;
2826
+ const owner = ownerByOutput.get(key);
2827
+ if (proposals.has(key) || owner !== void 0 && !replaced.has(owner)) throw new TypeError(`${name} duplicate key ${JSON.stringify(key)}.`);
2828
+ proposals.set(key, {
2829
+ parent,
2830
+ value: child
2831
+ });
2832
+ keys.push(key);
2833
+ }
2834
+ const previous = childrenByParent.get(parent);
2835
+ const unchanged = previous ? require_transform.sameArray(previous, keys) : keys.length === 0;
2836
+ if (!unchanged) orderDirty = true;
2837
+ if (keys.length) segments.set(parent, unchanged && previous ? previous : Object.freeze(keys));
2838
+ }
2839
+ if (plan.rebuild) {
2840
+ if (!evaluation.reset) {
2841
+ for (const key of output.previous.ids()) if (!proposals.has(key)) output.output.remove(key);
2842
+ }
2843
+ } else for (const parent of replaced) {
2844
+ const previous = childrenByParent.get(parent);
2845
+ if (previous) for (const key of previous) {
2846
+ if (!proposals.has(key)) output.output.remove(key);
2847
+ ownerByOutput.delete(key);
2848
+ }
2849
+ childrenByParent.delete(parent);
2850
+ }
2851
+ for (const entry of plan.removed) dependencies.remove(entry.key);
2852
+ for (const [parent, keys] of segments) childrenByParent.set(parent, keys);
2853
+ for (const [key, entry] of proposals) {
2854
+ ownerByOutput.set(key, entry.parent);
2855
+ output.output.set(key, entry.value);
2856
+ }
2857
+ if (orderDirty) {
2858
+ const order = [];
2859
+ const parents = driver.read.ids();
2860
+ for (const parent of parents) {
2861
+ const keys = childrenByParent.get(parent);
2862
+ if (keys) for (const key of keys) order.push(key);
2863
+ }
2864
+ require_transform.profile.collectionView.idsScanned(parents.length + order.length);
2865
+ output.output.order(order);
2866
+ }
2867
+ dependencies.remember(evaluation.sources);
2868
+ },
2869
+ release: () => {
2870
+ childrenByParent.clear();
2871
+ ownerByOutput.clear();
2872
+ dependencies.release();
2873
+ }
2874
+ };
2875
+ }
2876
+ });
2877
+ return projection;
2878
+ }
2879
+ //#endregion
2778
2880
  //#region core/src/projection/keyed/composition.ts
2779
2881
  const assertEquality = (equality, role) => {
2780
2882
  if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${role} equality must be a function.`);
@@ -3441,44 +3543,26 @@ function createKeyedGroupBy(source, dependenciesOrSelector, maybeSelector) {
3441
3543
  const driver = evaluation.sources[0];
3442
3544
  const output = evaluation.outputs[0];
3443
3545
  if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.groupBy requires collection input and output.");
3444
- dependencies.prepare(evaluation.sources, evaluation.reset);
3445
- if (evaluation.reset) {
3546
+ const invalidation = require_transform.collectKeyedInvalidation(driver, evaluation.sources, dependencies, evaluation.reset);
3547
+ dependencies.prepare(evaluation.sources, invalidation.rebuild);
3548
+ if (invalidation.rebuild) {
3446
3549
  rebuild(driver, evaluation.sources, output);
3447
3550
  dependencies.remember(evaluation.sources);
3448
3551
  return;
3449
3552
  }
3450
- const dirty = /* @__PURE__ */ new Set();
3451
3553
  const affected = /* @__PURE__ */ new Set();
3452
- let all = false;
3453
- let structural = false;
3454
- let orderDirty = false;
3455
- if (dependencies.driverChanged(driver)) {
3456
- const change = driver.change;
3457
- if (!change || change.kind === "reset") {
3458
- rebuild(driver, evaluation.sources, output);
3459
- dependencies.remember(evaluation.sources);
3460
- return;
3461
- } else {
3462
- for (const entry of change.removed) {
3463
- const changed = removeSourceKey(entry.key, affected);
3464
- orderDirty ||= changed;
3465
- dependencies.remove(entry.key);
3466
- dirty.delete(entry.key);
3467
- }
3468
- for (const entry of change.added) dirty.add(entry.key);
3469
- for (const entry of change.updated) dirty.add(entry.key);
3470
- structural = Boolean(change.added.length || change.removed.length || change.order);
3471
- orderDirty ||= structural;
3472
- if (structural) refreshOrderIndex(driver);
3473
- }
3554
+ let orderDirty = invalidation.structural;
3555
+ for (const entry of invalidation.removed) {
3556
+ const changed = removeSourceKey(entry.key, affected);
3557
+ orderDirty ||= changed;
3558
+ dependencies.remove(entry.key);
3474
3559
  }
3475
- all ||= dependencies.collectInvalidated(evaluation.sources, dirty);
3476
- if (all) for (const key of driver.read.ids()) dirty.add(key);
3477
- for (const key of dirty) {
3560
+ if (invalidation.structural) refreshOrderIndex(driver);
3561
+ for (const key of invalidation.keys) {
3478
3562
  const changed = selectGroups(driver, evaluation.sources, key, affected);
3479
3563
  orderDirty ||= changed;
3480
3564
  }
3481
- if (structural) markAllGroups(output, affected);
3565
+ if (invalidation.structural) markAllGroups(output, affected);
3482
3566
  publishAffected(affected, output, orderDirty);
3483
3567
  dependencies.remember(evaluation.sources);
3484
3568
  },
@@ -3502,6 +3586,7 @@ const keyedDerive = Object.assign(createKeyedDerive, {
3502
3586
  from: createKeyedFrom,
3503
3587
  merge: createKeyedMerge,
3504
3588
  groupBy: createKeyedGroupBy,
3589
+ flatMap: createKeyedFlatMap,
3505
3590
  singleton: createKeyedSingleton,
3506
3591
  subset: createKeyedSubset,
3507
3592
  filter: createKeyedFilter,
@@ -3623,11 +3708,11 @@ var PersistentKeyedIndex = class PersistentKeyedIndex {
3623
3708
  }
3624
3709
  };
3625
3710
  //#endregion
3626
- //#region core/src/projection/output/collection.ts
3627
- const createPublishedCollection = () => {
3628
- const values = /* @__PURE__ */ new Map();
3629
- let ids = Object.freeze([]);
3630
- let index = PersistentKeyedIndex.empty();
3711
+ //#region core/src/projection/collection/state.ts
3712
+ const createCollectionState = (initial) => {
3713
+ const values = new Map(initial);
3714
+ let ids = Object.freeze([...values.keys()]);
3715
+ let index = values.size ? PersistentKeyedIndex.from(values) : PersistentKeyedIndex.empty();
3631
3716
  return {
3632
3717
  get: (key) => values.get(key),
3633
3718
  has: (key) => values.has(key),
@@ -3652,7 +3737,7 @@ const createPublishedCollection = () => {
3652
3737
  }
3653
3738
  });
3654
3739
  },
3655
- publish: (staged, nextIds, reset) => {
3740
+ install: (staged, nextIds, reset) => {
3656
3741
  if (reset) {
3657
3742
  values.clear();
3658
3743
  for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
@@ -3671,9 +3756,11 @@ const createPublishedCollection = () => {
3671
3756
  }
3672
3757
  };
3673
3758
  };
3759
+ //#endregion
3760
+ //#region core/src/projection/output/collection.ts
3674
3761
  /** Owns staged keyed intent, exact transitions and published collection state. */
3675
3762
  const createCollectionOutput = (binding) => {
3676
- const published = createPublishedCollection();
3763
+ const published = createCollectionState();
3677
3764
  const staged = /* @__PURE__ */ new Map();
3678
3765
  let nextIds = published.ids();
3679
3766
  let change;
@@ -3762,12 +3849,18 @@ const createCollectionOutput = (binding) => {
3762
3849
  continue;
3763
3850
  }
3764
3851
  const before = published.get(key);
3765
- if (!isEqual(before, entry.value)) updatedEntries.push({
3852
+ const equal = isEqual(before, entry.value);
3853
+ require_transform.assertSynchronous(equal);
3854
+ if (!equal) updatedEntries.push({
3766
3855
  key,
3767
3856
  before,
3768
3857
  after: entry.value
3769
3858
  });
3770
- else if (!nextReset) staged.delete(key);
3859
+ else if (nextReset) staged.set(key, {
3860
+ present: true,
3861
+ value: before
3862
+ });
3863
+ else staged.delete(key);
3771
3864
  continue;
3772
3865
  }
3773
3866
  if (existed) removedEntries.push({
@@ -3837,7 +3930,7 @@ const createCollectionOutput = (binding) => {
3837
3930
  revision: () => revision,
3838
3931
  reset: () => reset,
3839
3932
  publish: () => {
3840
- if (change) published.publish(staged, nextIds, change.kind === "reset");
3933
+ if (change) published.install(staged, nextIds, change.kind === "reset");
3841
3934
  if (change && initialized) revision++;
3842
3935
  initialized = true;
3843
3936
  },
@@ -3917,7 +4010,9 @@ const createValueOutput = (binding) => {
3917
4010
  return false;
3918
4011
  }
3919
4012
  const candidate = staged;
3920
- changed = !initialized || !isEqual(value, candidate);
4013
+ const equal = initialized ? isEqual(value, candidate) : false;
4014
+ require_transform.assertSynchronous(equal);
4015
+ changed = !equal;
3921
4016
  next = changed ? candidate : value;
3922
4017
  reset = nextReset;
3923
4018
  return changed;
@@ -5399,101 +5494,145 @@ const createExternalSourceRegistry = (scheduler, documents) => {
5399
5494
  //#region core/src/projection/source/input.ts
5400
5495
  const materializeInputSource = (scheduler, definition) => {
5401
5496
  const source = definition.source;
5497
+ if (source.kind !== "value-input" && source.kind !== "collection-input") throw new Error("Invalid projection input source.");
5498
+ const equal = (previous, next) => {
5499
+ const result = source.equality(previous, next);
5500
+ require_transform.assertSynchronous(result);
5501
+ return result;
5502
+ };
5402
5503
  if (source.kind === "value-input") {
5403
5504
  let latest = source.initial;
5404
- const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
5505
+ const boundary = createValueBoundary(scheduler, "projection input", source.initial, Object.is, () => latest, () => void 0);
5506
+ boundary.detach(() => {
5507
+ latest = void 0;
5508
+ });
5405
5509
  return {
5406
5510
  ...boundary,
5407
- write: {
5511
+ input: {
5408
5512
  kind: "value",
5513
+ read: () => latest,
5409
5514
  set(value) {
5410
- scheduler.assertIdle();
5411
- latest = value;
5515
+ const next = scheduler.acceptInput(() => {
5516
+ if (equal(latest, value)) return latest;
5517
+ if (source.equality === Object.is) return value;
5518
+ const published = boundary.output.current();
5519
+ return !Object.is(latest, published) && equal(published, value) ? published : value;
5520
+ });
5521
+ if (Object.is(latest, next)) return;
5522
+ latest = next;
5412
5523
  boundary.mark();
5413
5524
  scheduler.run();
5414
5525
  }
5415
5526
  }
5416
5527
  };
5417
5528
  }
5418
- if (source.kind !== "collection-input") throw new Error("Invalid projection input source.");
5419
- const values = new Map(source.initial);
5420
- const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_transform.mapRead(values), definition.output.equality, () => void 0);
5529
+ const values = createCollectionState(source.initial);
5530
+ let view;
5531
+ const latest = {
5532
+ get: values.get,
5533
+ has: values.has,
5534
+ ids: values.ids
5535
+ };
5536
+ const boundary = createCollectionBoundary(scheduler, "projection collection input", () => latest, Object.is, () => void 0);
5537
+ boundary.detach(() => {
5538
+ values.release();
5539
+ view = void 0;
5540
+ });
5541
+ const check = () => {
5542
+ scheduler.assertActive();
5543
+ if (boundary.producer.disposed) throw new require_transform.ProjectionDisposedError();
5544
+ };
5421
5545
  return {
5422
5546
  ...boundary,
5423
- write: {
5547
+ input: {
5424
5548
  kind: "collection",
5549
+ read: () => view ??= require_transform.collectionView(values.read(check)),
5425
5550
  update(run) {
5426
- scheduler.assertIdle();
5427
- const staged = /* @__PURE__ */ new Map();
5428
- const operations = [];
5429
- let structural = false;
5430
- let active = true;
5431
- const draft = Object.freeze({
5432
- get: (key) => {
5433
- require_transform.assertScope(() => active);
5434
- const entry = staged.get(key);
5435
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
5436
- },
5437
- has: (key) => {
5438
- require_transform.assertScope(() => active);
5551
+ const accepted = scheduler.acceptInput(() => {
5552
+ const staged = /* @__PURE__ */ new Map();
5553
+ const appended = /* @__PURE__ */ new Set();
5554
+ let structural = false;
5555
+ let active = true;
5556
+ const has = (key) => {
5439
5557
  const entry = staged.get(key);
5440
5558
  return entry ? entry.present : values.has(key);
5441
- },
5442
- set: (key, value) => {
5443
- require_transform.assertScope(() => active);
5444
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5445
- const previous = staged.get(key);
5446
- structural ||= previous ? !previous.present : !values.has(key);
5447
- staged.set(key, {
5448
- present: true,
5449
- value
5450
- });
5451
- operations.push({
5452
- kind: "set",
5453
- key,
5454
- value
5455
- });
5456
- },
5457
- remove: (key) => {
5458
- require_transform.assertScope(() => active);
5459
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5460
- const previous = staged.get(key);
5461
- const present = previous ? previous.present : values.has(key);
5462
- structural ||= present;
5463
- staged.set(key, { present: false });
5464
- if (present) operations.push({
5465
- kind: "remove",
5466
- key
5467
- });
5559
+ };
5560
+ const draft = Object.freeze({
5561
+ get: (key) => {
5562
+ require_transform.assertScope(() => active);
5563
+ const entry = staged.get(key);
5564
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
5565
+ },
5566
+ has: (key) => {
5567
+ require_transform.assertScope(() => active);
5568
+ return has(key);
5569
+ },
5570
+ set: (key, value) => {
5571
+ require_transform.assertScope(() => active);
5572
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5573
+ if (!has(key)) {
5574
+ appended.add(key);
5575
+ structural = true;
5576
+ }
5577
+ staged.set(key, {
5578
+ present: true,
5579
+ value
5580
+ });
5581
+ },
5582
+ remove: (key) => {
5583
+ require_transform.assertScope(() => active);
5584
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
5585
+ if (!has(key)) return;
5586
+ appended.delete(key);
5587
+ structural = true;
5588
+ staged.set(key, { present: false });
5589
+ }
5590
+ });
5591
+ try {
5592
+ require_transform.assertSynchronous(run(draft));
5593
+ } finally {
5594
+ active = false;
5468
5595
  }
5469
- });
5470
- try {
5471
- require_transform.assertSynchronous(run(draft));
5472
- } finally {
5473
- active = false;
5474
- }
5475
- if (!operations.length) return;
5476
- const candidates = /* @__PURE__ */ new Set();
5477
- const retained = /* @__PURE__ */ new Map();
5478
- for (const [key, entry] of staged) {
5479
- const existed = values.has(key);
5480
- if (entry.present) {
5481
- if (!existed) {
5482
- candidates.add(key);
5596
+ const published = source.equality === Object.is ? void 0 : boundary.output.current();
5597
+ for (const [key, entry] of staged) {
5598
+ const existed = values.has(key);
5599
+ if (!entry.present) {
5600
+ if (!existed) staged.delete(key);
5483
5601
  continue;
5484
5602
  }
5485
5603
  const previous = values.get(key);
5486
- if (source.equality(previous, entry.value)) retained.set(key, previous);
5487
- else candidates.add(key);
5488
- } else if (existed) candidates.add(key);
5489
- }
5490
- if (!candidates.size && !structural) return;
5491
- for (const operation of operations) if (operation.kind === "set") values.set(operation.key, operation.value);
5492
- else values.delete(operation.key);
5493
- for (const [key, value] of retained) if (values.has(key)) values.set(key, value);
5604
+ let next = entry.value;
5605
+ if (existed && equal(previous, next)) next = previous;
5606
+ else if (published?.has(key)) {
5607
+ const before = published.get(key);
5608
+ if ((!existed || !Object.is(previous, before)) && equal(before, next)) next = before;
5609
+ }
5610
+ if (existed && Object.is(previous, next)) staged.delete(key);
5611
+ else if (!Object.is(entry.value, next)) staged.set(key, {
5612
+ present: true,
5613
+ value: next
5614
+ });
5615
+ }
5616
+ const beforeOrder = values.ids();
5617
+ let order = beforeOrder;
5618
+ if (structural) {
5619
+ const next = [];
5620
+ for (const key of beforeOrder) if (has(key) && !appended.has(key)) next.push(key);
5621
+ for (const key of appended) next.push(key);
5622
+ if (!require_transform.sameArray(beforeOrder, next)) order = Object.freeze(next);
5623
+ }
5624
+ return {
5625
+ staged,
5626
+ order,
5627
+ structural: order !== beforeOrder
5628
+ };
5629
+ });
5630
+ if (!accepted.staged.size && !accepted.structural) return;
5631
+ values.install(accepted.staged, accepted.order, false);
5632
+ view = void 0;
5494
5633
  boundary.mark({
5495
- candidates,
5496
- orderMayChange: structural
5634
+ candidates: accepted.staged.keys(),
5635
+ orderMayChange: accepted.structural
5497
5636
  });
5498
5637
  scheduler.run();
5499
5638
  }
@@ -5790,7 +5929,7 @@ const createProjectionRuntime = (options) => {
5790
5929
  result = Object.freeze({
5791
5930
  record: source.producer,
5792
5931
  outputs: Object.freeze([source.output]),
5793
- ...source.write ? { write: source.write } : {}
5932
+ ...source.input ? { input: source.input } : {}
5794
5933
  });
5795
5934
  } else {
5796
5935
  if (definition.owner === void 0 && definition.dependencies.some((dependency) => require_transform.producerOf(dependency).owner !== void 0)) throw new TypeError("A root projection cannot depend on a scoped projection.");
@@ -5875,16 +6014,18 @@ const createProjectionRuntime = (options) => {
5875
6014
  function selectProjection(projection, selector, equality) {
5876
6015
  return makeReadable(projection, selector, equality);
5877
6016
  }
5878
- const inputWrite = (target, requester) => {
6017
+ const inputAccess = (target, requester) => {
6018
+ scheduler.assertIdle();
6019
+ if (requester && !requester.active) throw new require_transform.ProjectionDisposedError();
5879
6020
  const ref = require_transform.projectionRef(target);
5880
6021
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
5881
6022
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
5882
- const write = materializeProducer(ref.producer, requester).write;
5883
- if (!write) throw new TypeError("Projection input is not writable.");
5884
- return write;
6023
+ const access = materializeProducer(ref.producer, requester).input;
6024
+ if (!access) throw new TypeError("Projection input is not writable.");
6025
+ return access;
5885
6026
  };
5886
6027
  const updateInput = (target, valueOrRun, requester) => {
5887
- const write = inputWrite(target, requester);
6028
+ const write = inputAccess(target, requester);
5888
6029
  if (write.kind === "value") {
5889
6030
  write.set(valueOrRun);
5890
6031
  return;
@@ -5895,7 +6036,18 @@ const createProjectionRuntime = (options) => {
5895
6036
  function update(target, valueOrRun) {
5896
6037
  updateInput(target, valueOrRun);
5897
6038
  }
5898
- const batch = (run, options) => scheduler.batch(run, options);
6039
+ const batch = (run, options, requester) => scheduler.batch(() => {
6040
+ let active = true;
6041
+ const read = (target) => {
6042
+ require_transform.assertScope(() => active);
6043
+ return inputAccess(target, requester).read();
6044
+ };
6045
+ try {
6046
+ return run(read);
6047
+ } finally {
6048
+ active = false;
6049
+ }
6050
+ }, options);
5899
6051
  const disposeScope = (state) => {
5900
6052
  if (!state.active) return;
5901
6053
  scheduler.assertIdle();
@@ -5987,7 +6139,7 @@ const createProjectionRuntime = (options) => {
5987
6139
  }
5988
6140
  function scopeBatch(run, options) {
5989
6141
  assertActive();
5990
- return batch(run, options);
6142
+ return batch(run, options, state);
5991
6143
  }
5992
6144
  return Object.freeze({
5993
6145
  own,