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 +33 -0
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +1 -1
- package/dist/{dependency-B1qIJJBL.d.cts → dependency-Cbf3C8eJ.d.cts} +2 -2
- package/dist/{dependency-BRgPvyN6.d.ts → dependency-DzxXGYe1.d.ts} +2 -2
- package/dist/index.cjs +273 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -12
- package/dist/index.d.ts +23 -12
- package/dist/index.js +273 -121
- package/dist/index.js.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js.map +1 -1
- package/dist/{transform-gOtqcdHe.js → transform-A9Lx5R2l.js} +215 -174
- package/dist/transform-A9Lx5R2l.js.map +1 -0
- package/dist/{transform-CENhTaVN.cjs → transform-CSPGdBe5.cjs} +220 -173
- package/dist/transform-CSPGdBe5.cjs.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +4 -2
- package/skills/doxum-runtime/references/integrations.md +1 -1
- package/skills/doxum-runtime/references/projections.md +59 -1
- package/skills/doxum-runtime/references/public-api.md +18 -4
- package/skills/doxum-runtime/references/recipes.md +32 -0
- package/dist/transform-CENhTaVN.cjs.map +0 -1
- package/dist/transform-gOtqcdHe.js.map +0 -1
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-
|
|
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" });
|
package/dist/advanced.d.cts
CHANGED
|
@@ -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-
|
|
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: {
|
package/dist/advanced.d.ts
CHANGED
|
@@ -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-
|
|
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 {
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
3445
|
-
|
|
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
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
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
|
-
|
|
3476
|
-
|
|
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/
|
|
3627
|
-
const
|
|
3628
|
-
const values =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
5511
|
+
input: {
|
|
5408
5512
|
kind: "value",
|
|
5513
|
+
read: () => latest,
|
|
5409
5514
|
set(value) {
|
|
5410
|
-
scheduler.
|
|
5411
|
-
|
|
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
|
-
|
|
5419
|
-
|
|
5420
|
-
const
|
|
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
|
-
|
|
5547
|
+
input: {
|
|
5424
5548
|
kind: "collection",
|
|
5549
|
+
read: () => view ??= require_transform.collectionView(values.read(check)),
|
|
5425
5550
|
update(run) {
|
|
5426
|
-
scheduler.
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
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
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
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
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
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
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
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.
|
|
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
|
|
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
|
|
5883
|
-
if (!
|
|
5884
|
-
return
|
|
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 =
|
|
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(
|
|
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,
|