doxum 0.1.37 → 0.1.39
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.js +1 -1
- package/dist/index.cjs +199 -166
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +199 -166
- package/dist/{transform-D45KWMzj.cjs → transform-BnQZlLRP.cjs} +2 -2
- package/dist/{transform-3mj0eStL.js → transform-Cl7CSsdR.js} +2 -2
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +2 -2
- package/skills/doxum-runtime/references/projections.md +73 -3
- package/skills/doxum-runtime/references/public-api.md +12 -2
- package/skills/doxum-runtime/references/recipes.md +34 -2
package/README.md
CHANGED
|
@@ -322,6 +322,16 @@ const ids = derive.keyed.keys(records);
|
|
|
322
322
|
const all = derive.keyed.values(records);
|
|
323
323
|
const entries = derive.keyed.entries(records);
|
|
324
324
|
const active = derive.keyed.get(records, activeRecordId);
|
|
325
|
+
const selectedRecords = derive.keyed.subset(
|
|
326
|
+
records,
|
|
327
|
+
{ selection },
|
|
328
|
+
({ selection }) => selection.recordIds
|
|
329
|
+
);
|
|
330
|
+
const selectedRecord = derive.keyed.get(
|
|
331
|
+
records,
|
|
332
|
+
{ selection },
|
|
333
|
+
({ selection }) => selection.activeRecordId
|
|
334
|
+
);
|
|
325
335
|
const rowsFromArray = derive.keyed.from(rowArray, row => row.id);
|
|
326
336
|
const effectiveRows = derive.keyed.merge([baseRows, rowOverrides], { conflict: 'last' });
|
|
327
337
|
const visible = derive.keyed.filter(records, record => record.visible);
|
|
@@ -342,6 +352,15 @@ source-relative order. `compact` omits keys whose selected value is `undefined`
|
|
|
342
352
|
uses its optional equality for present values. `filter` and `compact` support the same
|
|
343
353
|
named global/dynamic keyed dependency object as ordinary `derive.keyed`.
|
|
344
354
|
|
|
355
|
+
`get` accepts a static key, a key projection, or `(source, dependencies, selectKey, equality?)`.
|
|
356
|
+
`subset` accepts a static ordered key array, a key-array projection, or
|
|
357
|
+
`(source, dependencies, selectOrderedKeys)`. Named selectors run when their declared
|
|
358
|
+
dependencies change; source-only updates use the selected keys to filter deltas.
|
|
359
|
+
Missing requested keys remain live for later adds. Subset preserves source value
|
|
360
|
+
references and requested order, and has no value equality option. Unrelated source
|
|
361
|
+
updates may wake the processor to inspect deltas, but do not rerun selectors or rebuild
|
|
362
|
+
the subset. See [Keyed selection](docs/projections.md#keyed-selection).
|
|
363
|
+
|
|
345
364
|
`from` turns either a static readonly array or `Projection<readonly V[]>` into a keyed
|
|
346
365
|
projection using `keyOf(value)`. Input order becomes formal keyed order; duplicate keys
|
|
347
366
|
are errors rather than implicit overwrites. Static outer arrays are snapshotted at
|
|
@@ -362,6 +381,20 @@ and group keys are ordered by the earliest current source member that belongs to
|
|
|
362
381
|
If several groups first appear on the same source member, they follow that member's
|
|
363
382
|
selector-result order. Source membership/order changes may therefore reorder group keys.
|
|
364
383
|
`singleton` converts an optional scalar projection into a zero-or-one keyed projection.
|
|
384
|
+
It also computes an optional member directly from named dependencies:
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
const preview = derive.keyed.singleton(
|
|
388
|
+
{ record: activeRecord, enabled: previewEnabled },
|
|
389
|
+
({ record, enabled }) => (!enabled || record === undefined ? undefined : [record.id, record])
|
|
390
|
+
);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The named form returns one readonly `[key, value]` tuple or `undefined` for no member.
|
|
394
|
+
`[key, undefined]` is a present member. Optional equality compares values under the
|
|
395
|
+
same key and preserves equivalent references; it cannot suppress a key change.
|
|
396
|
+
Use `singleton` for zero or one result, and `fromEntries` for a complete result with
|
|
397
|
+
arbitrary membership. See [Singleton](docs/projections.md#zero-or-one-keyed-member).
|
|
365
398
|
|
|
366
399
|
All keyed projection producers use the public `KeyedProjection<K,V>` handle, including
|
|
367
400
|
collection `observe`, `derive.keyed`, `incremental.collection`, and collection leaves
|
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-BnQZlLRP.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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as defineProcessor, S as compileProjectionDependencies, _ as assertSynchronous, r as collectionChange, t as createKeyedTransform, w as collectionView } from "./transform-
|
|
1
|
+
import { D as defineProcessor, S as compileProjectionDependencies, _ as assertSynchronous, r as collectionChange, t as createKeyedTransform, w as collectionView } from "./transform-Cl7CSsdR.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" });
|
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-BnQZlLRP.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
|
|
@@ -2883,6 +2883,163 @@ function createKeyedFlatMap(source, dependenciesOrSelect, selectOrEquality, mayb
|
|
|
2883
2883
|
return projection;
|
|
2884
2884
|
}
|
|
2885
2885
|
//#endregion
|
|
2886
|
+
//#region core/src/projection/keyed/selection.ts
|
|
2887
|
+
/** Compiles selection inputs; runtime instances retain only the request and its dependency revisions.
|
|
2888
|
+
* Source slot zero belongs to the collection being queried, not to the selection expression.
|
|
2889
|
+
*/
|
|
2890
|
+
const compileSelection = (input, named, select, normalize, name) => {
|
|
2891
|
+
let projections;
|
|
2892
|
+
let compute;
|
|
2893
|
+
let initial;
|
|
2894
|
+
if (named) {
|
|
2895
|
+
const compiled = require_transform.compileProjectionDependencies(input, name);
|
|
2896
|
+
if (typeof select !== "function") throw new TypeError(`${name} requires a selection callback.`);
|
|
2897
|
+
projections = compiled.projections;
|
|
2898
|
+
const selectValues = select;
|
|
2899
|
+
compute = (sources) => selectValues(require_transform.snapshotProjectionValues(compiled.names, sources, 1));
|
|
2900
|
+
} else if (require_transform.isProjection(input)) {
|
|
2901
|
+
if (require_transform.outputKind(input) !== "value") throw new TypeError(`${name} selection must be a scalar projection.`);
|
|
2902
|
+
projections = [input];
|
|
2903
|
+
compute = (sources) => {
|
|
2904
|
+
const source = sources[1];
|
|
2905
|
+
if (source?.kind !== "value") throw new Error(`${name} resolved a non-scalar selection.`);
|
|
2906
|
+
return source.value;
|
|
2907
|
+
};
|
|
2908
|
+
} else {
|
|
2909
|
+
projections = [];
|
|
2910
|
+
initial = normalize(input);
|
|
2911
|
+
}
|
|
2912
|
+
return {
|
|
2913
|
+
projections,
|
|
2914
|
+
create: () => {
|
|
2915
|
+
let initialized = compute === void 0;
|
|
2916
|
+
let current = initial;
|
|
2917
|
+
const revisions = new Array(projections.length).fill(-1);
|
|
2918
|
+
return {
|
|
2919
|
+
get value() {
|
|
2920
|
+
if (!initialized) throw new Error(`${name} selection was not initialized.`);
|
|
2921
|
+
return current;
|
|
2922
|
+
},
|
|
2923
|
+
refresh(sources) {
|
|
2924
|
+
if (!compute) return false;
|
|
2925
|
+
let dirty = !initialized;
|
|
2926
|
+
for (let index = 0; index < revisions.length; index++) {
|
|
2927
|
+
const source = sources[index + 1];
|
|
2928
|
+
if (source.reset || source.revision !== revisions[index]) dirty = true;
|
|
2929
|
+
}
|
|
2930
|
+
if (!dirty) return false;
|
|
2931
|
+
const candidate = compute(sources);
|
|
2932
|
+
require_transform.assertSynchronous(candidate);
|
|
2933
|
+
const next = normalize(candidate, current);
|
|
2934
|
+
const changed = !initialized || !Object.is(current, next);
|
|
2935
|
+
current = next;
|
|
2936
|
+
initialized = true;
|
|
2937
|
+
for (let index = 0; index < revisions.length; index++) revisions[index] = sources[index + 1].revision;
|
|
2938
|
+
return changed;
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
};
|
|
2944
|
+
const normalizeKey = (value) => {
|
|
2945
|
+
if (value !== void 0 && typeof value !== "string") throw new TypeError("derive.keyed.get key must be a string or undefined.");
|
|
2946
|
+
return value;
|
|
2947
|
+
};
|
|
2948
|
+
const normalizeOrderedKeys = (value, previous) => {
|
|
2949
|
+
if (!Array.isArray(value)) throw new TypeError("derive.keyed.subset ordered keys must be an array.");
|
|
2950
|
+
if (previous && require_transform.sameArray(previous.keys, value)) return previous;
|
|
2951
|
+
const requested = /* @__PURE__ */ new Set();
|
|
2952
|
+
for (const key of value) {
|
|
2953
|
+
if (typeof key !== "string") throw new TypeError("derive.keyed.subset keys must be strings.");
|
|
2954
|
+
if (requested.has(key)) throw new TypeError("derive.keyed.subset keys must not contain duplicate keys.");
|
|
2955
|
+
requested.add(key);
|
|
2956
|
+
}
|
|
2957
|
+
return {
|
|
2958
|
+
keys: require_transform.snapshotArray(value),
|
|
2959
|
+
requested
|
|
2960
|
+
};
|
|
2961
|
+
};
|
|
2962
|
+
function createKeyedGet(source, input, selectOrEquality, maybeEquality) {
|
|
2963
|
+
const name = "derive.keyed.get";
|
|
2964
|
+
require_transform.assertKeyedProjection(source, `${name} source`);
|
|
2965
|
+
const named = !require_transform.isProjection(input) && typeof input !== "string" && input !== void 0;
|
|
2966
|
+
const selection = compileSelection(input, named, named ? selectOrEquality : void 0, normalizeKey, name);
|
|
2967
|
+
const equality = named ? maybeEquality : selectOrEquality;
|
|
2968
|
+
if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${name} equality must be a function.`);
|
|
2969
|
+
const [projection] = require_transform.defineProcessor({
|
|
2970
|
+
dependencies: [source, ...selection.projections],
|
|
2971
|
+
outputs: [{
|
|
2972
|
+
kind: "value",
|
|
2973
|
+
equality: equality ?? Object.is
|
|
2974
|
+
}],
|
|
2975
|
+
name,
|
|
2976
|
+
create: () => {
|
|
2977
|
+
const selected = selection.create();
|
|
2978
|
+
return { evaluate: (evaluation) => {
|
|
2979
|
+
const collection = evaluation.sources[0];
|
|
2980
|
+
const output = evaluation.outputs[0];
|
|
2981
|
+
if (collection.kind !== "collection" || output.kind !== "value") throw new Error(`${name} resolved invalid input/output kinds.`);
|
|
2982
|
+
const keyChanged = selected.refresh(evaluation.sources);
|
|
2983
|
+
const key = selected.value;
|
|
2984
|
+
const change = collection.change;
|
|
2985
|
+
if (evaluation.reset || keyChanged || change?.kind === "reset" || key !== void 0 && change && require_transform.collectionChangeTouchesKey(change, key)) output.output.set(key === void 0 ? void 0 : collection.read.get(key));
|
|
2986
|
+
} };
|
|
2987
|
+
}
|
|
2988
|
+
});
|
|
2989
|
+
return projection;
|
|
2990
|
+
}
|
|
2991
|
+
function createKeyedSubset(source, input, selectOrderedKeys) {
|
|
2992
|
+
const name = "derive.keyed.subset";
|
|
2993
|
+
require_transform.assertKeyedProjection(source, `${name} source`);
|
|
2994
|
+
const selection = compileSelection(input, !require_transform.isProjection(input) && !Array.isArray(input), selectOrderedKeys, normalizeOrderedKeys, name);
|
|
2995
|
+
const [projection] = require_transform.defineProcessor({
|
|
2996
|
+
dependencies: [source, ...selection.projections],
|
|
2997
|
+
outputs: [{
|
|
2998
|
+
kind: "collection",
|
|
2999
|
+
equality: Object.is
|
|
3000
|
+
}],
|
|
3001
|
+
name,
|
|
3002
|
+
create: () => {
|
|
3003
|
+
const selected = selection.create();
|
|
3004
|
+
return { evaluate: (evaluation) => {
|
|
3005
|
+
const driver = evaluation.sources[0];
|
|
3006
|
+
const output = evaluation.outputs[0];
|
|
3007
|
+
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error(`${name} requires collection input and output.`);
|
|
3008
|
+
const keysChanged = selected.refresh(evaluation.sources);
|
|
3009
|
+
const { keys, requested } = selected.value;
|
|
3010
|
+
const change = driver.change;
|
|
3011
|
+
if (evaluation.reset || change?.kind === "reset") {
|
|
3012
|
+
const present = keys.filter((key) => driver.read.has(key));
|
|
3013
|
+
for (const key of present) output.output.set(key, driver.read.get(key));
|
|
3014
|
+
output.output.order(present);
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
let orderChanged = keysChanged;
|
|
3018
|
+
if (keysChanged) {
|
|
3019
|
+
for (const key of output.previous.ids()) if (!requested.has(key) || !driver.read.has(key)) output.output.remove(key);
|
|
3020
|
+
for (const key of keys) if (!output.previous.has(key) && driver.read.has(key)) output.output.set(key, driver.read.get(key));
|
|
3021
|
+
} else if (change) {
|
|
3022
|
+
for (const entry of change.removed) {
|
|
3023
|
+
if (!requested.has(entry.key)) continue;
|
|
3024
|
+
output.output.remove(entry.key);
|
|
3025
|
+
orderChanged = true;
|
|
3026
|
+
}
|
|
3027
|
+
for (const entry of change.added) {
|
|
3028
|
+
if (!requested.has(entry.key)) continue;
|
|
3029
|
+
output.output.set(entry.key, entry.after);
|
|
3030
|
+
orderChanged = true;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
if (change) {
|
|
3034
|
+
for (const entry of change.updated) if (requested.has(entry.key) && (!keysChanged || output.previous.has(entry.key))) output.output.set(entry.key, entry.after);
|
|
3035
|
+
}
|
|
3036
|
+
if (orderChanged) output.output.order(keys.filter((key) => driver.read.has(key)));
|
|
3037
|
+
} };
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
return projection;
|
|
3041
|
+
}
|
|
3042
|
+
//#endregion
|
|
2886
3043
|
//#region core/src/projection/keyed/composition.ts
|
|
2887
3044
|
const assertEquality = (equality, role) => {
|
|
2888
3045
|
if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${role} equality must be a function.`);
|
|
@@ -3158,50 +3315,59 @@ function createKeyedMerge(sources, options) {
|
|
|
3158
3315
|
});
|
|
3159
3316
|
return projection;
|
|
3160
3317
|
}
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3318
|
+
function createKeyedSingleton(sourceOrDependencies, compute, equality) {
|
|
3319
|
+
const name = "derive.keyed.singleton";
|
|
3320
|
+
const selection = require_transform.isProjection(sourceOrDependencies) ? {
|
|
3321
|
+
kind: "scalar",
|
|
3322
|
+
source: sourceOrDependencies,
|
|
3323
|
+
keyOf: compute
|
|
3324
|
+
} : {
|
|
3325
|
+
kind: "named",
|
|
3326
|
+
compiled: require_transform.compileProjectionDependencies(sourceOrDependencies, name),
|
|
3327
|
+
computeEntry: compute
|
|
3328
|
+
};
|
|
3329
|
+
if (selection.kind === "scalar" && require_transform.outputKind(selection.source) !== "value") throw new TypeError(`${name} source must be a scalar projection.`);
|
|
3330
|
+
if (typeof compute !== "function") throw new TypeError(`${name} requires a ${selection.kind === "scalar" ? "key selector" : "compute callback"}.`);
|
|
3331
|
+
const outputEquality = assertEquality(equality, name);
|
|
3164
3332
|
const [projection] = require_transform.defineProcessor({
|
|
3165
|
-
dependencies: [source],
|
|
3333
|
+
dependencies: selection.kind === "named" ? selection.compiled.projections : [selection.source],
|
|
3166
3334
|
outputs: [{
|
|
3167
3335
|
kind: "collection",
|
|
3168
|
-
equality
|
|
3336
|
+
equality: outputEquality
|
|
3169
3337
|
}],
|
|
3170
3338
|
create: () => ({ evaluate: (evaluation) => {
|
|
3171
|
-
const scalar = evaluation.sources[0];
|
|
3172
3339
|
const output = evaluation.outputs[0];
|
|
3173
|
-
if (
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3340
|
+
if (output.kind !== "collection") throw new Error(`${name} requires a collection output.`);
|
|
3341
|
+
let key;
|
|
3342
|
+
let value;
|
|
3343
|
+
if (selection.kind === "named") {
|
|
3344
|
+
const entry = selection.computeEntry(require_transform.snapshotProjectionValues(selection.compiled.names, evaluation.sources));
|
|
3345
|
+
require_transform.assertSynchronous(entry);
|
|
3346
|
+
if (entry !== void 0) {
|
|
3347
|
+
assertKeyedEntry(entry, name);
|
|
3348
|
+
key = entry[0];
|
|
3349
|
+
value = entry[1];
|
|
3350
|
+
}
|
|
3351
|
+
} else {
|
|
3352
|
+
const scalar = evaluation.sources[0];
|
|
3353
|
+
if (scalar?.kind !== "value") throw new Error(`${name} resolved a non-scalar input.`);
|
|
3354
|
+
value = scalar.value;
|
|
3355
|
+
if (value !== void 0) {
|
|
3356
|
+
key = selection.keyOf(value);
|
|
3357
|
+
require_transform.assertSynchronous(key);
|
|
3358
|
+
if (typeof key !== "string") throw new TypeError(`${name} key selector must return a string.`);
|
|
3359
|
+
}
|
|
3178
3360
|
}
|
|
3179
|
-
const
|
|
3180
|
-
|
|
3181
|
-
if (
|
|
3182
|
-
for (const previousKey of previous) if (previousKey !== key) output.output.remove(previousKey);
|
|
3183
|
-
output.output.set(key, scalar.value);
|
|
3184
|
-
if (previous.length !== 1 || previous[0] !== key) output.output.order([key]);
|
|
3361
|
+
const previousKey = output.previous.ids()[0];
|
|
3362
|
+
if (previousKey !== void 0 && previousKey !== key) output.output.remove(previousKey);
|
|
3363
|
+
if (key !== void 0) output.output.set(key, value);
|
|
3185
3364
|
} }),
|
|
3186
|
-
name
|
|
3365
|
+
name
|
|
3187
3366
|
});
|
|
3188
3367
|
return projection;
|
|
3189
|
-
}
|
|
3368
|
+
}
|
|
3190
3369
|
//#endregion
|
|
3191
3370
|
//#region core/src/projection/derive/keyed.ts
|
|
3192
|
-
const compileOrderedKeys = (value, role) => {
|
|
3193
|
-
if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
|
|
3194
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3195
|
-
for (const key of value) {
|
|
3196
|
-
if (typeof key !== "string") throw new TypeError(`${role} must contain only string keys.`);
|
|
3197
|
-
if (seen.has(key)) throw new TypeError(`${role} must not contain duplicate keys.`);
|
|
3198
|
-
seen.add(key);
|
|
3199
|
-
}
|
|
3200
|
-
return {
|
|
3201
|
-
keys: value,
|
|
3202
|
-
requested: seen
|
|
3203
|
-
};
|
|
3204
|
-
};
|
|
3205
3371
|
function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
|
|
3206
3372
|
const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
|
|
3207
3373
|
const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
|
|
@@ -3306,44 +3472,6 @@ const createOrderedSnapshot = (source, name, make) => {
|
|
|
3306
3472
|
};
|
|
3307
3473
|
const createKeyedValues = (source) => createOrderedSnapshot(source, "derive.keyed.values", (_key, value) => value);
|
|
3308
3474
|
const createKeyedEntries = (source) => createOrderedSnapshot(source, "derive.keyed.entries", (key, value) => Object.freeze([key, value]));
|
|
3309
|
-
const createKeyedGet = (source, key, equality = Object.is) => {
|
|
3310
|
-
require_transform.assertKeyedProjection(source, "derive.keyed.get source");
|
|
3311
|
-
if (require_transform.outputKind(key) !== "value") throw new TypeError("derive.keyed.get key must be a scalar projection.");
|
|
3312
|
-
const [projection] = require_transform.defineProcessor({
|
|
3313
|
-
dependencies: [source, key],
|
|
3314
|
-
outputs: [{
|
|
3315
|
-
kind: "value",
|
|
3316
|
-
equality
|
|
3317
|
-
}],
|
|
3318
|
-
create: () => {
|
|
3319
|
-
let sourceRevision = -1;
|
|
3320
|
-
let keyRevision = -1;
|
|
3321
|
-
let selected;
|
|
3322
|
-
return { evaluate: (evaluation) => {
|
|
3323
|
-
const collection = evaluation.sources[0];
|
|
3324
|
-
const selectedKey = evaluation.sources[1];
|
|
3325
|
-
const output = evaluation.outputs[0];
|
|
3326
|
-
if (collection.kind !== "collection" || selectedKey.kind !== "value" || output.kind !== "value") throw new Error("derive.keyed.get resolved invalid input/output kinds.");
|
|
3327
|
-
let affected = evaluation.reset;
|
|
3328
|
-
if (evaluation.reset || keyRevision !== selectedKey.revision) {
|
|
3329
|
-
const candidate = selectedKey.value;
|
|
3330
|
-
if (candidate !== void 0 && typeof candidate !== "string") throw new TypeError("derive.keyed.get key must resolve to a string or undefined.");
|
|
3331
|
-
selected = candidate;
|
|
3332
|
-
affected = true;
|
|
3333
|
-
} else if (sourceRevision !== collection.revision) {
|
|
3334
|
-
const change = collection.change;
|
|
3335
|
-
if (!change || change.kind === "reset") affected = true;
|
|
3336
|
-
else if (selected !== void 0) affected = require_transform.collectionChangeTouchesKey(change, selected);
|
|
3337
|
-
}
|
|
3338
|
-
if (affected) output.output.set(selected === void 0 ? void 0 : collection.read.get(selected));
|
|
3339
|
-
sourceRevision = collection.revision;
|
|
3340
|
-
keyRevision = selectedKey.revision;
|
|
3341
|
-
} };
|
|
3342
|
-
},
|
|
3343
|
-
name: "derive.keyed.get"
|
|
3344
|
-
});
|
|
3345
|
-
return projection;
|
|
3346
|
-
};
|
|
3347
3475
|
function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
|
|
3348
3476
|
const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
|
|
3349
3477
|
const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
|
|
@@ -3378,101 +3506,6 @@ function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, mayb
|
|
|
3378
3506
|
}
|
|
3379
3507
|
});
|
|
3380
3508
|
}
|
|
3381
|
-
const createKeyedSubset = (source, orderedKeys) => {
|
|
3382
|
-
require_transform.assertKeyedProjection(source, "Keyed subset source");
|
|
3383
|
-
const keyProjection = require_transform.isProjection(orderedKeys) ? orderedKeys : void 0;
|
|
3384
|
-
if (keyProjection && require_transform.outputKind(keyProjection) !== "value") throw new TypeError("Keyed subset ordered keys must be a value projection.");
|
|
3385
|
-
const staticSelection = keyProjection ? void 0 : (() => {
|
|
3386
|
-
const compiled = compileOrderedKeys(orderedKeys, "Keyed subset ordered keys");
|
|
3387
|
-
return {
|
|
3388
|
-
keys: Object.freeze([...compiled.keys]),
|
|
3389
|
-
requested: compiled.requested
|
|
3390
|
-
};
|
|
3391
|
-
})();
|
|
3392
|
-
const [projection] = require_transform.defineProcessor({
|
|
3393
|
-
dependencies: keyProjection ? [source, keyProjection] : [source],
|
|
3394
|
-
outputs: [{
|
|
3395
|
-
kind: "collection",
|
|
3396
|
-
equality: Object.is
|
|
3397
|
-
}],
|
|
3398
|
-
create: () => {
|
|
3399
|
-
let keys = staticSelection?.keys;
|
|
3400
|
-
let requested = staticSelection?.requested;
|
|
3401
|
-
let keyRevision = -1;
|
|
3402
|
-
return {
|
|
3403
|
-
evaluate: (evaluation) => {
|
|
3404
|
-
const driver = evaluation.sources[0];
|
|
3405
|
-
const output = evaluation.outputs[0];
|
|
3406
|
-
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.subset requires collection input and output.");
|
|
3407
|
-
let keysChanged = false;
|
|
3408
|
-
if (keyProjection) {
|
|
3409
|
-
const sourceContext = evaluation.sources[1];
|
|
3410
|
-
if (sourceContext?.kind !== "value") throw new TypeError("Keyed subset ordered keys resolved to a collection.");
|
|
3411
|
-
if (keys === void 0 || sourceContext.reset || keyRevision !== sourceContext.revision) {
|
|
3412
|
-
const compiled = compileOrderedKeys(sourceContext.value, "Keyed subset ordered keys");
|
|
3413
|
-
keys = compiled.keys;
|
|
3414
|
-
requested = compiled.requested;
|
|
3415
|
-
keyRevision = sourceContext.revision;
|
|
3416
|
-
keysChanged = true;
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3419
|
-
if (!keys || !requested) throw new Error("Keyed subset keys were not initialized.");
|
|
3420
|
-
const activeKeys = keys;
|
|
3421
|
-
const activeRequested = requested;
|
|
3422
|
-
const orderedPresentKeys = () => activeKeys.filter((key) => driver.read.has(key));
|
|
3423
|
-
const rebuild = () => {
|
|
3424
|
-
const nextKeys = orderedPresentKeys();
|
|
3425
|
-
for (const key of nextKeys) output.output.set(key, driver.read.get(key));
|
|
3426
|
-
output.output.order(nextKeys);
|
|
3427
|
-
};
|
|
3428
|
-
const reconcileKeys = () => {
|
|
3429
|
-
const nextKeys = orderedPresentKeys();
|
|
3430
|
-
for (const key of output.previous.ids()) if (!activeRequested.has(key) || !driver.read.has(key)) output.output.remove(key);
|
|
3431
|
-
for (const key of nextKeys) if (!output.previous.has(key)) output.output.set(key, driver.read.get(key));
|
|
3432
|
-
output.output.order(nextKeys);
|
|
3433
|
-
};
|
|
3434
|
-
const change = driver.change;
|
|
3435
|
-
if (evaluation.reset || change?.kind === "reset") rebuild();
|
|
3436
|
-
else if (keysChanged) {
|
|
3437
|
-
reconcileKeys();
|
|
3438
|
-
if (change) for (const entry of change.updated) {
|
|
3439
|
-
const key = entry.key;
|
|
3440
|
-
if (activeRequested.has(key) && output.previous.has(key)) output.output.set(key, entry.after);
|
|
3441
|
-
}
|
|
3442
|
-
} else if (change) {
|
|
3443
|
-
let updateOrder = false;
|
|
3444
|
-
for (const entry of change.removed) {
|
|
3445
|
-
const key = entry.key;
|
|
3446
|
-
if (!activeRequested.has(key)) continue;
|
|
3447
|
-
output.output.remove(key);
|
|
3448
|
-
updateOrder = true;
|
|
3449
|
-
}
|
|
3450
|
-
for (const entry of change.added) {
|
|
3451
|
-
const key = entry.key;
|
|
3452
|
-
if (!activeRequested.has(key)) continue;
|
|
3453
|
-
output.output.set(key, entry.after);
|
|
3454
|
-
updateOrder = true;
|
|
3455
|
-
}
|
|
3456
|
-
for (const entry of change.updated) {
|
|
3457
|
-
const key = entry.key;
|
|
3458
|
-
if (activeRequested.has(key)) output.output.set(key, entry.after);
|
|
3459
|
-
}
|
|
3460
|
-
if (updateOrder) output.output.order(orderedPresentKeys());
|
|
3461
|
-
}
|
|
3462
|
-
},
|
|
3463
|
-
release: () => {
|
|
3464
|
-
if (keyProjection) {
|
|
3465
|
-
keys = void 0;
|
|
3466
|
-
requested = void 0;
|
|
3467
|
-
keyRevision = -1;
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
};
|
|
3471
|
-
},
|
|
3472
|
-
name: "derive.keyed.subset"
|
|
3473
|
-
});
|
|
3474
|
-
return projection;
|
|
3475
|
-
};
|
|
3476
3509
|
const normalizeGroups = (value) => {
|
|
3477
3510
|
const groups = typeof value === "string" ? [value] : value;
|
|
3478
3511
|
if (!Array.isArray(groups)) throw new TypeError("derive.keyed.groupBy selector must return a string or an array of strings.");
|
package/dist/index.d.cts
CHANGED
|
@@ -33,6 +33,13 @@ declare function observe<S extends ObjectSchema<object>, P>(document: ReadonlyDo
|
|
|
33
33
|
type ProjectionDependencies = Readonly<Record<string, Projection<unknown>>> & Partial<Record<keyof Projection<unknown>, never>>;
|
|
34
34
|
type ProjectionValues<D extends ProjectionDependencies> = { readonly [K in keyof D]: D[K] extends Projection<infer T> ? T : never };
|
|
35
35
|
//#endregion
|
|
36
|
+
//#region core/src/projection/keyed/selection.d.ts
|
|
37
|
+
type Equality$3<T> = (previous: T, next: T) => boolean;
|
|
38
|
+
declare function createKeyedGet<K extends string, V>(source: KeyedProjection<K, V>, key: NoInfer<K> | undefined | Projection<NoInfer<K> | undefined>, equality?: Equality$3<NoInfer<V> | undefined>): Projection<V | undefined>;
|
|
39
|
+
declare function createKeyedGet<K extends string, V, const D extends ProjectionDependencies>(source: KeyedProjection<K, V>, dependencies: D, selectKey: (values: ProjectionValues<D>) => Synchronous<NoInfer<K> | undefined>, equality?: Equality$3<NoInfer<V> | undefined>): Projection<V | undefined>;
|
|
40
|
+
declare function createKeyedSubset<K extends string, V>(source: KeyedProjection<K, V>, orderedKeys: Projection<readonly NoInfer<K>[]> | readonly NoInfer<K>[]): KeyedProjection<K, V>;
|
|
41
|
+
declare function createKeyedSubset<K extends string, V, const D extends ProjectionDependencies>(source: KeyedProjection<K, V>, dependencies: D, selectOrderedKeys: (values: ProjectionValues<D>) => Synchronous<readonly NoInfer<K>[]>): KeyedProjection<K, V>;
|
|
42
|
+
//#endregion
|
|
36
43
|
//#region core/src/projection/keyed/entries.d.ts
|
|
37
44
|
type KeyedEntries<K extends string, V> = readonly (readonly [K, V])[];
|
|
38
45
|
//#endregion
|
|
@@ -53,6 +60,8 @@ declare function createKeyedMerge<K extends string, V>(sources: readonly KeyedPr
|
|
|
53
60
|
}[], key: NoInfer<K>) => Synchronous<V>;
|
|
54
61
|
readonly equality?: Equality$2<V>;
|
|
55
62
|
}): KeyedProjection<K, V>;
|
|
63
|
+
declare function createKeyedSingleton<const K extends string, V>(source: Projection<V | undefined>, keyOf: (value: V) => K, equality?: Equality$2<V>): KeyedProjection<K, V>;
|
|
64
|
+
declare function createKeyedSingleton<const D extends ProjectionDependencies, K extends string, V>(dependencies: D, computeEntry: (values: ProjectionValues<D>) => Synchronous<readonly [K, V] | undefined>, equality?: Equality$2<V>): KeyedProjection<K, V>;
|
|
56
65
|
//#endregion
|
|
57
66
|
//#region core/src/projection/keyed/flat-map.d.ts
|
|
58
67
|
type Equality$1<V> = (previous: V, next: V) => boolean;
|
|
@@ -70,7 +79,7 @@ declare const derive: typeof createValueDerive & {
|
|
|
70
79
|
keys: <K extends string, V>(source: KeyedProjection<K, V>) => Projection<readonly K[]>;
|
|
71
80
|
values: <K extends string, V>(source: KeyedProjection<K, V>) => Projection<readonly V[]>;
|
|
72
81
|
entries: <K extends string, V>(source: KeyedProjection<K, V>) => Projection<readonly (readonly [K, V])[]>;
|
|
73
|
-
get:
|
|
82
|
+
get: typeof createKeyedGet;
|
|
74
83
|
from: typeof createKeyedFrom;
|
|
75
84
|
fromEntries: typeof createKeyedFromEntries;
|
|
76
85
|
merge: typeof createKeyedMerge;
|
|
@@ -79,8 +88,8 @@ declare const derive: typeof createValueDerive & {
|
|
|
79
88
|
<K extends string, V, const D extends object, G extends string>(source: KeyedProjection<K, V>, dependencies: D & KeyedDependencies<NoInfer<K>, NoInfer<V>, D>, selector: (value: NoInfer<V>, key: NoInfer<K>, dependencies: KeyedDependencyValues<D>) => Synchronous<G | readonly G[]>): KeyedProjection<G, readonly K[]>;
|
|
80
89
|
};
|
|
81
90
|
flatMap: typeof createKeyedFlatMap;
|
|
82
|
-
singleton:
|
|
83
|
-
subset:
|
|
91
|
+
singleton: typeof createKeyedSingleton;
|
|
92
|
+
subset: typeof createKeyedSubset;
|
|
84
93
|
filter: {
|
|
85
94
|
<K extends string, V>(source: KeyedProjection<K, V>, predicate: (value: NoInfer<V>, key: NoInfer<K>) => Synchronous<boolean>): KeyedProjection<K, V>;
|
|
86
95
|
<K extends string, V, const D extends object>(source: KeyedProjection<K, V>, dependencies: D & KeyedDependencies<NoInfer<K>, NoInfer<V>, D>, predicate: (value: NoInfer<V>, key: NoInfer<K>, dependencies: KeyedDependencyValues<D>) => Synchronous<boolean>): KeyedProjection<K, V>;
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,13 @@ declare function observe<S extends ObjectSchema<object>, P>(document: ReadonlyDo
|
|
|
33
33
|
type ProjectionDependencies = Readonly<Record<string, Projection<unknown>>> & Partial<Record<keyof Projection<unknown>, never>>;
|
|
34
34
|
type ProjectionValues<D extends ProjectionDependencies> = { readonly [K in keyof D]: D[K] extends Projection<infer T> ? T : never };
|
|
35
35
|
//#endregion
|
|
36
|
+
//#region core/src/projection/keyed/selection.d.ts
|
|
37
|
+
type Equality$3<T> = (previous: T, next: T) => boolean;
|
|
38
|
+
declare function createKeyedGet<K extends string, V>(source: KeyedProjection<K, V>, key: NoInfer<K> | undefined | Projection<NoInfer<K> | undefined>, equality?: Equality$3<NoInfer<V> | undefined>): Projection<V | undefined>;
|
|
39
|
+
declare function createKeyedGet<K extends string, V, const D extends ProjectionDependencies>(source: KeyedProjection<K, V>, dependencies: D, selectKey: (values: ProjectionValues<D>) => Synchronous<NoInfer<K> | undefined>, equality?: Equality$3<NoInfer<V> | undefined>): Projection<V | undefined>;
|
|
40
|
+
declare function createKeyedSubset<K extends string, V>(source: KeyedProjection<K, V>, orderedKeys: Projection<readonly NoInfer<K>[]> | readonly NoInfer<K>[]): KeyedProjection<K, V>;
|
|
41
|
+
declare function createKeyedSubset<K extends string, V, const D extends ProjectionDependencies>(source: KeyedProjection<K, V>, dependencies: D, selectOrderedKeys: (values: ProjectionValues<D>) => Synchronous<readonly NoInfer<K>[]>): KeyedProjection<K, V>;
|
|
42
|
+
//#endregion
|
|
36
43
|
//#region core/src/projection/keyed/entries.d.ts
|
|
37
44
|
type KeyedEntries<K extends string, V> = readonly (readonly [K, V])[];
|
|
38
45
|
//#endregion
|
|
@@ -53,6 +60,8 @@ declare function createKeyedMerge<K extends string, V>(sources: readonly KeyedPr
|
|
|
53
60
|
}[], key: NoInfer<K>) => Synchronous<V>;
|
|
54
61
|
readonly equality?: Equality$2<V>;
|
|
55
62
|
}): KeyedProjection<K, V>;
|
|
63
|
+
declare function createKeyedSingleton<const K extends string, V>(source: Projection<V | undefined>, keyOf: (value: V) => K, equality?: Equality$2<V>): KeyedProjection<K, V>;
|
|
64
|
+
declare function createKeyedSingleton<const D extends ProjectionDependencies, K extends string, V>(dependencies: D, computeEntry: (values: ProjectionValues<D>) => Synchronous<readonly [K, V] | undefined>, equality?: Equality$2<V>): KeyedProjection<K, V>;
|
|
56
65
|
//#endregion
|
|
57
66
|
//#region core/src/projection/keyed/flat-map.d.ts
|
|
58
67
|
type Equality$1<V> = (previous: V, next: V) => boolean;
|
|
@@ -70,7 +79,7 @@ declare const derive: typeof createValueDerive & {
|
|
|
70
79
|
keys: <K extends string, V>(source: KeyedProjection<K, V>) => Projection<readonly K[]>;
|
|
71
80
|
values: <K extends string, V>(source: KeyedProjection<K, V>) => Projection<readonly V[]>;
|
|
72
81
|
entries: <K extends string, V>(source: KeyedProjection<K, V>) => Projection<readonly (readonly [K, V])[]>;
|
|
73
|
-
get:
|
|
82
|
+
get: typeof createKeyedGet;
|
|
74
83
|
from: typeof createKeyedFrom;
|
|
75
84
|
fromEntries: typeof createKeyedFromEntries;
|
|
76
85
|
merge: typeof createKeyedMerge;
|
|
@@ -79,8 +88,8 @@ declare const derive: typeof createValueDerive & {
|
|
|
79
88
|
<K extends string, V, const D extends object, G extends string>(source: KeyedProjection<K, V>, dependencies: D & KeyedDependencies<NoInfer<K>, NoInfer<V>, D>, selector: (value: NoInfer<V>, key: NoInfer<K>, dependencies: KeyedDependencyValues<D>) => Synchronous<G | readonly G[]>): KeyedProjection<G, readonly K[]>;
|
|
80
89
|
};
|
|
81
90
|
flatMap: typeof createKeyedFlatMap;
|
|
82
|
-
singleton:
|
|
83
|
-
subset:
|
|
91
|
+
singleton: typeof createKeyedSingleton;
|
|
92
|
+
subset: typeof createKeyedSubset;
|
|
84
93
|
filter: {
|
|
85
94
|
<K extends string, V>(source: KeyedProjection<K, V>, predicate: (value: NoInfer<V>, key: NoInfer<K>) => Synchronous<boolean>): KeyedProjection<K, V>;
|
|
86
95
|
<K extends string, V, const D extends object>(source: KeyedProjection<K, V>, dependencies: D & KeyedDependencies<NoInfer<K>, NoInfer<V>, D>, predicate: (value: NoInfer<V>, key: NoInfer<K>, dependencies: KeyedDependencyValues<D>) => Synchronous<boolean>): KeyedProjection<K, V>;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform-
|
|
1
|
+
import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform-Cl7CSsdR.js";
|
|
2
2
|
import { n as isPlainObject, r as isRecord, t as installOwn } from "./record-B2yRhYsc.js";
|
|
3
3
|
import { c as AddressIndex, d as invalidValue, f as children, g as validate, h as parent, i as contextOf, l as MutationRejected, m as is, o as decodeChanges, p as contains$1, r as bindContext, s as sealChanges, t as assertRuntimeWritable, u as fail } from "./driver-B7RR-VfX.js";
|
|
4
4
|
//#region core/src/schema/model.ts
|
|
@@ -2882,6 +2882,163 @@ function createKeyedFlatMap(source, dependenciesOrSelect, selectOrEquality, mayb
|
|
|
2882
2882
|
return projection;
|
|
2883
2883
|
}
|
|
2884
2884
|
//#endregion
|
|
2885
|
+
//#region core/src/projection/keyed/selection.ts
|
|
2886
|
+
/** Compiles selection inputs; runtime instances retain only the request and its dependency revisions.
|
|
2887
|
+
* Source slot zero belongs to the collection being queried, not to the selection expression.
|
|
2888
|
+
*/
|
|
2889
|
+
const compileSelection = (input, named, select, normalize, name) => {
|
|
2890
|
+
let projections;
|
|
2891
|
+
let compute;
|
|
2892
|
+
let initial;
|
|
2893
|
+
if (named) {
|
|
2894
|
+
const compiled = compileProjectionDependencies(input, name);
|
|
2895
|
+
if (typeof select !== "function") throw new TypeError(`${name} requires a selection callback.`);
|
|
2896
|
+
projections = compiled.projections;
|
|
2897
|
+
const selectValues = select;
|
|
2898
|
+
compute = (sources) => selectValues(snapshotProjectionValues(compiled.names, sources, 1));
|
|
2899
|
+
} else if (isProjection(input)) {
|
|
2900
|
+
if (outputKind(input) !== "value") throw new TypeError(`${name} selection must be a scalar projection.`);
|
|
2901
|
+
projections = [input];
|
|
2902
|
+
compute = (sources) => {
|
|
2903
|
+
const source = sources[1];
|
|
2904
|
+
if (source?.kind !== "value") throw new Error(`${name} resolved a non-scalar selection.`);
|
|
2905
|
+
return source.value;
|
|
2906
|
+
};
|
|
2907
|
+
} else {
|
|
2908
|
+
projections = [];
|
|
2909
|
+
initial = normalize(input);
|
|
2910
|
+
}
|
|
2911
|
+
return {
|
|
2912
|
+
projections,
|
|
2913
|
+
create: () => {
|
|
2914
|
+
let initialized = compute === void 0;
|
|
2915
|
+
let current = initial;
|
|
2916
|
+
const revisions = new Array(projections.length).fill(-1);
|
|
2917
|
+
return {
|
|
2918
|
+
get value() {
|
|
2919
|
+
if (!initialized) throw new Error(`${name} selection was not initialized.`);
|
|
2920
|
+
return current;
|
|
2921
|
+
},
|
|
2922
|
+
refresh(sources) {
|
|
2923
|
+
if (!compute) return false;
|
|
2924
|
+
let dirty = !initialized;
|
|
2925
|
+
for (let index = 0; index < revisions.length; index++) {
|
|
2926
|
+
const source = sources[index + 1];
|
|
2927
|
+
if (source.reset || source.revision !== revisions[index]) dirty = true;
|
|
2928
|
+
}
|
|
2929
|
+
if (!dirty) return false;
|
|
2930
|
+
const candidate = compute(sources);
|
|
2931
|
+
assertSynchronous(candidate);
|
|
2932
|
+
const next = normalize(candidate, current);
|
|
2933
|
+
const changed = !initialized || !Object.is(current, next);
|
|
2934
|
+
current = next;
|
|
2935
|
+
initialized = true;
|
|
2936
|
+
for (let index = 0; index < revisions.length; index++) revisions[index] = sources[index + 1].revision;
|
|
2937
|
+
return changed;
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
};
|
|
2943
|
+
const normalizeKey = (value) => {
|
|
2944
|
+
if (value !== void 0 && typeof value !== "string") throw new TypeError("derive.keyed.get key must be a string or undefined.");
|
|
2945
|
+
return value;
|
|
2946
|
+
};
|
|
2947
|
+
const normalizeOrderedKeys = (value, previous) => {
|
|
2948
|
+
if (!Array.isArray(value)) throw new TypeError("derive.keyed.subset ordered keys must be an array.");
|
|
2949
|
+
if (previous && sameArray(previous.keys, value)) return previous;
|
|
2950
|
+
const requested = /* @__PURE__ */ new Set();
|
|
2951
|
+
for (const key of value) {
|
|
2952
|
+
if (typeof key !== "string") throw new TypeError("derive.keyed.subset keys must be strings.");
|
|
2953
|
+
if (requested.has(key)) throw new TypeError("derive.keyed.subset keys must not contain duplicate keys.");
|
|
2954
|
+
requested.add(key);
|
|
2955
|
+
}
|
|
2956
|
+
return {
|
|
2957
|
+
keys: snapshotArray(value),
|
|
2958
|
+
requested
|
|
2959
|
+
};
|
|
2960
|
+
};
|
|
2961
|
+
function createKeyedGet(source, input, selectOrEquality, maybeEquality) {
|
|
2962
|
+
const name = "derive.keyed.get";
|
|
2963
|
+
assertKeyedProjection(source, `${name} source`);
|
|
2964
|
+
const named = !isProjection(input) && typeof input !== "string" && input !== void 0;
|
|
2965
|
+
const selection = compileSelection(input, named, named ? selectOrEquality : void 0, normalizeKey, name);
|
|
2966
|
+
const equality = named ? maybeEquality : selectOrEquality;
|
|
2967
|
+
if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${name} equality must be a function.`);
|
|
2968
|
+
const [projection] = defineProcessor({
|
|
2969
|
+
dependencies: [source, ...selection.projections],
|
|
2970
|
+
outputs: [{
|
|
2971
|
+
kind: "value",
|
|
2972
|
+
equality: equality ?? Object.is
|
|
2973
|
+
}],
|
|
2974
|
+
name,
|
|
2975
|
+
create: () => {
|
|
2976
|
+
const selected = selection.create();
|
|
2977
|
+
return { evaluate: (evaluation) => {
|
|
2978
|
+
const collection = evaluation.sources[0];
|
|
2979
|
+
const output = evaluation.outputs[0];
|
|
2980
|
+
if (collection.kind !== "collection" || output.kind !== "value") throw new Error(`${name} resolved invalid input/output kinds.`);
|
|
2981
|
+
const keyChanged = selected.refresh(evaluation.sources);
|
|
2982
|
+
const key = selected.value;
|
|
2983
|
+
const change = collection.change;
|
|
2984
|
+
if (evaluation.reset || keyChanged || change?.kind === "reset" || key !== void 0 && change && collectionChangeTouchesKey(change, key)) output.output.set(key === void 0 ? void 0 : collection.read.get(key));
|
|
2985
|
+
} };
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
return projection;
|
|
2989
|
+
}
|
|
2990
|
+
function createKeyedSubset(source, input, selectOrderedKeys) {
|
|
2991
|
+
const name = "derive.keyed.subset";
|
|
2992
|
+
assertKeyedProjection(source, `${name} source`);
|
|
2993
|
+
const selection = compileSelection(input, !isProjection(input) && !Array.isArray(input), selectOrderedKeys, normalizeOrderedKeys, name);
|
|
2994
|
+
const [projection] = defineProcessor({
|
|
2995
|
+
dependencies: [source, ...selection.projections],
|
|
2996
|
+
outputs: [{
|
|
2997
|
+
kind: "collection",
|
|
2998
|
+
equality: Object.is
|
|
2999
|
+
}],
|
|
3000
|
+
name,
|
|
3001
|
+
create: () => {
|
|
3002
|
+
const selected = selection.create();
|
|
3003
|
+
return { evaluate: (evaluation) => {
|
|
3004
|
+
const driver = evaluation.sources[0];
|
|
3005
|
+
const output = evaluation.outputs[0];
|
|
3006
|
+
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error(`${name} requires collection input and output.`);
|
|
3007
|
+
const keysChanged = selected.refresh(evaluation.sources);
|
|
3008
|
+
const { keys, requested } = selected.value;
|
|
3009
|
+
const change = driver.change;
|
|
3010
|
+
if (evaluation.reset || change?.kind === "reset") {
|
|
3011
|
+
const present = keys.filter((key) => driver.read.has(key));
|
|
3012
|
+
for (const key of present) output.output.set(key, driver.read.get(key));
|
|
3013
|
+
output.output.order(present);
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
let orderChanged = keysChanged;
|
|
3017
|
+
if (keysChanged) {
|
|
3018
|
+
for (const key of output.previous.ids()) if (!requested.has(key) || !driver.read.has(key)) output.output.remove(key);
|
|
3019
|
+
for (const key of keys) if (!output.previous.has(key) && driver.read.has(key)) output.output.set(key, driver.read.get(key));
|
|
3020
|
+
} else if (change) {
|
|
3021
|
+
for (const entry of change.removed) {
|
|
3022
|
+
if (!requested.has(entry.key)) continue;
|
|
3023
|
+
output.output.remove(entry.key);
|
|
3024
|
+
orderChanged = true;
|
|
3025
|
+
}
|
|
3026
|
+
for (const entry of change.added) {
|
|
3027
|
+
if (!requested.has(entry.key)) continue;
|
|
3028
|
+
output.output.set(entry.key, entry.after);
|
|
3029
|
+
orderChanged = true;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
if (change) {
|
|
3033
|
+
for (const entry of change.updated) if (requested.has(entry.key) && (!keysChanged || output.previous.has(entry.key))) output.output.set(entry.key, entry.after);
|
|
3034
|
+
}
|
|
3035
|
+
if (orderChanged) output.output.order(keys.filter((key) => driver.read.has(key)));
|
|
3036
|
+
} };
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
return projection;
|
|
3040
|
+
}
|
|
3041
|
+
//#endregion
|
|
2885
3042
|
//#region core/src/projection/keyed/composition.ts
|
|
2886
3043
|
const assertEquality = (equality, role) => {
|
|
2887
3044
|
if (equality !== void 0 && typeof equality !== "function") throw new TypeError(`${role} equality must be a function.`);
|
|
@@ -3157,50 +3314,59 @@ function createKeyedMerge(sources, options) {
|
|
|
3157
3314
|
});
|
|
3158
3315
|
return projection;
|
|
3159
3316
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3317
|
+
function createKeyedSingleton(sourceOrDependencies, compute, equality) {
|
|
3318
|
+
const name = "derive.keyed.singleton";
|
|
3319
|
+
const selection = isProjection(sourceOrDependencies) ? {
|
|
3320
|
+
kind: "scalar",
|
|
3321
|
+
source: sourceOrDependencies,
|
|
3322
|
+
keyOf: compute
|
|
3323
|
+
} : {
|
|
3324
|
+
kind: "named",
|
|
3325
|
+
compiled: compileProjectionDependencies(sourceOrDependencies, name),
|
|
3326
|
+
computeEntry: compute
|
|
3327
|
+
};
|
|
3328
|
+
if (selection.kind === "scalar" && outputKind(selection.source) !== "value") throw new TypeError(`${name} source must be a scalar projection.`);
|
|
3329
|
+
if (typeof compute !== "function") throw new TypeError(`${name} requires a ${selection.kind === "scalar" ? "key selector" : "compute callback"}.`);
|
|
3330
|
+
const outputEquality = assertEquality(equality, name);
|
|
3163
3331
|
const [projection] = defineProcessor({
|
|
3164
|
-
dependencies: [source],
|
|
3332
|
+
dependencies: selection.kind === "named" ? selection.compiled.projections : [selection.source],
|
|
3165
3333
|
outputs: [{
|
|
3166
3334
|
kind: "collection",
|
|
3167
|
-
equality
|
|
3335
|
+
equality: outputEquality
|
|
3168
3336
|
}],
|
|
3169
3337
|
create: () => ({ evaluate: (evaluation) => {
|
|
3170
|
-
const scalar = evaluation.sources[0];
|
|
3171
3338
|
const output = evaluation.outputs[0];
|
|
3172
|
-
if (
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3339
|
+
if (output.kind !== "collection") throw new Error(`${name} requires a collection output.`);
|
|
3340
|
+
let key;
|
|
3341
|
+
let value;
|
|
3342
|
+
if (selection.kind === "named") {
|
|
3343
|
+
const entry = selection.computeEntry(snapshotProjectionValues(selection.compiled.names, evaluation.sources));
|
|
3344
|
+
assertSynchronous(entry);
|
|
3345
|
+
if (entry !== void 0) {
|
|
3346
|
+
assertKeyedEntry(entry, name);
|
|
3347
|
+
key = entry[0];
|
|
3348
|
+
value = entry[1];
|
|
3349
|
+
}
|
|
3350
|
+
} else {
|
|
3351
|
+
const scalar = evaluation.sources[0];
|
|
3352
|
+
if (scalar?.kind !== "value") throw new Error(`${name} resolved a non-scalar input.`);
|
|
3353
|
+
value = scalar.value;
|
|
3354
|
+
if (value !== void 0) {
|
|
3355
|
+
key = selection.keyOf(value);
|
|
3356
|
+
assertSynchronous(key);
|
|
3357
|
+
if (typeof key !== "string") throw new TypeError(`${name} key selector must return a string.`);
|
|
3358
|
+
}
|
|
3177
3359
|
}
|
|
3178
|
-
const
|
|
3179
|
-
|
|
3180
|
-
if (
|
|
3181
|
-
for (const previousKey of previous) if (previousKey !== key) output.output.remove(previousKey);
|
|
3182
|
-
output.output.set(key, scalar.value);
|
|
3183
|
-
if (previous.length !== 1 || previous[0] !== key) output.output.order([key]);
|
|
3360
|
+
const previousKey = output.previous.ids()[0];
|
|
3361
|
+
if (previousKey !== void 0 && previousKey !== key) output.output.remove(previousKey);
|
|
3362
|
+
if (key !== void 0) output.output.set(key, value);
|
|
3184
3363
|
} }),
|
|
3185
|
-
name
|
|
3364
|
+
name
|
|
3186
3365
|
});
|
|
3187
3366
|
return projection;
|
|
3188
|
-
}
|
|
3367
|
+
}
|
|
3189
3368
|
//#endregion
|
|
3190
3369
|
//#region core/src/projection/derive/keyed.ts
|
|
3191
|
-
const compileOrderedKeys = (value, role) => {
|
|
3192
|
-
if (!Array.isArray(value)) throw new TypeError(`${role} must be an array of string keys.`);
|
|
3193
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3194
|
-
for (const key of value) {
|
|
3195
|
-
if (typeof key !== "string") throw new TypeError(`${role} must contain only string keys.`);
|
|
3196
|
-
if (seen.has(key)) throw new TypeError(`${role} must not contain duplicate keys.`);
|
|
3197
|
-
seen.add(key);
|
|
3198
|
-
}
|
|
3199
|
-
return {
|
|
3200
|
-
keys: value,
|
|
3201
|
-
requested: seen
|
|
3202
|
-
};
|
|
3203
|
-
};
|
|
3204
3370
|
function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
|
|
3205
3371
|
const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
|
|
3206
3372
|
const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
|
|
@@ -3305,44 +3471,6 @@ const createOrderedSnapshot = (source, name, make) => {
|
|
|
3305
3471
|
};
|
|
3306
3472
|
const createKeyedValues = (source) => createOrderedSnapshot(source, "derive.keyed.values", (_key, value) => value);
|
|
3307
3473
|
const createKeyedEntries = (source) => createOrderedSnapshot(source, "derive.keyed.entries", (key, value) => Object.freeze([key, value]));
|
|
3308
|
-
const createKeyedGet = (source, key, equality = Object.is) => {
|
|
3309
|
-
assertKeyedProjection(source, "derive.keyed.get source");
|
|
3310
|
-
if (outputKind(key) !== "value") throw new TypeError("derive.keyed.get key must be a scalar projection.");
|
|
3311
|
-
const [projection] = defineProcessor({
|
|
3312
|
-
dependencies: [source, key],
|
|
3313
|
-
outputs: [{
|
|
3314
|
-
kind: "value",
|
|
3315
|
-
equality
|
|
3316
|
-
}],
|
|
3317
|
-
create: () => {
|
|
3318
|
-
let sourceRevision = -1;
|
|
3319
|
-
let keyRevision = -1;
|
|
3320
|
-
let selected;
|
|
3321
|
-
return { evaluate: (evaluation) => {
|
|
3322
|
-
const collection = evaluation.sources[0];
|
|
3323
|
-
const selectedKey = evaluation.sources[1];
|
|
3324
|
-
const output = evaluation.outputs[0];
|
|
3325
|
-
if (collection.kind !== "collection" || selectedKey.kind !== "value" || output.kind !== "value") throw new Error("derive.keyed.get resolved invalid input/output kinds.");
|
|
3326
|
-
let affected = evaluation.reset;
|
|
3327
|
-
if (evaluation.reset || keyRevision !== selectedKey.revision) {
|
|
3328
|
-
const candidate = selectedKey.value;
|
|
3329
|
-
if (candidate !== void 0 && typeof candidate !== "string") throw new TypeError("derive.keyed.get key must resolve to a string or undefined.");
|
|
3330
|
-
selected = candidate;
|
|
3331
|
-
affected = true;
|
|
3332
|
-
} else if (sourceRevision !== collection.revision) {
|
|
3333
|
-
const change = collection.change;
|
|
3334
|
-
if (!change || change.kind === "reset") affected = true;
|
|
3335
|
-
else if (selected !== void 0) affected = collectionChangeTouchesKey(change, selected);
|
|
3336
|
-
}
|
|
3337
|
-
if (affected) output.output.set(selected === void 0 ? void 0 : collection.read.get(selected));
|
|
3338
|
-
sourceRevision = collection.revision;
|
|
3339
|
-
keyRevision = selectedKey.revision;
|
|
3340
|
-
} };
|
|
3341
|
-
},
|
|
3342
|
-
name: "derive.keyed.get"
|
|
3343
|
-
});
|
|
3344
|
-
return projection;
|
|
3345
|
-
};
|
|
3346
3474
|
function createKeyedFilter(source, dependenciesOrPredicate, maybePredicate) {
|
|
3347
3475
|
const dependencySpecs = typeof dependenciesOrPredicate === "function" ? void 0 : dependenciesOrPredicate;
|
|
3348
3476
|
const predicate = dependencySpecs === void 0 ? dependenciesOrPredicate : maybePredicate;
|
|
@@ -3377,101 +3505,6 @@ function createKeyedCompact(source, dependenciesOrSelect, selectOrEquality, mayb
|
|
|
3377
3505
|
}
|
|
3378
3506
|
});
|
|
3379
3507
|
}
|
|
3380
|
-
const createKeyedSubset = (source, orderedKeys) => {
|
|
3381
|
-
assertKeyedProjection(source, "Keyed subset source");
|
|
3382
|
-
const keyProjection = isProjection(orderedKeys) ? orderedKeys : void 0;
|
|
3383
|
-
if (keyProjection && outputKind(keyProjection) !== "value") throw new TypeError("Keyed subset ordered keys must be a value projection.");
|
|
3384
|
-
const staticSelection = keyProjection ? void 0 : (() => {
|
|
3385
|
-
const compiled = compileOrderedKeys(orderedKeys, "Keyed subset ordered keys");
|
|
3386
|
-
return {
|
|
3387
|
-
keys: Object.freeze([...compiled.keys]),
|
|
3388
|
-
requested: compiled.requested
|
|
3389
|
-
};
|
|
3390
|
-
})();
|
|
3391
|
-
const [projection] = defineProcessor({
|
|
3392
|
-
dependencies: keyProjection ? [source, keyProjection] : [source],
|
|
3393
|
-
outputs: [{
|
|
3394
|
-
kind: "collection",
|
|
3395
|
-
equality: Object.is
|
|
3396
|
-
}],
|
|
3397
|
-
create: () => {
|
|
3398
|
-
let keys = staticSelection?.keys;
|
|
3399
|
-
let requested = staticSelection?.requested;
|
|
3400
|
-
let keyRevision = -1;
|
|
3401
|
-
return {
|
|
3402
|
-
evaluate: (evaluation) => {
|
|
3403
|
-
const driver = evaluation.sources[0];
|
|
3404
|
-
const output = evaluation.outputs[0];
|
|
3405
|
-
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("derive.keyed.subset requires collection input and output.");
|
|
3406
|
-
let keysChanged = false;
|
|
3407
|
-
if (keyProjection) {
|
|
3408
|
-
const sourceContext = evaluation.sources[1];
|
|
3409
|
-
if (sourceContext?.kind !== "value") throw new TypeError("Keyed subset ordered keys resolved to a collection.");
|
|
3410
|
-
if (keys === void 0 || sourceContext.reset || keyRevision !== sourceContext.revision) {
|
|
3411
|
-
const compiled = compileOrderedKeys(sourceContext.value, "Keyed subset ordered keys");
|
|
3412
|
-
keys = compiled.keys;
|
|
3413
|
-
requested = compiled.requested;
|
|
3414
|
-
keyRevision = sourceContext.revision;
|
|
3415
|
-
keysChanged = true;
|
|
3416
|
-
}
|
|
3417
|
-
}
|
|
3418
|
-
if (!keys || !requested) throw new Error("Keyed subset keys were not initialized.");
|
|
3419
|
-
const activeKeys = keys;
|
|
3420
|
-
const activeRequested = requested;
|
|
3421
|
-
const orderedPresentKeys = () => activeKeys.filter((key) => driver.read.has(key));
|
|
3422
|
-
const rebuild = () => {
|
|
3423
|
-
const nextKeys = orderedPresentKeys();
|
|
3424
|
-
for (const key of nextKeys) output.output.set(key, driver.read.get(key));
|
|
3425
|
-
output.output.order(nextKeys);
|
|
3426
|
-
};
|
|
3427
|
-
const reconcileKeys = () => {
|
|
3428
|
-
const nextKeys = orderedPresentKeys();
|
|
3429
|
-
for (const key of output.previous.ids()) if (!activeRequested.has(key) || !driver.read.has(key)) output.output.remove(key);
|
|
3430
|
-
for (const key of nextKeys) if (!output.previous.has(key)) output.output.set(key, driver.read.get(key));
|
|
3431
|
-
output.output.order(nextKeys);
|
|
3432
|
-
};
|
|
3433
|
-
const change = driver.change;
|
|
3434
|
-
if (evaluation.reset || change?.kind === "reset") rebuild();
|
|
3435
|
-
else if (keysChanged) {
|
|
3436
|
-
reconcileKeys();
|
|
3437
|
-
if (change) for (const entry of change.updated) {
|
|
3438
|
-
const key = entry.key;
|
|
3439
|
-
if (activeRequested.has(key) && output.previous.has(key)) output.output.set(key, entry.after);
|
|
3440
|
-
}
|
|
3441
|
-
} else if (change) {
|
|
3442
|
-
let updateOrder = false;
|
|
3443
|
-
for (const entry of change.removed) {
|
|
3444
|
-
const key = entry.key;
|
|
3445
|
-
if (!activeRequested.has(key)) continue;
|
|
3446
|
-
output.output.remove(key);
|
|
3447
|
-
updateOrder = true;
|
|
3448
|
-
}
|
|
3449
|
-
for (const entry of change.added) {
|
|
3450
|
-
const key = entry.key;
|
|
3451
|
-
if (!activeRequested.has(key)) continue;
|
|
3452
|
-
output.output.set(key, entry.after);
|
|
3453
|
-
updateOrder = true;
|
|
3454
|
-
}
|
|
3455
|
-
for (const entry of change.updated) {
|
|
3456
|
-
const key = entry.key;
|
|
3457
|
-
if (activeRequested.has(key)) output.output.set(key, entry.after);
|
|
3458
|
-
}
|
|
3459
|
-
if (updateOrder) output.output.order(orderedPresentKeys());
|
|
3460
|
-
}
|
|
3461
|
-
},
|
|
3462
|
-
release: () => {
|
|
3463
|
-
if (keyProjection) {
|
|
3464
|
-
keys = void 0;
|
|
3465
|
-
requested = void 0;
|
|
3466
|
-
keyRevision = -1;
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
};
|
|
3470
|
-
},
|
|
3471
|
-
name: "derive.keyed.subset"
|
|
3472
|
-
});
|
|
3473
|
-
return projection;
|
|
3474
|
-
};
|
|
3475
3508
|
const normalizeGroups = (value) => {
|
|
3476
3509
|
const groups = typeof value === "string" ? [value] : value;
|
|
3477
3510
|
if (!Array.isArray(groups)) throw new TypeError("derive.keyed.groupBy selector must return a string or an array of strings.");
|
|
@@ -186,9 +186,9 @@ const readNamedDependencyEntries = (dependencies, label) => {
|
|
|
186
186
|
/** Creates the durable dependency value exposed to pure derive callbacks. */
|
|
187
187
|
const snapshotDependencyValue = (source) => source.kind === "value" ? source.value : snapshotCollectionView(source.read);
|
|
188
188
|
/** Builds the same readonly named value boundary for scalar and keyed pure computations. */
|
|
189
|
-
const snapshotProjectionValues = (names, sources) => {
|
|
189
|
+
const snapshotProjectionValues = (names, sources, offset = 0) => {
|
|
190
190
|
const values = Object.create(null);
|
|
191
|
-
for (let index = 0; index < names.length; index++) values[names[index]] = snapshotDependencyValue(sources[index]);
|
|
191
|
+
for (let index = 0; index < names.length; index++) values[names[index]] = snapshotDependencyValue(sources[offset + index]);
|
|
192
192
|
return Object.freeze(values);
|
|
193
193
|
};
|
|
194
194
|
/** Compiles the shared named Projection dependency declaration contract. */
|
|
@@ -186,9 +186,9 @@ const readNamedDependencyEntries = (dependencies, label) => {
|
|
|
186
186
|
/** Creates the durable dependency value exposed to pure derive callbacks. */
|
|
187
187
|
const snapshotDependencyValue = (source) => source.kind === "value" ? source.value : snapshotCollectionView(source.read);
|
|
188
188
|
/** Builds the same readonly named value boundary for scalar and keyed pure computations. */
|
|
189
|
-
const snapshotProjectionValues = (names, sources) => {
|
|
189
|
+
const snapshotProjectionValues = (names, sources, offset = 0) => {
|
|
190
190
|
const values = Object.create(null);
|
|
191
|
-
for (let index = 0; index < names.length; index++) values[names[index]] = snapshotDependencyValue(sources[index]);
|
|
191
|
+
for (let index = 0; index < names.length; index++) values[names[index]] = snapshotDependencyValue(sources[offset + index]);
|
|
192
192
|
return Object.freeze(values);
|
|
193
193
|
};
|
|
194
194
|
/** Compiles the shared named Projection dependency declaration contract. */
|
package/package.json
CHANGED
|
@@ -44,8 +44,8 @@ Follow these choices unless the requirement says otherwise:
|
|
|
44
44
|
3. Use `read` for one synchronous document read and `select` for a document `Readable` with dynamic dependency tracking.
|
|
45
45
|
4. Use `observe` to bring a document/readable/external source into the projection graph.
|
|
46
46
|
5. Use named-object `derive` for pure aggregate values.
|
|
47
|
-
6. Use `derive.keyed.from` to turn an ordered scalar/static collection into a keyed projection. Use `derive.keyed.
|
|
48
|
-
7. Use `derive.keyed` when one keyed source owns output membership/order.
|
|
47
|
+
6. Use `derive.keyed.from` to turn an ordered scalar/static collection into a keyed projection. Use `derive.keyed.singleton({ inputs }, computeEntry)` for zero or one computed member (`[key, value]` or `undefined`), and `derive.keyed.fromEntries({ inputs }, compute)` for a complete keyed result of arbitrary size; use `flatMap` when individual keyed parents must recompute independently. Use `derive.keyed.merge` to compose multiple keyed projections; for base + sparse overrides, use `{ conflict: 'last' }`.
|
|
48
|
+
7. Use `derive.keyed` when one keyed source owns output membership/order. Use `get(source, dependencies, selectKey)` or `subset(source, dependencies, selectOrderedKeys)` when named projections determine which existing members to read; static keys/key arrays and existing scalar selection projections also work. Prefer the keyed family's built-in primitives over hand-written incremental collection patches.
|
|
49
49
|
8. Express per-output-key joins through `{ source }` for same-key lookup, `{ source, key }` for one mapped key, or `{ source, keys }` for several mapped keys. Let the Runtime own binding and reverse invalidation; do not maintain an application `Map` just to route dependency changes.
|
|
50
50
|
9. Use `runtime.items(keyedProjection)` or `scope.items(...)` for stable per-membership item `Readable`s.
|
|
51
51
|
10. Use `incremental.keyed` for independent retained state per driver key. Use `incremental.collection` or `incremental.group` only when direct incremental output patching or shared retained state is required.
|
|
@@ -167,9 +167,26 @@ Produces ordered readonly `[key,value]` tuples. Value-only updates publish corre
|
|
|
167
167
|
|
|
168
168
|
```ts
|
|
169
169
|
const active = derive.keyed.get(rows, activeId);
|
|
170
|
+
const fixed = derive.keyed.get(rows, rowId);
|
|
171
|
+
const selected = derive.keyed.get(rows, { selection }, ({ selection }) => selection.activeRowId);
|
|
170
172
|
```
|
|
171
173
|
|
|
172
|
-
`activeId`
|
|
174
|
+
`activeId` may be a static `K | undefined` or scalar `Projection<K | undefined>`.
|
|
175
|
+
Named form accepts ordinary projection dependencies, including `{}`, and a synchronous
|
|
176
|
+
callback returning `K | undefined`. The source owns K/V; callback results, key projections
|
|
177
|
+
and equality must agree with those domains. The result is `Projection<V | undefined>`.
|
|
178
|
+
An absent selected key remains requested for later add; undefined selects no key.
|
|
179
|
+
Optional result equality defaults to `Object.is` and retains equivalent references.
|
|
180
|
+
|
|
181
|
+
Named selectors run on initialization, declared dependency changes or recovery. Source
|
|
182
|
+
updates inspect deltas for the selected key; source-only reorder is ignored. Unrelated
|
|
183
|
+
updates can wake the processor but do not rerun selectors or change output. Keyed named
|
|
184
|
+
dependencies are whole snapshots: explicitly declaring the queried source as a named
|
|
185
|
+
dependency also makes its changes relevant to the selector. Public Runtime reads and
|
|
186
|
+
driver-relative dependency specs are not allowed inside this protocol. Callback, key
|
|
187
|
+
validation and equality failures follow normal recovery without partial publication.
|
|
188
|
+
Definitions are lazy, scoped and isolated per runtime; current batch reads and deferred
|
|
189
|
+
notifications use the ordinary runtime contract.
|
|
173
190
|
|
|
174
191
|
Use this for active entity, editor target, selected record, or current-cell lookups.
|
|
175
192
|
|
|
@@ -242,11 +259,32 @@ Value-only source changes only recompute affected keys. Structural or source-ord
|
|
|
242
259
|
|
|
243
260
|
```ts
|
|
244
261
|
const visibleRows = derive.keyed.subset(rows, orderedVisibleIds);
|
|
262
|
+
const selectedRows = derive.keyed.subset(rows, { selection }, ({ selection }) => selection.rowIds);
|
|
245
263
|
```
|
|
246
264
|
|
|
247
265
|
Output order is `orderedVisibleIds ∩ source membership`. Missing requested ids remain latent and appear later if the source adds them. Duplicate requested keys are invalid. Values are source values and are not re-derived.
|
|
248
266
|
|
|
249
|
-
|
|
267
|
+
The second argument may be a static readonly key array or a scalar key-array projection.
|
|
268
|
+
Named form accepts ordinary projection dependencies (including `{}`) and a synchronous
|
|
269
|
+
callback returning `readonly K[]`. The source owns K/V; selection cannot widen the key
|
|
270
|
+
domain. `[]` is empty; undefined, malformed arrays and duplicate keys (even absent ones)
|
|
271
|
+
are errors. Static arrays are snapshotted at definition time; dynamic arrays are captured
|
|
272
|
+
when accepted. Equal sequences reuse request state, regardless of array identity.
|
|
273
|
+
|
|
274
|
+
Dependency changes rerun the selector. Source-only updates filter the existing request;
|
|
275
|
+
they do not rerun selectors. Selected values update with the source's original identity,
|
|
276
|
+
missing keys join when added, and source-only reorder is ignored. Subset has no value
|
|
277
|
+
or keys equality option. A selection change and value change in one evaluation both
|
|
278
|
+
take effect, even when the key sequence remains equal. Reset/recovery, scope disposal,
|
|
279
|
+
runtime isolation and notification-boundary item lifecycles follow the normal contract.
|
|
280
|
+
|
|
281
|
+
Each get/subset uses one processor and one output. Source-delta filtering can cost
|
|
282
|
+
O(delta size + selection dependency count); the processor can wake for unrelated source
|
|
283
|
+
changes. It does not scan all source members. Structural changes/reset may scan requested
|
|
284
|
+
keys and prior output. Selection changes pay callback, normalization and dependency
|
|
285
|
+
snapshot costs. Named keyed dependencies are whole snapshots; including the queried
|
|
286
|
+
source explicitly can rerun the selector on its changes. Use `get`/`subset` to select
|
|
287
|
+
existing members and `singleton`/`fromEntries` to construct new keyed results.
|
|
250
288
|
|
|
251
289
|
### `filter`
|
|
252
290
|
|
|
@@ -280,9 +318,41 @@ Use this for domain-neutral reverse indexing such as record→sections, node→e
|
|
|
280
318
|
|
|
281
319
|
```ts
|
|
282
320
|
const activeEntity = derive.keyed.singleton(activeRecord, record => record.id);
|
|
321
|
+
|
|
322
|
+
const preview = derive.keyed.singleton(
|
|
323
|
+
{ record: activeRecord, enabled: previewEnabled },
|
|
324
|
+
({ record, enabled }) => (!enabled || record === undefined ? undefined : [record.id, record])
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const fixed = derive.keyed.singleton({}, () => ['fixed', 1]);
|
|
283
328
|
```
|
|
284
329
|
|
|
285
|
-
|
|
330
|
+
Scalar form accepts `Projection<V | undefined>` and `keyOf(value)`. Undefined sources
|
|
331
|
+
are empty and skip `keyOf`; present values are preserved as one member. Named form
|
|
332
|
+
accepts ordinary `derive` dependencies, including `{}`, and a synchronous callback
|
|
333
|
+
returning a readonly `[K, V]` tuple or `undefined`. `undefined` means no member;
|
|
334
|
+
`[key, undefined]` is a present member. Keys must be strings. `null`, `false`, `[]`,
|
|
335
|
+
malformed tuples and thenables are errors. Formal order is empty or the sole key.
|
|
336
|
+
|
|
337
|
+
Optional equality defaults to `Object.is` and compares same-key values, retaining equal
|
|
338
|
+
references. Changing the key removes the old member and adds the new one regardless of
|
|
339
|
+
value equality. Typed equality participates in inference and must accept every emitted
|
|
340
|
+
variant; inline tuples, optional branches and union values do not require an output
|
|
341
|
+
projection annotation. Use a declared value type or `as const` when object discriminants
|
|
342
|
+
would otherwise widen under normal TypeScript rules.
|
|
343
|
+
|
|
344
|
+
Dependencies are captured at definition time; callback values are readonly. Keyed named
|
|
345
|
+
inputs are whole durable map snapshots. For a precise active-entry dependency, compose
|
|
346
|
+
`derive.keyed.get(records, activeId)` first; driver-relative specs do not belong here.
|
|
347
|
+
Public Runtime reads in callbacks remain forbidden. Callback, validation and equality
|
|
348
|
+
failures install no partial output and follow normal processor recovery. Definitions
|
|
349
|
+
are lazy, isolated per runtime and compatible with scopes. Current reads can evaluate
|
|
350
|
+
multiple times in a batch; item lifecycles end only at the notification boundary.
|
|
351
|
+
|
|
352
|
+
Both forms use one processor and one collection output. Singleton's member coordination
|
|
353
|
+
is O(1), plus callback and dependency snapshot costs. Use this primitive for optional
|
|
354
|
+
previews, selections and active members instead of manually building zero-or-one arrays
|
|
355
|
+
or staging collection patches. Use `fromEntries` when more than one member is possible.
|
|
286
356
|
|
|
287
357
|
## Runtime read/select
|
|
288
358
|
|
|
@@ -331,7 +331,7 @@ The keyed family:
|
|
|
331
331
|
| `merge` | Multiple keyed projections → union with explicit conflict policy |
|
|
332
332
|
| `flatMap` | Ordered pure one-to-many keyed expansion |
|
|
333
333
|
| `groupBy` | One-to-many reverse index |
|
|
334
|
-
| `singleton` | Optional scalar → zero-or-one keyed
|
|
334
|
+
| `singleton` | Optional scalar or named dependencies → zero-or-one keyed result |
|
|
335
335
|
| `subset` | Externally ordered keyed subset |
|
|
336
336
|
| `filter` | Predicate-controlled membership preserving source values |
|
|
337
337
|
| `compact` | Map values while dropping `undefined` results |
|
|
@@ -345,7 +345,9 @@ derive.keyed(source, dependencies, (value, key, dependencies) => result, equalit
|
|
|
345
345
|
derive.keyed.keys(source): Projection<readonly K[]>
|
|
346
346
|
derive.keyed.values(source): Projection<readonly V[]>
|
|
347
347
|
derive.keyed.entries(source): Projection<readonly (readonly [K,V])[]>
|
|
348
|
-
derive.keyed.get(source,
|
|
348
|
+
derive.keyed.get(source, keyOrProjection, equality?): Projection<V | undefined>
|
|
349
|
+
derive.keyed.get(source, dependencies, selectKey, equality?): Projection<V | undefined>
|
|
350
|
+
// selectKey(readonlyNamedValues) returns Synchronous<K | undefined>
|
|
349
351
|
derive.keyed.from(source: Projection<readonly V[]>, keyOf, equality?): KeyedProjection<K,V>
|
|
350
352
|
derive.keyed.from(source: readonly V[], keyOf, equality?): KeyedProjection<K,V>
|
|
351
353
|
derive.keyed.fromEntries(dependencies, compute, equality?): KeyedProjection<K,V>
|
|
@@ -354,6 +356,8 @@ derive.keyed.fromEntries(dependencies, compute, equality?): KeyedProjection<K,V>
|
|
|
354
356
|
derive.keyed.merge(sources, { conflict: 'error' | 'first' | 'last', equality? }): KeyedProjection<K,V>
|
|
355
357
|
derive.keyed.merge(sources, { conflict: 'resolve', resolve, equality? }): KeyedProjection<K,V>
|
|
356
358
|
derive.keyed.subset(source, orderedKeysOrProjection): KeyedProjection<K,V>
|
|
359
|
+
derive.keyed.subset(source, dependencies, selectOrderedKeys): KeyedProjection<K,V>
|
|
360
|
+
// selectOrderedKeys(readonlyNamedValues) returns Synchronous<readonly K[]>
|
|
357
361
|
derive.keyed.filter(source, predicate): KeyedProjection<K,V>
|
|
358
362
|
derive.keyed.filter(source, dependencies, predicate): KeyedProjection<K,V>
|
|
359
363
|
derive.keyed.compact(source, selector, equality?): KeyedProjection<K,T>
|
|
@@ -364,12 +368,18 @@ derive.keyed.flatMap(source, dependencies, select, equality?): KeyedProjection<O
|
|
|
364
368
|
derive.keyed.groupBy(source, selector): KeyedProjection<GroupKey, readonly K[]>
|
|
365
369
|
derive.keyed.groupBy(source, dependencies, selector): KeyedProjection<GroupKey, readonly K[]>
|
|
366
370
|
derive.keyed.singleton(sourceProjection, keyOf, equality?): KeyedProjection<K,V>
|
|
371
|
+
derive.keyed.singleton(dependencies, computeEntry, equality?): KeyedProjection<K,V>
|
|
372
|
+
// computeEntry(readonlyNamedValues) returns Synchronous<readonly [K,V] | undefined>
|
|
367
373
|
```
|
|
368
374
|
|
|
375
|
+
`get` and `subset` accept named ordinary projection dependencies, including `{}`, to compute one optional key or an ordered key array. Source defines K/V; selectors and equality cannot widen them. Source-only updates filter deltas without rerunning selectors. Missing keys remain requested. Subset preserves source values and requested order, rejects duplicate keys, and has no equality option. Equal key sequences reuse request state. Get's optional result equality defaults to `Object.is`. Callback/validation/equality failure follows normal recovery without partial publication. Processors may inspect unrelated source deltas; this is not scheduler-level key subscription. A keyed named dependency is a whole snapshot, including when it is also the queried source. See [Precise scalar lookup](projections.md#precise-scalar-lookup) and [Subset](projections.md#subset).
|
|
376
|
+
|
|
369
377
|
`from` uses `keyOf(value)` as member identity and preserves the input array's formal order. Duplicate keys are processor errors; there is no silent first/last overwrite. Static arrays are shallow-snapshotted at definition creation while `keyOf` remains lazy. For a scalar array projection, each scalar publication is scanned because the source has no per-entry delta. Equality defaults to `Object.is`; an equality-equivalent value under the same key retains the previous published value identity.
|
|
370
378
|
|
|
371
379
|
`fromEntries` accepts the same named projection object as `derive`, including `{}` and keyed projections. Single inputs use `{ source }`; direct-source, static-array and driver-relative dependency specs are not overloads. The callback receives readonly named values and returns the complete ordered tuple array synchronously. Empty arrays mean no members; missing keys are removed; present `undefined` is valid. Duplicate string keys, malformed tuples and callback/equality failures follow processor error/recovery rules without partial installation. Per-entry equality defaults to `Object.is` and retains equivalent value references without suppressing membership or order. Computation scans the complete old/new result, while downstream publication is incremental. A keyed dependency is a whole collection, not automatically tracked by `get`; compose `derive.keyed.get` for precise scalar lookup. See [Projections](projections.md#keyed-results-from-named-dependencies) for lifecycle, cost and examples.
|
|
372
380
|
|
|
381
|
+
`singleton` returns at most one member. Scalar form accepts `Projection<V | undefined>`, omits undefined sources and preserves the source value under `keyOf(value)`. Named form uses ordinary `derive` dependencies (including `{}`), returning `[key, value]` or `undefined`; `[key, undefined]` is present. Keys are strings; empty arrays, null, false, malformed tuples and thenables are rejected. Typed equality participates in inference and compares same-key values only, preserving equivalent references. Key changes remain membership changes. Both forms share normal processor recovery and notification-boundary item lifecycles. Keyed named inputs are whole snapshots; compose `derive.keyed.get` for precise lookup. See [Singleton](projections.md#singleton) for examples and costs.
|
|
382
|
+
|
|
373
383
|
`merge` has union membership and requires an explicit conflict policy. `first` and `last` use source-list priority. `error` rejects overlapping keys. `resolve` calls `resolve(contributions, key)` only for keys with two or more current contributions; contributions are ordered by source priority and have `{ sourceIndex, value }`. A single contribution passes through unchanged. The source list is fixed at definition time.
|
|
374
384
|
|
|
375
385
|
Merged formal order is always `stableUnique(S0.ids() ++ S1.ids() ++ ... ++ Sn.ids())`, independent of which source supplies the effective value. Therefore `derive.keyed.merge([base, overrides], { conflict: 'last' })` is the standard base + sparse-override composition: override values win for shared keys without moving their base positions; override-only keys are included after earlier-source first occurrences. Removing an override does not end the merged membership lifecycle while another source still contains the key.
|
|
@@ -323,10 +323,31 @@ Shared keys use override values while keeping the first formal position from `ba
|
|
|
323
323
|
```ts
|
|
324
324
|
const activeId = input<RowId | undefined>(undefined);
|
|
325
325
|
const activeRow = derive.keyed.get(rows, activeId);
|
|
326
|
+
const selectedRow = derive.keyed.get(rows, { selection }, ({ selection }) => selection.activeRowId);
|
|
326
327
|
```
|
|
327
328
|
|
|
328
329
|
The binding remains meaningful while the selected key is absent and reacts if that key appears later.
|
|
329
330
|
|
|
331
|
+
Use a static key directly for a fixed lookup. Use named dependencies when an existing
|
|
332
|
+
selection projection owns the key; an intermediate scalar derive is unnecessary.
|
|
333
|
+
|
|
334
|
+
## Ordered selected members
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
const selectedNodes = derive.keyed.subset(
|
|
338
|
+
graph.node,
|
|
339
|
+
{ selection },
|
|
340
|
+
({ selection }) => selection.nodeIds
|
|
341
|
+
);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Output follows `selection.nodeIds` order, including requested members that appear later
|
|
345
|
+
in the source. Selected source values pass through by identity. Source-only updates do
|
|
346
|
+
not rerun the selector; a new selection with equal keys does not produce a fake change.
|
|
347
|
+
Use `[]` to select nothing and avoid duplicate keys. Keep source out of named dependencies
|
|
348
|
+
unless computing the keys actually needs the entire source collection. The processor can
|
|
349
|
+
still inspect unrelated source deltas; it does not rebuild the whole subset for them.
|
|
350
|
+
|
|
330
351
|
## Reverse index / groupBy
|
|
331
352
|
|
|
332
353
|
```ts
|
|
@@ -337,13 +358,24 @@ Output is keyed by section id; each value is ordered source record ids. Use this
|
|
|
337
358
|
|
|
338
359
|
Group keys follow first appearance in the source's formal order. If one source record returns several groups, those groups use the selector's returned order for that record when they first appear together.
|
|
339
360
|
|
|
340
|
-
##
|
|
361
|
+
## Zero or one keyed member
|
|
341
362
|
|
|
342
363
|
```ts
|
|
343
364
|
const activeRecordCollection = derive.keyed.singleton(activeRecord, record => record.id);
|
|
365
|
+
|
|
366
|
+
const preview = derive.keyed.singleton(
|
|
367
|
+
{ record: activeRecord, enabled: previewEnabled },
|
|
368
|
+
({ record, enabled }) => (!enabled || record === undefined ? undefined : [record.id, record])
|
|
369
|
+
);
|
|
344
370
|
```
|
|
345
371
|
|
|
346
|
-
|
|
372
|
+
Scalar form turns an undefined source into empty membership. Named form returns a
|
|
373
|
+
tuple for a present member or `undefined` for no member; `[key, undefined]` is present.
|
|
374
|
+
Use the named form when several projections determine the member. Pass a value equality
|
|
375
|
+
callback when constructing equivalent objects; it preserves same-key value references
|
|
376
|
+
but cannot suppress a key change. For a precise dynamic input, obtain `activeRecord`
|
|
377
|
+
with `derive.keyed.get(records, activeId)`. Do not wrap dependencies in an intermediate
|
|
378
|
+
scalar derive or use `fromEntries` solely to wrap one tuple in an array.
|
|
347
379
|
|
|
348
380
|
## Stable keyed item Readables
|
|
349
381
|
|