fyflow-orchestration 0.9.0 → 0.9.1
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/AGENTS.md +12 -1
- package/README.md +1 -1
- package/dist/browser/chunks/{chunk-3U4UZC34.js → chunk-I3KDEOLB.js} +108 -16
- package/dist/browser/index.js +3 -1
- package/dist/browser/scheduler/index.js +1 -1
- package/dist/node/chunks/{chunk-3U4UZC34.js → chunk-I3KDEOLB.js} +108 -16
- package/dist/node/index.js +3 -1
- package/dist/node/scheduler/index.js +1 -1
- package/dist/types/src/definition/regions.d.ts +12 -0
- package/dist/types/src/driver/driver.d.ts +25 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -477,6 +477,16 @@ once per item* is a fact about your document rather than about your run. **Measu
|
|
|
477
477
|
obstacle** — a size measurement is about 0.03% of what it would warn about at arity 1000.
|
|
478
478
|
`spec/flow-definition.md` section 3 carries the whole verdict and the measurements.
|
|
479
479
|
|
|
480
|
+
**Finding what a reference names costs its ANSWER** — from 0.9.1
|
|
481
|
+
([ADR 0124](doc/decisions/0124-a-reference-read-costs-its-answer.md), `spec/flow-definition.md` v0.46). A branch task
|
|
482
|
+
may read any earlier task of its own `map` instance by id — one step back or several, where `"."` reaches only its
|
|
483
|
+
predecessor — and the read takes that one output, however many instances the map has: the driver files a node's outputs
|
|
484
|
+
by the branch-path prefix its readers share. In 0.9.0 every read searched every output of the node it names, so such a
|
|
485
|
+
map cost O(items²) (the driver alone at 8,000 items: 22 s in 0.9.0, 0.22 s from 0.9.1, node 24). **A read never costs
|
|
486
|
+
less than its answer**: `["n"]` from a reader outside every region `n` is in — a SIBLING map's reader — is handed every
|
|
487
|
+
output of `n`, so two maps of `n` and `m` instances compose `n·m` entries. That is the answer's price, the cliff above by
|
|
488
|
+
another road, and the remedy is the same.
|
|
489
|
+
|
|
480
490
|
### `"$payload"` — what reached the RUN, from any node
|
|
481
491
|
|
|
482
492
|
**`"$payload"` names the value the run was STARTED with** — `start(flow, payload)`'s `payload` — on any node that may
|
|
@@ -1743,7 +1753,8 @@ need the state; step only where you hold a short one. Both return exactly what t
|
|
|
1743
1753
|
**The driver's own work per settlement does not grow with the run either** — `RN-R13` v0.30, from 0.9.0
|
|
1744
1754
|
([ADR 0121](doc/decisions/0121-the-driver-keeps-a-per-task-view-of-its-log.md)): every question it asks of its log about
|
|
1745
1755
|
ONE task — a settlement's effective outcome, whether a task is parked, what is in flight, what a `needs` can reach — reads
|
|
1746
|
-
that task's records alone
|
|
1756
|
+
that task's records alone (and from 0.9.1 a `needs` read takes the outputs it is handed rather than searching its node's —
|
|
1757
|
+
`RN-R13` v0.32, [ADR 0124](doc/decisions/0124-a-reference-read-costs-its-answer.md)), where 0.8.0 scanned the whole log (a 16,000-item `map` driven on a scheduler double: 999 ms in
|
|
1747
1758
|
0.8.0, 229 ms from 0.9.0, node 24). `snapshot()`, `foldInputs()` and `checkpoint()` still hand you a copy of the whole run,
|
|
1748
1759
|
O(run) a call: read them when you need the run, not after every event.
|
|
1749
1760
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ worker returned, routing can **fan out** into parallel branches, and a **join**
|
|
|
17
17
|
The scheduler runs tasks; this library decides **which task runs next**. `fyflow-scheduler` has no
|
|
18
18
|
dependency graph of its own, so sequence, decision, fan-out and join live here.
|
|
19
19
|
|
|
20
|
-
Published on npm as `fyflow-orchestration` and on JSR as `@fyflow/orchestration`. The manifests read `0.9.
|
|
20
|
+
Published on npm as `fyflow-orchestration` and on JSR as `@fyflow/orchestration`. The manifests read `0.9.1`.
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
@@ -136,14 +136,14 @@ function sharedRegionDepth(regions, a, b) {
|
|
|
136
136
|
}
|
|
137
137
|
function outputIsVisible(regions, readerId, readerPath, targetId, targetPath) {
|
|
138
138
|
const depth = sharedRegionDepth(regions, readerId, targetId);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
139
|
+
const key = visibilityKey(readerPath, depth);
|
|
140
|
+
return key !== null && key === visibilityKey(targetPath, depth);
|
|
141
|
+
}
|
|
142
|
+
function visibilityKey(path, depth) {
|
|
143
|
+
if (depth === 0) return "";
|
|
144
|
+
const segs = path === "-" ? [] : path.split(".");
|
|
145
|
+
if (segs.length < depth) return null;
|
|
146
|
+
return segs.length === depth ? path : segs.slice(0, depth).join(".");
|
|
147
147
|
}
|
|
148
148
|
function branchIndexOf(regions, nodeId) {
|
|
149
149
|
const r = innermostRegionOf(regions, nodeId);
|
|
@@ -2993,6 +2993,18 @@ function describeElement(e) {
|
|
|
2993
2993
|
}
|
|
2994
2994
|
return s.length > 160 ? `${s.slice(0, 157)}...` : s;
|
|
2995
2995
|
}
|
|
2996
|
+
var NO_OUTPUTS = Object.freeze([]);
|
|
2997
|
+
function groupPosition(group, path) {
|
|
2998
|
+
let lo = 0;
|
|
2999
|
+
let hi = group.length;
|
|
3000
|
+
if (hi === 0 || compareBranchPaths(group[hi - 1].path, path) < 0) return hi;
|
|
3001
|
+
while (lo < hi) {
|
|
3002
|
+
const mid = lo + hi >>> 1;
|
|
3003
|
+
if (compareBranchPaths(group[mid].path, path) < 0) lo = mid + 1;
|
|
3004
|
+
else hi = mid;
|
|
3005
|
+
}
|
|
3006
|
+
return lo;
|
|
3007
|
+
}
|
|
2996
3008
|
var UndispatchedTask = class extends Error {
|
|
2997
3009
|
constructor(message, options) {
|
|
2998
3010
|
super(message, options);
|
|
@@ -5128,7 +5140,7 @@ var Driver = class {
|
|
|
5128
5140
|
* node or an edge that declares a `needs` asks, and a run that declares none never builds it.
|
|
5129
5141
|
*/
|
|
5130
5142
|
outputs() {
|
|
5131
|
-
const c = this.outputsCache ??= { at: 0, byNode: /* @__PURE__ */ new Map(), tasks: /* @__PURE__ */ new Map(), orders: /* @__PURE__ */ new Map() };
|
|
5143
|
+
const c = this.outputsCache ??= { at: 0, byNode: /* @__PURE__ */ new Map(), tasks: /* @__PURE__ */ new Map(), orders: /* @__PURE__ */ new Map(), index: /* @__PURE__ */ new Map(), depths: /* @__PURE__ */ new Map(), examined: 0 };
|
|
5132
5144
|
for (; c.at < this.log.length; c.at += 1) {
|
|
5133
5145
|
const r = this.log[c.at];
|
|
5134
5146
|
let t = c.tasks.get(r.taskId);
|
|
@@ -5171,6 +5183,7 @@ var Driver = class {
|
|
|
5171
5183
|
const at = list.indexOf(t.entry);
|
|
5172
5184
|
list.splice(at, 1);
|
|
5173
5185
|
orders.splice(at, 1);
|
|
5186
|
+
this.reindex(c, t.nodeId, t.entry, null);
|
|
5174
5187
|
t.entry = null;
|
|
5175
5188
|
if (list.length === 0) {
|
|
5176
5189
|
c.byNode.delete(t.nodeId);
|
|
@@ -5181,10 +5194,12 @@ var Driver = class {
|
|
|
5181
5194
|
const entry = { path: t.path, result: eff.result };
|
|
5182
5195
|
if (t.entry !== null && list !== void 0) {
|
|
5183
5196
|
list[list.indexOf(t.entry)] = entry;
|
|
5197
|
+
this.reindex(c, t.nodeId, t.entry, entry);
|
|
5184
5198
|
t.entry = entry;
|
|
5185
5199
|
return;
|
|
5186
5200
|
}
|
|
5187
5201
|
t.entry = entry;
|
|
5202
|
+
this.reindex(c, t.nodeId, null, entry);
|
|
5188
5203
|
if (list === void 0 || orders === void 0) {
|
|
5189
5204
|
c.byNode.set(t.nodeId, [entry]);
|
|
5190
5205
|
c.orders.set(t.nodeId, [t.order]);
|
|
@@ -5195,6 +5210,81 @@ var Driver = class {
|
|
|
5195
5210
|
list.splice(i, 0, entry);
|
|
5196
5211
|
orders.splice(i, 0, t.order);
|
|
5197
5212
|
}
|
|
5213
|
+
/**
|
|
5214
|
+
* **One entry's change carried into every index built for its node** — W199, ADR 0124: called by `place` at each of
|
|
5215
|
+
* its cases (a first entry, an append, an insertion at a first-record position, a replacement in place, a removal), so
|
|
5216
|
+
* the index is caught up exactly when `byNode` is and can say nothing `byNode` does not. `was` is the entry leaving
|
|
5217
|
+
* (`null` for an insertion), `now` the one arriving (`null` for a removal); a replacement keeps its path, so its
|
|
5218
|
+
* position. A group is ordered by PATH, not by first record, so where `byNode` inserted mid-list the group finds its own
|
|
5219
|
+
* place by a binary search. An entry with fewer than `depth` segments is visible to no reader at that depth and is filed
|
|
5220
|
+
* nowhere.
|
|
5221
|
+
*/
|
|
5222
|
+
reindex(c, nodeId, was, now) {
|
|
5223
|
+
const byDepth = c.index.get(nodeId);
|
|
5224
|
+
if (byDepth === void 0) return;
|
|
5225
|
+
const path = (now ?? was).path;
|
|
5226
|
+
for (const [depth, groups] of byDepth) {
|
|
5227
|
+
const key = visibilityKey(path, depth);
|
|
5228
|
+
if (key === null) continue;
|
|
5229
|
+
c.examined += 1;
|
|
5230
|
+
const group = groups.get(key);
|
|
5231
|
+
if (was !== null) {
|
|
5232
|
+
const at = groupPosition(group, path);
|
|
5233
|
+
if (now !== null) group[at] = now;
|
|
5234
|
+
else if (group.length === 1) groups.delete(key);
|
|
5235
|
+
else group.splice(at, 1);
|
|
5236
|
+
} else if (group === void 0) {
|
|
5237
|
+
groups.set(key, [now]);
|
|
5238
|
+
} else {
|
|
5239
|
+
group.splice(groupPosition(group, path), 0, now);
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
/**
|
|
5244
|
+
* **A node's outputs grouped for readers at `depth`** — built on the first read at that depth from the node's current
|
|
5245
|
+
* entries (each one examined), each group sorted ONCE, and from then on kept by `reindex`.
|
|
5246
|
+
*/
|
|
5247
|
+
groupsOf(c, nodeId, depth) {
|
|
5248
|
+
let byDepth = c.index.get(nodeId);
|
|
5249
|
+
if (byDepth === void 0) c.index.set(nodeId, byDepth = /* @__PURE__ */ new Map());
|
|
5250
|
+
let groups = byDepth.get(depth);
|
|
5251
|
+
if (groups !== void 0) return groups;
|
|
5252
|
+
groups = /* @__PURE__ */ new Map();
|
|
5253
|
+
for (const e of c.byNode.get(nodeId) ?? NO_OUTPUTS) {
|
|
5254
|
+
c.examined += 1;
|
|
5255
|
+
const key = visibilityKey(e.path, depth);
|
|
5256
|
+
if (key === null) continue;
|
|
5257
|
+
const group = groups.get(key);
|
|
5258
|
+
if (group === void 0) groups.set(key, [e]);
|
|
5259
|
+
else group.push(e);
|
|
5260
|
+
}
|
|
5261
|
+
for (const group of groups.values()) if (group.length > 1) group.sort((x, y) => compareBranchPaths(x.path, y.path));
|
|
5262
|
+
byDepth.set(depth, groups);
|
|
5263
|
+
return groups;
|
|
5264
|
+
}
|
|
5265
|
+
/** The depth `reader` and `target` share — `sharedRegionDepth`, a graph fact, asked once per pair. */
|
|
5266
|
+
depthOf(c, reader, target) {
|
|
5267
|
+
let byTarget = c.depths.get(reader);
|
|
5268
|
+
if (byTarget === void 0) c.depths.set(reader, byTarget = /* @__PURE__ */ new Map());
|
|
5269
|
+
let depth = byTarget.get(target);
|
|
5270
|
+
if (depth === void 0) byTarget.set(target, depth = sharedRegionDepth(this.regions, reader, target));
|
|
5271
|
+
return depth;
|
|
5272
|
+
}
|
|
5273
|
+
/**
|
|
5274
|
+
* **The outputs of `nodeId` visible to `atNodeId` at `here`, in `compareBranchPaths` order** — W199, ADR 0124: ADR
|
|
5275
|
+
* 0044's predicate answered by a LOOKUP. The predicate is `visibilityKey(here, d) === visibilityKey(path, d)` at the depth
|
|
5276
|
+
* `d` the two share (`outputIsVisible`), so the visible outputs are exactly the group filed under the reader's key: what
|
|
5277
|
+
* the filter over every output and the sort of its survivors returned until W199, found in the cost of its answer.
|
|
5278
|
+
* **The index's own list, never handed out** — the caller maps it to copies.
|
|
5279
|
+
*/
|
|
5280
|
+
visibleOutputs(atNodeId, here, nodeId) {
|
|
5281
|
+
this.outputs();
|
|
5282
|
+
const c = this.outputsCache;
|
|
5283
|
+
const depth = this.depthOf(c, atNodeId, nodeId);
|
|
5284
|
+
const key = visibilityKey(here, depth);
|
|
5285
|
+
if (key === null) return NO_OUTPUTS;
|
|
5286
|
+
return this.groupsOf(c, nodeId, depth).get(key) ?? NO_OUTPUTS;
|
|
5287
|
+
}
|
|
5198
5288
|
/**
|
|
5199
5289
|
* The projection above, CAUGHT UP to `at` — `RN-R13` v0.30, ADR 0121 (W195). **Not run state and not a second copy of
|
|
5200
5290
|
* it**: `byNode`'s entries hold each result BY REFERENCE (the log record's own object) — so it is a holder of results and
|
|
@@ -5225,7 +5315,7 @@ var Driver = class {
|
|
|
5225
5315
|
compose(node, edgeValue, itemIndices, runPayload, item) {
|
|
5226
5316
|
const needs = node.needs;
|
|
5227
5317
|
if (!needs) return edgeValue;
|
|
5228
|
-
let
|
|
5318
|
+
let here = null;
|
|
5229
5319
|
const out = {};
|
|
5230
5320
|
for (const [key, ref] of Object.entries(needs)) {
|
|
5231
5321
|
const t = referenceTarget(ref);
|
|
@@ -5241,8 +5331,8 @@ var Driver = class {
|
|
|
5241
5331
|
out[key] = cloneValue(item());
|
|
5242
5332
|
continue;
|
|
5243
5333
|
}
|
|
5244
|
-
|
|
5245
|
-
const v = this.resolveReference(node.id,
|
|
5334
|
+
here ??= this.pathOf(node.id, itemIndices);
|
|
5335
|
+
const v = this.resolveReference(node.id, here, key, t, "needs");
|
|
5246
5336
|
if (v !== ABSENT) out[key] = v;
|
|
5247
5337
|
}
|
|
5248
5338
|
return out;
|
|
@@ -5263,8 +5353,8 @@ var Driver = class {
|
|
|
5263
5353
|
*
|
|
5264
5354
|
* @throws UnresolvedReference — `FD-R3`.
|
|
5265
5355
|
*/
|
|
5266
|
-
resolveReference(atNodeId, here,
|
|
5267
|
-
const visible =
|
|
5356
|
+
resolveReference(atNodeId, here, key, t, site) {
|
|
5357
|
+
const visible = this.visibleOutputs(atNodeId, here, t.node);
|
|
5268
5358
|
if (visible.length === 0 && t.conditional) {
|
|
5269
5359
|
return t.form === "all" ? [] : ABSENT;
|
|
5270
5360
|
}
|
|
@@ -5273,6 +5363,7 @@ var Driver = class {
|
|
|
5273
5363
|
`node "${atNodeId}" at branch path "${here}" declares \`${site}[${JSON.stringify(key)}]\` naming "${t.node}", and no output of that node is on this path. \`FD-V27\` checked that this node is reachable FROM it and \`FD-V29\` that it must have RUN, which is everything the graph can say; whether any of its outputs is on YOUR path is a statement about this run \u2014 a reader inside a region whose indices do not line up with the target\`s, or a join policy that fired before that node settled. FD-R3.`
|
|
5274
5364
|
);
|
|
5275
5365
|
}
|
|
5366
|
+
this.outputsCache.examined += t.form === "all" ? visible.length : 1;
|
|
5276
5367
|
return t.form === "all" ? visible.map((e) => cloneValue(e.result)) : cloneValue(visible[0].result);
|
|
5277
5368
|
}
|
|
5278
5369
|
/**
|
|
@@ -5325,7 +5416,7 @@ var Driver = class {
|
|
|
5325
5416
|
const needs = edge.needs;
|
|
5326
5417
|
if (!needs) return EMPTY_NEEDS;
|
|
5327
5418
|
const here = this.pathOf(node.id, itemIndices);
|
|
5328
|
-
|
|
5419
|
+
this.outputs();
|
|
5329
5420
|
const out = {};
|
|
5330
5421
|
for (const [key, ref] of Object.entries(needs)) {
|
|
5331
5422
|
const t = referenceTarget(ref);
|
|
@@ -5340,7 +5431,7 @@ var Driver = class {
|
|
|
5340
5431
|
if (t.form === "self") {
|
|
5341
5432
|
throw new Error(`node "${node.id}"'s edge declares needs[${JSON.stringify(key)}] = ${JSON.stringify(ref)}, which FD-V26 refuses on an edge`);
|
|
5342
5433
|
}
|
|
5343
|
-
const v = this.resolveReference(node.id, here,
|
|
5434
|
+
const v = this.resolveReference(node.id, here, key, t, "edge needs");
|
|
5344
5435
|
if (v !== ABSENT) out[key] = v;
|
|
5345
5436
|
}
|
|
5346
5437
|
return Object.freeze(out);
|
|
@@ -6556,6 +6647,7 @@ export {
|
|
|
6556
6647
|
innermostMapOf,
|
|
6557
6648
|
sharedRegionDepth,
|
|
6558
6649
|
outputIsVisible,
|
|
6650
|
+
visibilityKey,
|
|
6559
6651
|
branchIndexOf,
|
|
6560
6652
|
certainlySettled,
|
|
6561
6653
|
SCHEMA_KEYWORDS,
|
package/dist/browser/index.js
CHANGED
|
@@ -141,9 +141,10 @@ import {
|
|
|
141
141
|
transformShapeProblem,
|
|
142
142
|
validateDefinition,
|
|
143
143
|
violatedRules,
|
|
144
|
+
visibilityKey,
|
|
144
145
|
witness,
|
|
145
146
|
workersOf
|
|
146
|
-
} from "./chunks/chunk-
|
|
147
|
+
} from "./chunks/chunk-I3KDEOLB.js";
|
|
147
148
|
|
|
148
149
|
// src/definition/assist.ts
|
|
149
150
|
var TYPES = ["object", "array", "string", "number", "integer", "boolean", "null"];
|
|
@@ -440,6 +441,7 @@ export {
|
|
|
440
441
|
tsTypeOf,
|
|
441
442
|
validateDefinition,
|
|
442
443
|
violatedRules,
|
|
444
|
+
visibilityKey,
|
|
443
445
|
witness,
|
|
444
446
|
workersOf
|
|
445
447
|
};
|
|
@@ -136,14 +136,14 @@ function sharedRegionDepth(regions, a, b) {
|
|
|
136
136
|
}
|
|
137
137
|
function outputIsVisible(regions, readerId, readerPath, targetId, targetPath) {
|
|
138
138
|
const depth = sharedRegionDepth(regions, readerId, targetId);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
139
|
+
const key = visibilityKey(readerPath, depth);
|
|
140
|
+
return key !== null && key === visibilityKey(targetPath, depth);
|
|
141
|
+
}
|
|
142
|
+
function visibilityKey(path, depth) {
|
|
143
|
+
if (depth === 0) return "";
|
|
144
|
+
const segs = path === "-" ? [] : path.split(".");
|
|
145
|
+
if (segs.length < depth) return null;
|
|
146
|
+
return segs.length === depth ? path : segs.slice(0, depth).join(".");
|
|
147
147
|
}
|
|
148
148
|
function branchIndexOf(regions, nodeId) {
|
|
149
149
|
const r = innermostRegionOf(regions, nodeId);
|
|
@@ -2993,6 +2993,18 @@ function describeElement(e) {
|
|
|
2993
2993
|
}
|
|
2994
2994
|
return s.length > 160 ? `${s.slice(0, 157)}...` : s;
|
|
2995
2995
|
}
|
|
2996
|
+
var NO_OUTPUTS = Object.freeze([]);
|
|
2997
|
+
function groupPosition(group, path) {
|
|
2998
|
+
let lo = 0;
|
|
2999
|
+
let hi = group.length;
|
|
3000
|
+
if (hi === 0 || compareBranchPaths(group[hi - 1].path, path) < 0) return hi;
|
|
3001
|
+
while (lo < hi) {
|
|
3002
|
+
const mid = lo + hi >>> 1;
|
|
3003
|
+
if (compareBranchPaths(group[mid].path, path) < 0) lo = mid + 1;
|
|
3004
|
+
else hi = mid;
|
|
3005
|
+
}
|
|
3006
|
+
return lo;
|
|
3007
|
+
}
|
|
2996
3008
|
var UndispatchedTask = class extends Error {
|
|
2997
3009
|
constructor(message, options) {
|
|
2998
3010
|
super(message, options);
|
|
@@ -5128,7 +5140,7 @@ var Driver = class {
|
|
|
5128
5140
|
* node or an edge that declares a `needs` asks, and a run that declares none never builds it.
|
|
5129
5141
|
*/
|
|
5130
5142
|
outputs() {
|
|
5131
|
-
const c = this.outputsCache ??= { at: 0, byNode: /* @__PURE__ */ new Map(), tasks: /* @__PURE__ */ new Map(), orders: /* @__PURE__ */ new Map() };
|
|
5143
|
+
const c = this.outputsCache ??= { at: 0, byNode: /* @__PURE__ */ new Map(), tasks: /* @__PURE__ */ new Map(), orders: /* @__PURE__ */ new Map(), index: /* @__PURE__ */ new Map(), depths: /* @__PURE__ */ new Map(), examined: 0 };
|
|
5132
5144
|
for (; c.at < this.log.length; c.at += 1) {
|
|
5133
5145
|
const r = this.log[c.at];
|
|
5134
5146
|
let t = c.tasks.get(r.taskId);
|
|
@@ -5171,6 +5183,7 @@ var Driver = class {
|
|
|
5171
5183
|
const at = list.indexOf(t.entry);
|
|
5172
5184
|
list.splice(at, 1);
|
|
5173
5185
|
orders.splice(at, 1);
|
|
5186
|
+
this.reindex(c, t.nodeId, t.entry, null);
|
|
5174
5187
|
t.entry = null;
|
|
5175
5188
|
if (list.length === 0) {
|
|
5176
5189
|
c.byNode.delete(t.nodeId);
|
|
@@ -5181,10 +5194,12 @@ var Driver = class {
|
|
|
5181
5194
|
const entry = { path: t.path, result: eff.result };
|
|
5182
5195
|
if (t.entry !== null && list !== void 0) {
|
|
5183
5196
|
list[list.indexOf(t.entry)] = entry;
|
|
5197
|
+
this.reindex(c, t.nodeId, t.entry, entry);
|
|
5184
5198
|
t.entry = entry;
|
|
5185
5199
|
return;
|
|
5186
5200
|
}
|
|
5187
5201
|
t.entry = entry;
|
|
5202
|
+
this.reindex(c, t.nodeId, null, entry);
|
|
5188
5203
|
if (list === void 0 || orders === void 0) {
|
|
5189
5204
|
c.byNode.set(t.nodeId, [entry]);
|
|
5190
5205
|
c.orders.set(t.nodeId, [t.order]);
|
|
@@ -5195,6 +5210,81 @@ var Driver = class {
|
|
|
5195
5210
|
list.splice(i, 0, entry);
|
|
5196
5211
|
orders.splice(i, 0, t.order);
|
|
5197
5212
|
}
|
|
5213
|
+
/**
|
|
5214
|
+
* **One entry's change carried into every index built for its node** — W199, ADR 0124: called by `place` at each of
|
|
5215
|
+
* its cases (a first entry, an append, an insertion at a first-record position, a replacement in place, a removal), so
|
|
5216
|
+
* the index is caught up exactly when `byNode` is and can say nothing `byNode` does not. `was` is the entry leaving
|
|
5217
|
+
* (`null` for an insertion), `now` the one arriving (`null` for a removal); a replacement keeps its path, so its
|
|
5218
|
+
* position. A group is ordered by PATH, not by first record, so where `byNode` inserted mid-list the group finds its own
|
|
5219
|
+
* place by a binary search. An entry with fewer than `depth` segments is visible to no reader at that depth and is filed
|
|
5220
|
+
* nowhere.
|
|
5221
|
+
*/
|
|
5222
|
+
reindex(c, nodeId, was, now) {
|
|
5223
|
+
const byDepth = c.index.get(nodeId);
|
|
5224
|
+
if (byDepth === void 0) return;
|
|
5225
|
+
const path = (now ?? was).path;
|
|
5226
|
+
for (const [depth, groups] of byDepth) {
|
|
5227
|
+
const key = visibilityKey(path, depth);
|
|
5228
|
+
if (key === null) continue;
|
|
5229
|
+
c.examined += 1;
|
|
5230
|
+
const group = groups.get(key);
|
|
5231
|
+
if (was !== null) {
|
|
5232
|
+
const at = groupPosition(group, path);
|
|
5233
|
+
if (now !== null) group[at] = now;
|
|
5234
|
+
else if (group.length === 1) groups.delete(key);
|
|
5235
|
+
else group.splice(at, 1);
|
|
5236
|
+
} else if (group === void 0) {
|
|
5237
|
+
groups.set(key, [now]);
|
|
5238
|
+
} else {
|
|
5239
|
+
group.splice(groupPosition(group, path), 0, now);
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
/**
|
|
5244
|
+
* **A node's outputs grouped for readers at `depth`** — built on the first read at that depth from the node's current
|
|
5245
|
+
* entries (each one examined), each group sorted ONCE, and from then on kept by `reindex`.
|
|
5246
|
+
*/
|
|
5247
|
+
groupsOf(c, nodeId, depth) {
|
|
5248
|
+
let byDepth = c.index.get(nodeId);
|
|
5249
|
+
if (byDepth === void 0) c.index.set(nodeId, byDepth = /* @__PURE__ */ new Map());
|
|
5250
|
+
let groups = byDepth.get(depth);
|
|
5251
|
+
if (groups !== void 0) return groups;
|
|
5252
|
+
groups = /* @__PURE__ */ new Map();
|
|
5253
|
+
for (const e of c.byNode.get(nodeId) ?? NO_OUTPUTS) {
|
|
5254
|
+
c.examined += 1;
|
|
5255
|
+
const key = visibilityKey(e.path, depth);
|
|
5256
|
+
if (key === null) continue;
|
|
5257
|
+
const group = groups.get(key);
|
|
5258
|
+
if (group === void 0) groups.set(key, [e]);
|
|
5259
|
+
else group.push(e);
|
|
5260
|
+
}
|
|
5261
|
+
for (const group of groups.values()) if (group.length > 1) group.sort((x, y) => compareBranchPaths(x.path, y.path));
|
|
5262
|
+
byDepth.set(depth, groups);
|
|
5263
|
+
return groups;
|
|
5264
|
+
}
|
|
5265
|
+
/** The depth `reader` and `target` share — `sharedRegionDepth`, a graph fact, asked once per pair. */
|
|
5266
|
+
depthOf(c, reader, target) {
|
|
5267
|
+
let byTarget = c.depths.get(reader);
|
|
5268
|
+
if (byTarget === void 0) c.depths.set(reader, byTarget = /* @__PURE__ */ new Map());
|
|
5269
|
+
let depth = byTarget.get(target);
|
|
5270
|
+
if (depth === void 0) byTarget.set(target, depth = sharedRegionDepth(this.regions, reader, target));
|
|
5271
|
+
return depth;
|
|
5272
|
+
}
|
|
5273
|
+
/**
|
|
5274
|
+
* **The outputs of `nodeId` visible to `atNodeId` at `here`, in `compareBranchPaths` order** — W199, ADR 0124: ADR
|
|
5275
|
+
* 0044's predicate answered by a LOOKUP. The predicate is `visibilityKey(here, d) === visibilityKey(path, d)` at the depth
|
|
5276
|
+
* `d` the two share (`outputIsVisible`), so the visible outputs are exactly the group filed under the reader's key: what
|
|
5277
|
+
* the filter over every output and the sort of its survivors returned until W199, found in the cost of its answer.
|
|
5278
|
+
* **The index's own list, never handed out** — the caller maps it to copies.
|
|
5279
|
+
*/
|
|
5280
|
+
visibleOutputs(atNodeId, here, nodeId) {
|
|
5281
|
+
this.outputs();
|
|
5282
|
+
const c = this.outputsCache;
|
|
5283
|
+
const depth = this.depthOf(c, atNodeId, nodeId);
|
|
5284
|
+
const key = visibilityKey(here, depth);
|
|
5285
|
+
if (key === null) return NO_OUTPUTS;
|
|
5286
|
+
return this.groupsOf(c, nodeId, depth).get(key) ?? NO_OUTPUTS;
|
|
5287
|
+
}
|
|
5198
5288
|
/**
|
|
5199
5289
|
* The projection above, CAUGHT UP to `at` — `RN-R13` v0.30, ADR 0121 (W195). **Not run state and not a second copy of
|
|
5200
5290
|
* it**: `byNode`'s entries hold each result BY REFERENCE (the log record's own object) — so it is a holder of results and
|
|
@@ -5225,7 +5315,7 @@ var Driver = class {
|
|
|
5225
5315
|
compose(node, edgeValue, itemIndices, runPayload, item) {
|
|
5226
5316
|
const needs = node.needs;
|
|
5227
5317
|
if (!needs) return edgeValue;
|
|
5228
|
-
let
|
|
5318
|
+
let here = null;
|
|
5229
5319
|
const out = {};
|
|
5230
5320
|
for (const [key, ref] of Object.entries(needs)) {
|
|
5231
5321
|
const t = referenceTarget(ref);
|
|
@@ -5241,8 +5331,8 @@ var Driver = class {
|
|
|
5241
5331
|
out[key] = cloneValue(item());
|
|
5242
5332
|
continue;
|
|
5243
5333
|
}
|
|
5244
|
-
|
|
5245
|
-
const v = this.resolveReference(node.id,
|
|
5334
|
+
here ??= this.pathOf(node.id, itemIndices);
|
|
5335
|
+
const v = this.resolveReference(node.id, here, key, t, "needs");
|
|
5246
5336
|
if (v !== ABSENT) out[key] = v;
|
|
5247
5337
|
}
|
|
5248
5338
|
return out;
|
|
@@ -5263,8 +5353,8 @@ var Driver = class {
|
|
|
5263
5353
|
*
|
|
5264
5354
|
* @throws UnresolvedReference — `FD-R3`.
|
|
5265
5355
|
*/
|
|
5266
|
-
resolveReference(atNodeId, here,
|
|
5267
|
-
const visible =
|
|
5356
|
+
resolveReference(atNodeId, here, key, t, site) {
|
|
5357
|
+
const visible = this.visibleOutputs(atNodeId, here, t.node);
|
|
5268
5358
|
if (visible.length === 0 && t.conditional) {
|
|
5269
5359
|
return t.form === "all" ? [] : ABSENT;
|
|
5270
5360
|
}
|
|
@@ -5273,6 +5363,7 @@ var Driver = class {
|
|
|
5273
5363
|
`node "${atNodeId}" at branch path "${here}" declares \`${site}[${JSON.stringify(key)}]\` naming "${t.node}", and no output of that node is on this path. \`FD-V27\` checked that this node is reachable FROM it and \`FD-V29\` that it must have RUN, which is everything the graph can say; whether any of its outputs is on YOUR path is a statement about this run \u2014 a reader inside a region whose indices do not line up with the target\`s, or a join policy that fired before that node settled. FD-R3.`
|
|
5274
5364
|
);
|
|
5275
5365
|
}
|
|
5366
|
+
this.outputsCache.examined += t.form === "all" ? visible.length : 1;
|
|
5276
5367
|
return t.form === "all" ? visible.map((e) => cloneValue(e.result)) : cloneValue(visible[0].result);
|
|
5277
5368
|
}
|
|
5278
5369
|
/**
|
|
@@ -5325,7 +5416,7 @@ var Driver = class {
|
|
|
5325
5416
|
const needs = edge.needs;
|
|
5326
5417
|
if (!needs) return EMPTY_NEEDS;
|
|
5327
5418
|
const here = this.pathOf(node.id, itemIndices);
|
|
5328
|
-
|
|
5419
|
+
this.outputs();
|
|
5329
5420
|
const out = {};
|
|
5330
5421
|
for (const [key, ref] of Object.entries(needs)) {
|
|
5331
5422
|
const t = referenceTarget(ref);
|
|
@@ -5340,7 +5431,7 @@ var Driver = class {
|
|
|
5340
5431
|
if (t.form === "self") {
|
|
5341
5432
|
throw new Error(`node "${node.id}"'s edge declares needs[${JSON.stringify(key)}] = ${JSON.stringify(ref)}, which FD-V26 refuses on an edge`);
|
|
5342
5433
|
}
|
|
5343
|
-
const v = this.resolveReference(node.id, here,
|
|
5434
|
+
const v = this.resolveReference(node.id, here, key, t, "edge needs");
|
|
5344
5435
|
if (v !== ABSENT) out[key] = v;
|
|
5345
5436
|
}
|
|
5346
5437
|
return Object.freeze(out);
|
|
@@ -6556,6 +6647,7 @@ export {
|
|
|
6556
6647
|
innermostMapOf,
|
|
6557
6648
|
sharedRegionDepth,
|
|
6558
6649
|
outputIsVisible,
|
|
6650
|
+
visibilityKey,
|
|
6559
6651
|
branchIndexOf,
|
|
6560
6652
|
certainlySettled,
|
|
6561
6653
|
SCHEMA_KEYWORDS,
|
package/dist/node/index.js
CHANGED
|
@@ -141,9 +141,10 @@ import {
|
|
|
141
141
|
transformShapeProblem,
|
|
142
142
|
validateDefinition,
|
|
143
143
|
violatedRules,
|
|
144
|
+
visibilityKey,
|
|
144
145
|
witness,
|
|
145
146
|
workersOf
|
|
146
|
-
} from "./chunks/chunk-
|
|
147
|
+
} from "./chunks/chunk-I3KDEOLB.js";
|
|
147
148
|
|
|
148
149
|
// src/definition/assist.ts
|
|
149
150
|
var TYPES = ["object", "array", "string", "number", "integer", "boolean", "null"];
|
|
@@ -440,6 +441,7 @@ export {
|
|
|
440
441
|
tsTypeOf,
|
|
441
442
|
validateDefinition,
|
|
442
443
|
violatedRules,
|
|
444
|
+
visibilityKey,
|
|
443
445
|
witness,
|
|
444
446
|
workersOf
|
|
445
447
|
};
|
|
@@ -309,6 +309,18 @@ export declare function sharedRegionDepth(regions: RegionSets[], a: string, b: s
|
|
|
309
309
|
* own, so for an edge's `needs` this is the node the edge hangs on (`FD-C3`).
|
|
310
310
|
*/
|
|
311
311
|
export declare function outputIsVisible(regions: RegionSets[], readerId: string, readerPath: string, targetId: string, targetPath: string): boolean;
|
|
312
|
+
/**
|
|
313
|
+
* **The key an output is filed under for a reader that shares `depth` regions with it** — the first `depth` SEGMENTS of
|
|
314
|
+
* its branch path, joined, or `null` for a path with fewer (`-` has none) — W199,
|
|
315
|
+
* [ADR 0124](../../doc/decisions/0124-a-reference-read-costs-its-answer.md). An output is visible to a reader exactly
|
|
316
|
+
* when the two have the SAME key at their shared depth ({@link outputIsVisible}), so the driver files a node's outputs
|
|
317
|
+
* under it once and a read takes its group, where it searched every output (Q735).
|
|
318
|
+
*
|
|
319
|
+
* **Segment-wise and never by string** (ADR 0044 rule 3): a segment holds no `.`, so two keys of `depth` segments are
|
|
320
|
+
* equal exactly when their segments are — `1` and `10` stay distinct, and `1.0` at depth 1 is `1`. At depth 0 every path
|
|
321
|
+
* has the one key `''`.
|
|
322
|
+
*/
|
|
323
|
+
export declare function visibilityKey(path: string, depth: number): string | null;
|
|
312
324
|
/**
|
|
313
325
|
* Which region a node belongs to, and via which branch — the value the task id's
|
|
314
326
|
* `branchPath` segment is derived from (`spec/task-id.md` section 1).
|
|
@@ -1749,6 +1749,31 @@ export declare class Driver {
|
|
|
1749
1749
|
* none (W195's conformity reader, C4; `test/driver/log-view.test.ts` drives it and the resolution that places it again).
|
|
1750
1750
|
*/
|
|
1751
1751
|
private place;
|
|
1752
|
+
/**
|
|
1753
|
+
* **One entry's change carried into every index built for its node** — W199, ADR 0124: called by `place` at each of
|
|
1754
|
+
* its cases (a first entry, an append, an insertion at a first-record position, a replacement in place, a removal), so
|
|
1755
|
+
* the index is caught up exactly when `byNode` is and can say nothing `byNode` does not. `was` is the entry leaving
|
|
1756
|
+
* (`null` for an insertion), `now` the one arriving (`null` for a removal); a replacement keeps its path, so its
|
|
1757
|
+
* position. A group is ordered by PATH, not by first record, so where `byNode` inserted mid-list the group finds its own
|
|
1758
|
+
* place by a binary search. An entry with fewer than `depth` segments is visible to no reader at that depth and is filed
|
|
1759
|
+
* nowhere.
|
|
1760
|
+
*/
|
|
1761
|
+
private reindex;
|
|
1762
|
+
/**
|
|
1763
|
+
* **A node's outputs grouped for readers at `depth`** — built on the first read at that depth from the node's current
|
|
1764
|
+
* entries (each one examined), each group sorted ONCE, and from then on kept by `reindex`.
|
|
1765
|
+
*/
|
|
1766
|
+
private groupsOf;
|
|
1767
|
+
/** The depth `reader` and `target` share — `sharedRegionDepth`, a graph fact, asked once per pair. */
|
|
1768
|
+
private depthOf;
|
|
1769
|
+
/**
|
|
1770
|
+
* **The outputs of `nodeId` visible to `atNodeId` at `here`, in `compareBranchPaths` order** — W199, ADR 0124: ADR
|
|
1771
|
+
* 0044's predicate answered by a LOOKUP. The predicate is `visibilityKey(here, d) === visibilityKey(path, d)` at the depth
|
|
1772
|
+
* `d` the two share (`outputIsVisible`), so the visible outputs are exactly the group filed under the reader's key: what
|
|
1773
|
+
* the filter over every output and the sort of its survivors returned until W199, found in the cost of its answer.
|
|
1774
|
+
* **The index's own list, never handed out** — the caller maps it to copies.
|
|
1775
|
+
*/
|
|
1776
|
+
private visibleOutputs;
|
|
1752
1777
|
/**
|
|
1753
1778
|
* The projection above, CAUGHT UP to `at` — `RN-R13` v0.30, ADR 0121 (W195). **Not run state and not a second copy of
|
|
1754
1779
|
* it**: `byNode`'s entries hold each result BY REFERENCE (the log record's own object) — so it is a holder of results and
|