@smthrs/plan 0.0.0-stage → 1.0.0-rc.12
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/CHANGELOG.md +185 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/dist/cjs/CachePolicy.d.ts +131 -0
- package/dist/cjs/CachePolicy.d.ts.map +1 -0
- package/dist/cjs/CachePolicy.js +50 -0
- package/dist/cjs/CachePolicy.js.map +7 -0
- package/dist/cjs/Effects.d.ts +258 -0
- package/dist/cjs/Effects.d.ts.map +1 -0
- package/dist/cjs/Effects.js +76 -0
- package/dist/cjs/Effects.js.map +7 -0
- package/dist/cjs/FileSet.d.ts +366 -0
- package/dist/cjs/FileSet.d.ts.map +1 -0
- package/dist/cjs/FileSet.js +196 -0
- package/dist/cjs/FileSet.js.map +7 -0
- package/dist/cjs/GraphBuildError.d.ts +120 -0
- package/dist/cjs/GraphBuildError.d.ts.map +1 -0
- package/dist/cjs/GraphBuildError.js +71 -0
- package/dist/cjs/GraphBuildError.js.map +7 -0
- package/dist/cjs/KeyMaterial.d.ts +107 -0
- package/dist/cjs/KeyMaterial.d.ts.map +1 -0
- package/dist/cjs/KeyMaterial.js +64 -0
- package/dist/cjs/KeyMaterial.js.map +7 -0
- package/dist/cjs/Node.d.ts +677 -0
- package/dist/cjs/Node.d.ts.map +1 -0
- package/dist/cjs/Node.js +229 -0
- package/dist/cjs/Node.js.map +7 -0
- package/dist/cjs/Placement.d.ts +117 -0
- package/dist/cjs/Placement.d.ts.map +1 -0
- package/dist/cjs/Placement.js +46 -0
- package/dist/cjs/Placement.js.map +7 -0
- package/dist/cjs/Plan.d.ts +489 -0
- package/dist/cjs/Plan.d.ts.map +1 -0
- package/dist/cjs/Plan.js +511 -0
- package/dist/cjs/Plan.js.map +7 -0
- package/dist/cjs/PlanDiff.d.ts +77 -0
- package/dist/cjs/PlanDiff.d.ts.map +1 -0
- package/dist/cjs/PlanDiff.js +149 -0
- package/dist/cjs/PlanDiff.js.map +7 -0
- package/dist/cjs/Planned.d.ts +121 -0
- package/dist/cjs/Planned.d.ts.map +1 -0
- package/dist/cjs/Planned.js +72 -0
- package/dist/cjs/Planned.js.map +7 -0
- package/dist/cjs/Repetition.d.ts +31 -0
- package/dist/cjs/Repetition.d.ts.map +1 -0
- package/dist/cjs/Repetition.js +36 -0
- package/dist/cjs/Repetition.js.map +7 -0
- package/dist/cjs/Scheduling.d.ts +89 -0
- package/dist/cjs/Scheduling.d.ts.map +1 -0
- package/dist/cjs/Scheduling.js +76 -0
- package/dist/cjs/Scheduling.js.map +7 -0
- package/dist/cjs/StepKey.d.ts +354 -0
- package/dist/cjs/StepKey.d.ts.map +1 -0
- package/dist/cjs/StepKey.js +313 -0
- package/dist/cjs/StepKey.js.map +7 -0
- package/dist/cjs/index.d.ts +91 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +59 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ConflictAnnotation.d.ts +59 -0
- package/dist/cjs/internal/ConflictAnnotation.d.ts.map +1 -0
- package/dist/cjs/internal/ConflictAnnotation.js +247 -0
- package/dist/cjs/internal/ConflictAnnotation.js.map +7 -0
- package/dist/cjs/internal/EffectCandidates.d.ts +19 -0
- package/dist/cjs/internal/EffectCandidates.d.ts.map +1 -0
- package/dist/cjs/internal/EffectCandidates.js +92 -0
- package/dist/cjs/internal/EffectCandidates.js.map +7 -0
- package/dist/cjs/internal/JsonMirror.d.ts +34 -0
- package/dist/cjs/internal/JsonMirror.d.ts.map +1 -0
- package/dist/cjs/internal/JsonMirror.js +195 -0
- package/dist/cjs/internal/JsonMirror.js.map +7 -0
- package/dist/cjs/internal/SchemaIdentity.d.ts +140 -0
- package/dist/cjs/internal/SchemaIdentity.d.ts.map +1 -0
- package/dist/cjs/internal/SchemaIdentity.js +718 -0
- package/dist/cjs/internal/SchemaIdentity.js.map +7 -0
- package/dist/cjs/internal/effects.d.ts +245 -0
- package/dist/cjs/internal/effects.d.ts.map +1 -0
- package/dist/cjs/internal/effects.js +290 -0
- package/dist/cjs/internal/effects.js.map +7 -0
- package/dist/cjs/internal/node.d.ts +526 -0
- package/dist/cjs/internal/node.d.ts.map +1 -0
- package/dist/cjs/internal/node.js +313 -0
- package/dist/cjs/internal/node.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/PlanFixtures.d.ts +127 -0
- package/dist/cjs/test/PlanFixtures.d.ts.map +1 -0
- package/dist/cjs/test/PlanFixtures.js +59 -0
- package/dist/cjs/test/PlanFixtures.js.map +7 -0
- package/dist/esm/CachePolicy.d.ts +131 -0
- package/dist/esm/CachePolicy.d.ts.map +1 -0
- package/dist/esm/CachePolicy.js +117 -0
- package/dist/esm/CachePolicy.js.map +1 -0
- package/dist/esm/Effects.d.ts +258 -0
- package/dist/esm/Effects.d.ts.map +1 -0
- package/dist/esm/Effects.js +178 -0
- package/dist/esm/Effects.js.map +1 -0
- package/dist/esm/FileSet.d.ts +366 -0
- package/dist/esm/FileSet.d.ts.map +1 -0
- package/dist/esm/FileSet.js +398 -0
- package/dist/esm/FileSet.js.map +1 -0
- package/dist/esm/GraphBuildError.d.ts +120 -0
- package/dist/esm/GraphBuildError.d.ts.map +1 -0
- package/dist/esm/GraphBuildError.js +145 -0
- package/dist/esm/GraphBuildError.js.map +1 -0
- package/dist/esm/KeyMaterial.d.ts +107 -0
- package/dist/esm/KeyMaterial.d.ts.map +1 -0
- package/dist/esm/KeyMaterial.js +92 -0
- package/dist/esm/KeyMaterial.js.map +1 -0
- package/dist/esm/Node.d.ts +677 -0
- package/dist/esm/Node.d.ts.map +1 -0
- package/dist/esm/Node.js +586 -0
- package/dist/esm/Node.js.map +1 -0
- package/dist/esm/Placement.d.ts +117 -0
- package/dist/esm/Placement.d.ts.map +1 -0
- package/dist/esm/Placement.js +65 -0
- package/dist/esm/Placement.js.map +1 -0
- package/dist/esm/Plan.d.ts +489 -0
- package/dist/esm/Plan.d.ts.map +1 -0
- package/dist/esm/Plan.js +718 -0
- package/dist/esm/Plan.js.map +1 -0
- package/dist/esm/PlanDiff.d.ts +77 -0
- package/dist/esm/PlanDiff.d.ts.map +1 -0
- package/dist/esm/PlanDiff.js +205 -0
- package/dist/esm/PlanDiff.js.map +1 -0
- package/dist/esm/Planned.d.ts +121 -0
- package/dist/esm/Planned.d.ts.map +1 -0
- package/dist/esm/Planned.js +147 -0
- package/dist/esm/Planned.js.map +1 -0
- package/dist/esm/Repetition.d.ts +31 -0
- package/dist/esm/Repetition.d.ts.map +1 -0
- package/dist/esm/Repetition.js +24 -0
- package/dist/esm/Repetition.js.map +1 -0
- package/dist/esm/Scheduling.d.ts +89 -0
- package/dist/esm/Scheduling.d.ts.map +1 -0
- package/dist/esm/Scheduling.js +82 -0
- package/dist/esm/Scheduling.js.map +1 -0
- package/dist/esm/StepKey.d.ts +354 -0
- package/dist/esm/StepKey.d.ts.map +1 -0
- package/dist/esm/StepKey.js +503 -0
- package/dist/esm/StepKey.js.map +1 -0
- package/dist/esm/index.d.ts +91 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +91 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ConflictAnnotation.d.ts +59 -0
- package/dist/esm/internal/ConflictAnnotation.d.ts.map +1 -0
- package/dist/esm/internal/ConflictAnnotation.js +332 -0
- package/dist/esm/internal/ConflictAnnotation.js.map +1 -0
- package/dist/esm/internal/EffectCandidates.d.ts +19 -0
- package/dist/esm/internal/EffectCandidates.d.ts.map +1 -0
- package/dist/esm/internal/EffectCandidates.js +86 -0
- package/dist/esm/internal/EffectCandidates.js.map +1 -0
- package/dist/esm/internal/JsonMirror.d.ts +34 -0
- package/dist/esm/internal/JsonMirror.d.ts.map +1 -0
- package/dist/esm/internal/JsonMirror.js +204 -0
- package/dist/esm/internal/JsonMirror.js.map +1 -0
- package/dist/esm/internal/SchemaIdentity.d.ts +140 -0
- package/dist/esm/internal/SchemaIdentity.d.ts.map +1 -0
- package/dist/esm/internal/SchemaIdentity.js +1002 -0
- package/dist/esm/internal/SchemaIdentity.js.map +1 -0
- package/dist/esm/internal/effects.d.ts +245 -0
- package/dist/esm/internal/effects.d.ts.map +1 -0
- package/dist/esm/internal/effects.js +459 -0
- package/dist/esm/internal/effects.js.map +1 -0
- package/dist/esm/internal/node.d.ts +526 -0
- package/dist/esm/internal/node.d.ts.map +1 -0
- package/dist/esm/internal/node.js +565 -0
- package/dist/esm/internal/node.js.map +1 -0
- package/dist/esm/test/PlanFixtures.d.ts +127 -0
- package/dist/esm/test/PlanFixtures.d.ts.map +1 -0
- package/dist/esm/test/PlanFixtures.js +60 -0
- package/dist/esm/test/PlanFixtures.js.map +1 -0
- package/docs/README.md +167 -0
- package/docs/api.md +1485 -0
- package/docs/concepts/authoring-ast.md +204 -0
- package/docs/concepts/effects-and-conflicts.md +135 -0
- package/docs/concepts/plan-value.md +168 -0
- package/docs/concepts/step-keys.md +179 -0
- package/docs/guides/author-a-node-graph.md +182 -0
- package/docs/guides/compile-a-plan.md +146 -0
- package/docs/guides/declare-file-effects.md +143 -0
- package/docs/guides/diff-two-plans.md +310 -0
- package/docs/installation.md +75 -0
- package/docs/quickstart.md +128 -0
- package/docs/testing.md +73 -0
- package/docs/troubleshooting.md +314 -0
- package/package.json +255 -3
- package/src/CachePolicy.ts +143 -0
- package/src/Effects.ts +302 -0
- package/src/FileSet.ts +493 -0
- package/src/GraphBuildError.ts +156 -0
- package/src/KeyMaterial.ts +111 -0
- package/src/Node.ts +969 -0
- package/src/Placement.ts +99 -0
- package/src/Plan.ts +911 -0
- package/src/PlanDiff.ts +244 -0
- package/src/Planned.ts +208 -0
- package/src/Repetition.ts +33 -0
- package/src/Scheduling.ts +154 -0
- package/src/StepKey.ts +736 -0
- package/src/index.ts +103 -0
- package/src/internal/ConflictAnnotation.ts +374 -0
- package/src/internal/EffectCandidates.ts +89 -0
- package/src/internal/JsonMirror.ts +255 -0
- package/src/internal/SchemaIdentity.ts +1096 -0
- package/src/internal/effects.ts +543 -0
- package/src/internal/node.ts +859 -0
- package/src/test/PlanFixtures.ts +76 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The placeholder a body sees where a step result will be.
|
|
3
|
+
*
|
|
4
|
+
* A flow body runs at PLAN time, so its
|
|
5
|
+
* own payload is real data while every step result is a value that does not
|
|
6
|
+
* exist yet. That value has a name and one rule: a planned value may be **passed** — into a payload field,
|
|
7
|
+
* into a branch, into a map — and field access is allowed, because it records a
|
|
8
|
+
* reference path. It may never be **computed on**.
|
|
9
|
+
*
|
|
10
|
+
* {@link Planned} is branded, so arithmetic is a compile error.
|
|
11
|
+
* {@link make} returns a strict proxy
|
|
12
|
+
* whose `Symbol.toPrimitive`, `valueOf`, `toString`, `toJSON`, and call traps
|
|
13
|
+
* throw {@link module:GraphBuildError.GraphBuildError} rather than let a plan be
|
|
14
|
+
* built around `NaN` or `"[object Object]"`. The lenient proxy this adapts
|
|
15
|
+
* (`@smthrs/core`'s `Graph.plannedValue`) answered `toPrimitive` with a
|
|
16
|
+
* readable string, which is exactly the silent-wrong-plan path this module
|
|
17
|
+
* exists to close.
|
|
18
|
+
*
|
|
19
|
+
* JavaScript does not expose traps for `Boolean(value)` or strict identity
|
|
20
|
+
* (`value === other`), so those operations cannot be refused at run time.
|
|
21
|
+
* They reveal only proxy truthiness or identity and never the planned result;
|
|
22
|
+
* authors must use {@link module:Node.branch} for decisions on real values.
|
|
23
|
+
* Type-aware ESLint `@typescript-eslint/strict-boolean-expressions` rejects
|
|
24
|
+
* planned conditions, but does not reject explicit Boolean coercion or all
|
|
25
|
+
* reference comparisons. Those still require review.
|
|
26
|
+
*
|
|
27
|
+
* @since 0.1.0
|
|
28
|
+
*/
|
|
29
|
+
import { GraphBuildError } from "./GraphBuildError.js";
|
|
30
|
+
/**
|
|
31
|
+
* The brand carried by every planned value, and the key its reference is read
|
|
32
|
+
* from. Interned with `Symbol.for` so a value that crossed a module boundary is
|
|
33
|
+
* still recognised. The interned symbol is a recognition aid, not a
|
|
34
|
+
* capability, so {@link reference} validates the value stored under it.
|
|
35
|
+
*
|
|
36
|
+
* @since 0.1.0
|
|
37
|
+
* @category symbols
|
|
38
|
+
* @slop
|
|
39
|
+
*/
|
|
40
|
+
export const TypeId = Symbol.for("@smthrs/plan/Planned");
|
|
41
|
+
/**
|
|
42
|
+
* The operations that are computation rather than reference, keyed by the
|
|
43
|
+
* property that performs them. `toJSON` is here because `JSON.stringify` reads
|
|
44
|
+
* it, and a plan that serialized a placeholder would look correct.
|
|
45
|
+
*
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
const refusals = new Map([
|
|
49
|
+
[Symbol.toPrimitive, "Symbol.toPrimitive"],
|
|
50
|
+
["valueOf", "valueOf"],
|
|
51
|
+
["toString", "toString"],
|
|
52
|
+
["toJSON", "toJSON"]
|
|
53
|
+
]);
|
|
54
|
+
/** @private */
|
|
55
|
+
const describe = (reference) => reference.path.length === 0 ? reference.node : `${reference.node}.${reference.path.join(".")}`;
|
|
56
|
+
/**
|
|
57
|
+
* The one refusal this module raises. The guidance is spelled out rather than
|
|
58
|
+
* linked because the author reading it is inside a body and needs the
|
|
59
|
+
* correction on the spot.
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
const refuse = (reference, operation) => new GraphBuildError({
|
|
64
|
+
code: "planned_value_computed",
|
|
65
|
+
node: reference.node,
|
|
66
|
+
path: reference.path,
|
|
67
|
+
message: `Planned value ${describe(reference)} was computed at plan time by ${operation}. ` +
|
|
68
|
+
"Use Node.map to compute, Node.branch to decide, or pass the value into a payload."
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* The proxy itself. `_T` of {@link Identity} is a phantom and never exists at
|
|
72
|
+
* run time, so the trap carries the plain {@link Reference}.
|
|
73
|
+
*
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
const placeholder = (reference) => {
|
|
77
|
+
/* v8 ignore next -- a Proxy application always enters the `apply` trap below, so the callable target is unreachable by construction */
|
|
78
|
+
const target = () => undefined;
|
|
79
|
+
return new Proxy(target, {
|
|
80
|
+
get: (_target, key) => {
|
|
81
|
+
if (key === TypeId)
|
|
82
|
+
return reference;
|
|
83
|
+
const refusal = refusals.get(key);
|
|
84
|
+
if (refusal !== undefined)
|
|
85
|
+
throw refuse(reference, refusal);
|
|
86
|
+
// Not a thenable: a body that returns a planned value inside a promise
|
|
87
|
+
// must resolve to the placeholder, not hang awaiting a proxy.
|
|
88
|
+
if (key === "then")
|
|
89
|
+
return undefined;
|
|
90
|
+
return placeholder({ node: reference.node, path: [...reference.path, String(key)] });
|
|
91
|
+
},
|
|
92
|
+
apply: () => {
|
|
93
|
+
throw refuse(reference, "function application");
|
|
94
|
+
},
|
|
95
|
+
has: () => {
|
|
96
|
+
throw refuse(reference, "property existence (`in`)");
|
|
97
|
+
},
|
|
98
|
+
ownKeys: () => {
|
|
99
|
+
throw refuse(reference, "property enumeration");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Creates the strict placeholder standing for a node's result.
|
|
105
|
+
*
|
|
106
|
+
* Planning hands one of these to every builder that consumes an upstream
|
|
107
|
+
* value — a branch arm, a continuation — and reads the {@link Reference} back
|
|
108
|
+
* off whatever the builder passed it into.
|
|
109
|
+
*
|
|
110
|
+
* @since 0.1.0
|
|
111
|
+
* @category constructors
|
|
112
|
+
* @slop
|
|
113
|
+
*/
|
|
114
|
+
export const make = (node) => placeholder({ node, path: [] });
|
|
115
|
+
/** @private */
|
|
116
|
+
const carrier = (value) => (typeof value === "object" && value !== null) || typeof value === "function";
|
|
117
|
+
/** @private */
|
|
118
|
+
const validReference = (value) => typeof value === "object" && value !== null &&
|
|
119
|
+
typeof value.node === "string" && value.node.length > 0 &&
|
|
120
|
+
Array.isArray(value.path) &&
|
|
121
|
+
Array.from(value.path).every((segment) => typeof segment === "string");
|
|
122
|
+
/**
|
|
123
|
+
* Reads the reference a planned value records, or `undefined` for anything
|
|
124
|
+
* else. An interned symbol is a recognition aid rather than a capability, so a
|
|
125
|
+
* forged carrier is accepted only when its node and path have the complete
|
|
126
|
+
* reference shape. This is how a payload is scanned for the upstream results
|
|
127
|
+
* it consumes.
|
|
128
|
+
*
|
|
129
|
+
* @since 0.1.0
|
|
130
|
+
* @category accessors
|
|
131
|
+
* @slop
|
|
132
|
+
*/
|
|
133
|
+
export const reference = (value) => {
|
|
134
|
+
if (!carrier(value))
|
|
135
|
+
return undefined;
|
|
136
|
+
const candidate = value[TypeId];
|
|
137
|
+
return validReference(candidate) ? candidate : undefined;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Checks whether a value is a planned placeholder.
|
|
141
|
+
*
|
|
142
|
+
* @since 0.1.0
|
|
143
|
+
* @category guards
|
|
144
|
+
* @slop
|
|
145
|
+
*/
|
|
146
|
+
export const isPlanned = (value) => reference(value) !== undefined;
|
|
147
|
+
//# sourceMappingURL=Planned.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Planned.js","sourceRoot":"","sources":["../../src/Planned.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The placeholder a body sees where a step result will be.\n *\n * A flow body runs at PLAN time, so its\n * own payload is real data while every step result is a value that does not\n * exist yet. That value has a name and one rule: a planned value may be **passed** — into a payload field,\n * into a branch, into a map — and field access is allowed, because it records a\n * reference path. It may never be **computed on**.\n *\n * {@link Planned} is branded, so arithmetic is a compile error.\n * {@link make} returns a strict proxy\n * whose `Symbol.toPrimitive`, `valueOf`, `toString`, `toJSON`, and call traps\n * throw {@link module:GraphBuildError.GraphBuildError} rather than let a plan be\n * built around `NaN` or `\"[object Object]\"`. The lenient proxy this adapts\n * (`@smthrs/core`'s `Graph.plannedValue`) answered `toPrimitive` with a\n * readable string, which is exactly the silent-wrong-plan path this module\n * exists to close.\n *\n * JavaScript does not expose traps for `Boolean(value)` or strict identity\n * (`value === other`), so those operations cannot be refused at run time.\n * They reveal only proxy truthiness or identity and never the planned result;\n * authors must use {@link module:Node.branch} for decisions on real values.\n * Type-aware ESLint `@typescript-eslint/strict-boolean-expressions` rejects\n * planned conditions, but does not reject explicit Boolean coercion or all\n * reference comparisons. Those still require review.\n *\n * @since 0.1.0\n */\n\nimport type * as Types from \"effect/Types\"\nimport { GraphBuildError } from \"./GraphBuildError.ts\"\n\n/**\n * The brand carried by every planned value, and the key its reference is read\n * from. Interned with `Symbol.for` so a value that crossed a module boundary is\n * still recognised. The interned symbol is a recognition aid, not a\n * capability, so {@link reference} validates the value stored under it.\n *\n * @since 0.1.0\n * @category symbols\n * @slop\n */\nexport const TypeId: unique symbol = Symbol.for(\"@smthrs/plan/Planned\")\n\n/**\n * The type-level form of {@link TypeId}.\n *\n * @since 0.1.0\n * @category symbols\n * @slop\n */\nexport type TypeId = typeof TypeId\n\n/**\n * What a planned value points at: the node that will produce the result, and\n * the property path read from it. `path` is empty for the result itself.\n *\n * @since 0.1.0\n * @category models\n * @slop\n */\nexport interface Reference {\n readonly node: string\n readonly path: ReadonlyArray<string>\n}\n\n/**\n * A {@link Reference} plus the phantom type of the value it stands for, so\n * `Planned<string>` and `Planned<number>` are not interchangeable.\n *\n * @since 0.1.0\n * @category models\n * @slop\n */\nexport interface Identity<out T> extends Reference {\n readonly _T: Types.Covariant<T>\n}\n\n/**\n * A step result that has not been produced yet.\n *\n * The brand rejects arithmetic and ordinary value arguments. The mapped half keeps field\n * access typed — `result.files` is a `Planned` of the field, which is what\n * makes \"pass it on, never compute on it\" expressible rather than merely\n * documented.\n *\n * @since 0.1.0\n * @category models\n * @slop\n */\nexport type Planned<T> =\n & { readonly [TypeId]: Identity<T> }\n & ([T] extends [object] ? { readonly [K in keyof T]: Planned<T[K]> } : unknown)\n\n/**\n * The operations that are computation rather than reference, keyed by the\n * property that performs them. `toJSON` is here because `JSON.stringify` reads\n * it, and a plan that serialized a placeholder would look correct.\n *\n * @private\n */\nconst refusals = new Map<PropertyKey, string>([\n [Symbol.toPrimitive, \"Symbol.toPrimitive\"],\n [\"valueOf\", \"valueOf\"],\n [\"toString\", \"toString\"],\n [\"toJSON\", \"toJSON\"]\n])\n\n/** @private */\nconst describe = (reference: Reference): string =>\n reference.path.length === 0 ? reference.node : `${reference.node}.${reference.path.join(\".\")}`\n\n/**\n * The one refusal this module raises. The guidance is spelled out rather than\n * linked because the author reading it is inside a body and needs the\n * correction on the spot.\n *\n * @private\n */\nconst refuse = (reference: Reference, operation: string): GraphBuildError =>\n new GraphBuildError({\n code: \"planned_value_computed\",\n node: reference.node,\n path: reference.path,\n message: `Planned value ${describe(reference)} was computed at plan time by ${operation}. ` +\n \"Use Node.map to compute, Node.branch to decide, or pass the value into a payload.\"\n })\n\n/**\n * The proxy itself. `_T` of {@link Identity} is a phantom and never exists at\n * run time, so the trap carries the plain {@link Reference}.\n *\n * @private\n */\nconst placeholder = (reference: Reference): unknown => {\n /* v8 ignore next -- a Proxy application always enters the `apply` trap below, so the callable target is unreachable by construction */\n const target = (): undefined => undefined\n return new Proxy(target, {\n get: (_target, key) => {\n if (key === TypeId) return reference\n const refusal = refusals.get(key)\n if (refusal !== undefined) throw refuse(reference, refusal)\n // Not a thenable: a body that returns a planned value inside a promise\n // must resolve to the placeholder, not hang awaiting a proxy.\n if (key === \"then\") return undefined\n return placeholder({ node: reference.node, path: [...reference.path, String(key)] })\n },\n apply: () => {\n throw refuse(reference, \"function application\")\n },\n has: () => {\n throw refuse(reference, \"property existence (`in`)\")\n },\n ownKeys: () => {\n throw refuse(reference, \"property enumeration\")\n }\n })\n}\n\n/**\n * Creates the strict placeholder standing for a node's result.\n *\n * Planning hands one of these to every builder that consumes an upstream\n * value — a branch arm, a continuation — and reads the {@link Reference} back\n * off whatever the builder passed it into.\n *\n * @since 0.1.0\n * @category constructors\n * @slop\n */\nexport const make = <T>(node: string): Planned<T> => placeholder({ node, path: [] }) as Planned<T>\n\n/** @private */\nconst carrier = (value: unknown): value is { readonly [TypeId]?: Reference } =>\n (typeof value === \"object\" && value !== null) || typeof value === \"function\"\n\n/** @private */\nconst validReference = (value: unknown): value is Reference =>\n typeof value === \"object\" && value !== null &&\n typeof (value as Reference).node === \"string\" && (value as Reference).node.length > 0 &&\n Array.isArray((value as Reference).path) &&\n Array.from((value as Reference).path).every((segment) => typeof segment === \"string\")\n\n/**\n * Reads the reference a planned value records, or `undefined` for anything\n * else. An interned symbol is a recognition aid rather than a capability, so a\n * forged carrier is accepted only when its node and path have the complete\n * reference shape. This is how a payload is scanned for the upstream results\n * it consumes.\n *\n * @since 0.1.0\n * @category accessors\n * @slop\n */\nexport const reference = (value: unknown): Reference | undefined => {\n if (!carrier(value)) return undefined\n const candidate = value[TypeId]\n return validReference(candidate) ? candidate : undefined\n}\n\n/**\n * Checks whether a value is a planned placeholder.\n *\n * @since 0.1.0\n * @category guards\n * @slop\n */\nexport const isPlanned = (value: unknown): value is Planned<unknown> => reference(value) !== undefined\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bounded repetition does when it reaches its ceiling.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE ceiling vocabulary. `@smthrs/flow`'s `Poll.make` takes it as
|
|
5
|
+
* `onTimeout`, for the attempt a poll gives up on, and `@smthrs/patterns`'
|
|
6
|
+
* `Loop` takes it as `onMaxReached`, for the iteration a loop gives up on. It
|
|
7
|
+
* was the same two words written out in both packages, with no edge between
|
|
8
|
+
* them, so a third member added on one side would have been invisible to the
|
|
9
|
+
* other. It lives here because `@smthrs/plan` is the package both already
|
|
10
|
+
* depend on.
|
|
11
|
+
*
|
|
12
|
+
* @since 1.0.0-rc.0
|
|
13
|
+
*/
|
|
14
|
+
import * as Schema from "effect/Schema";
|
|
15
|
+
/**
|
|
16
|
+
* What a bounded repetition answers with at its ceiling: `fail` raises the
|
|
17
|
+
* repetition's own typed failure, and `return-last` settles with the last
|
|
18
|
+
* value it produced.
|
|
19
|
+
*
|
|
20
|
+
* @category models
|
|
21
|
+
* @since 1.0.0-rc.0
|
|
22
|
+
*/
|
|
23
|
+
export declare const AtCeiling: Schema.Literals<readonly ["fail", "return-last"]>;
|
|
24
|
+
/**
|
|
25
|
+
* The value form of {@link AtCeiling}.
|
|
26
|
+
*
|
|
27
|
+
* @category models
|
|
28
|
+
* @since 1.0.0-rc.0
|
|
29
|
+
*/
|
|
30
|
+
export type AtCeiling = typeof AtCeiling.Type;
|
|
31
|
+
//# sourceMappingURL=Repetition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Repetition.d.ts","sourceRoot":"","sources":["../../src/Repetition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,mDAA2C,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bounded repetition does when it reaches its ceiling.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE ceiling vocabulary. `@smthrs/flow`'s `Poll.make` takes it as
|
|
5
|
+
* `onTimeout`, for the attempt a poll gives up on, and `@smthrs/patterns`'
|
|
6
|
+
* `Loop` takes it as `onMaxReached`, for the iteration a loop gives up on. It
|
|
7
|
+
* was the same two words written out in both packages, with no edge between
|
|
8
|
+
* them, so a third member added on one side would have been invisible to the
|
|
9
|
+
* other. It lives here because `@smthrs/plan` is the package both already
|
|
10
|
+
* depend on.
|
|
11
|
+
*
|
|
12
|
+
* @since 1.0.0-rc.0
|
|
13
|
+
*/
|
|
14
|
+
import * as Schema from "effect/Schema";
|
|
15
|
+
/**
|
|
16
|
+
* What a bounded repetition answers with at its ceiling: `fail` raises the
|
|
17
|
+
* repetition's own typed failure, and `return-last` settles with the last
|
|
18
|
+
* value it produced.
|
|
19
|
+
*
|
|
20
|
+
* @category models
|
|
21
|
+
* @since 1.0.0-rc.0
|
|
22
|
+
*/
|
|
23
|
+
export const AtCeiling = Schema.Literals(["fail", "return-last"]);
|
|
24
|
+
//# sourceMappingURL=Repetition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Repetition.js","sourceRoot":"","sources":["../../src/Repetition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA","sourcesContent":["/**\n * What a bounded repetition does when it reaches its ceiling.\n *\n * This is the ONE ceiling vocabulary. `@smthrs/flow`'s `Poll.make` takes it as\n * `onTimeout`, for the attempt a poll gives up on, and `@smthrs/patterns`'\n * `Loop` takes it as `onMaxReached`, for the iteration a loop gives up on. It\n * was the same two words written out in both packages, with no edge between\n * them, so a third member added on one side would have been invisible to the\n * other. It lives here because `@smthrs/plan` is the package both already\n * depend on.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as Schema from \"effect/Schema\"\n\n/**\n * What a bounded repetition answers with at its ceiling: `fail` raises the\n * repetition's own typed failure, and `return-last` settles with the last\n * value it produced.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const AtCeiling = Schema.Literals([\"fail\", \"return-last\"])\n\n/**\n * The value form of {@link AtCeiling}.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type AtCeiling = typeof AtCeiling.Type\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ready-work admission policy. This module decides priority, aging,
|
|
3
|
+
* deterministic ties, and step/agent capacity. It does not decide which nodes
|
|
4
|
+
* are ready, evaluate branches, launch effects, or own execution lifetimes.
|
|
5
|
+
* Those decisions must use the compiled graph and the caller's activation
|
|
6
|
+
* state before presenting candidates here.
|
|
7
|
+
*
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
import type * as Plan from "./Plan.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Admission limits, independent of graph width and provider capacity.
|
|
13
|
+
* Omitted limits are unbounded within JavaScript's safe-integer range.
|
|
14
|
+
*
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
* @category models
|
|
17
|
+
*/
|
|
18
|
+
export interface Concurrency {
|
|
19
|
+
readonly steps?: number | undefined;
|
|
20
|
+
readonly agents?: number | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Currently held permits. Every agent also holds a step permit.
|
|
24
|
+
*
|
|
25
|
+
* @since 1.0.0
|
|
26
|
+
* @category models
|
|
27
|
+
*/
|
|
28
|
+
export interface Usage {
|
|
29
|
+
readonly steps: number;
|
|
30
|
+
readonly agents: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The part of a plan node admission reads. Declared once here so the three
|
|
34
|
+
* places that name it, {@link Candidate}, {@link Admission} and
|
|
35
|
+
* {@link Policy.admit}, cannot drift apart.
|
|
36
|
+
*/
|
|
37
|
+
type Schedulable = Pick<Plan.PlanNode, "id" | "kind" | "priority">;
|
|
38
|
+
/**
|
|
39
|
+
* A ready node's scheduling state. `order` is its position in the compiled
|
|
40
|
+
* plan, not arrival order. `waited` counts capacity-constrained admission
|
|
41
|
+
* passes, not wall-clock time.
|
|
42
|
+
*
|
|
43
|
+
* @since 1.0.0
|
|
44
|
+
* @category models
|
|
45
|
+
*/
|
|
46
|
+
export interface Candidate<N extends Schedulable = Plan.PlanNode> {
|
|
47
|
+
readonly node: N;
|
|
48
|
+
readonly order: number;
|
|
49
|
+
readonly waited: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One admission decision. Deferred candidates carry their incremented age;
|
|
53
|
+
* admitted candidates retain their age for dispatch diagnostics. No input is
|
|
54
|
+
* mutated and neither array owns or freezes the caller's node values.
|
|
55
|
+
*
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
* @category models
|
|
58
|
+
*/
|
|
59
|
+
export interface Admission<N extends Schedulable = Plan.PlanNode> {
|
|
60
|
+
readonly admitted: ReadonlyArray<Candidate<N>>;
|
|
61
|
+
readonly deferred: ReadonlyArray<Candidate<N>>;
|
|
62
|
+
readonly agents: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Pure policy shared by execution coordinators.
|
|
66
|
+
*
|
|
67
|
+
* @since 1.0.0
|
|
68
|
+
* @category models
|
|
69
|
+
*/
|
|
70
|
+
export interface Policy {
|
|
71
|
+
readonly limits: Usage;
|
|
72
|
+
readonly admit: <N extends Schedulable>(ready: ReadonlyArray<Candidate<N>>, active: Usage) => Admission<N>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Snapshots validated limits. Admission orders by declared priority plus age,
|
|
76
|
+
* using exact integer arithmetic even when their sum exceeds MAX_SAFE_INTEGER.
|
|
77
|
+
* Ties preserve compiled plan order. An agent blocked by its subset cap does
|
|
78
|
+
* not prevent ordinary ready work from using spare step capacity.
|
|
79
|
+
*
|
|
80
|
+
* Invalid candidate/permit state is refused before any decision is returned.
|
|
81
|
+
* At MAX_SAFE_INTEGER the age saturates; a bounded plan cannot require that
|
|
82
|
+
* many capacity-constrained passes to drain finite work.
|
|
83
|
+
*
|
|
84
|
+
* @since 1.0.0
|
|
85
|
+
* @category constructors
|
|
86
|
+
*/
|
|
87
|
+
export declare const make: (concurrency?: Concurrency) => Policy;
|
|
88
|
+
export {};
|
|
89
|
+
//# sourceMappingURL=Scheduling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduling.d.ts","sourceRoot":"","sources":["../../src/Scheduling.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACrC;AAED;;;;;GAKG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,KAAK,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,CAAA;AAElE;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,WAAW,GAAG,IAAI,CAAC,QAAQ;IAC9D,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IAChB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,WAAW,GAAG,IAAI,CAAC,QAAQ;IAC9D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,SAAS,WAAW,EACpC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAClC,MAAM,EAAE,KAAK,KACV,SAAS,CAAC,CAAC,CAAC,CAAA;CAClB;AASD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,IAAI,GAAI,cAAa,WAAgB,KAAG,MAiDpD,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ready-work admission policy. This module decides priority, aging,
|
|
3
|
+
* deterministic ties, and step/agent capacity. It does not decide which nodes
|
|
4
|
+
* are ready, evaluate branches, launch effects, or own execution lifetimes.
|
|
5
|
+
* Those decisions must use the compiled graph and the caller's activation
|
|
6
|
+
* state before presenting candidates here.
|
|
7
|
+
*
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
const integer = (name, value, minimum) => {
|
|
11
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
12
|
+
throw new RangeError(`${name} must be a ${minimum === 1 ? "positive" : "non-negative"} safe integer`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Snapshots validated limits. Admission orders by declared priority plus age,
|
|
18
|
+
* using exact integer arithmetic even when their sum exceeds MAX_SAFE_INTEGER.
|
|
19
|
+
* Ties preserve compiled plan order. An agent blocked by its subset cap does
|
|
20
|
+
* not prevent ordinary ready work from using spare step capacity.
|
|
21
|
+
*
|
|
22
|
+
* Invalid candidate/permit state is refused before any decision is returned.
|
|
23
|
+
* At MAX_SAFE_INTEGER the age saturates; a bounded plan cannot require that
|
|
24
|
+
* many capacity-constrained passes to drain finite work.
|
|
25
|
+
*
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
* @category constructors
|
|
28
|
+
*/
|
|
29
|
+
export const make = (concurrency = {}) => {
|
|
30
|
+
const limits = Object.freeze({
|
|
31
|
+
steps: integer("concurrency.steps", concurrency.steps ?? Number.MAX_SAFE_INTEGER, 1),
|
|
32
|
+
agents: integer("concurrency.agents", concurrency.agents ?? Number.MAX_SAFE_INTEGER, 1)
|
|
33
|
+
});
|
|
34
|
+
const admit = (ready, active) => {
|
|
35
|
+
integer("active.steps", active.steps, 0);
|
|
36
|
+
integer("active.agents", active.agents, 0);
|
|
37
|
+
if (active.agents > active.steps)
|
|
38
|
+
throw new RangeError("active.agents cannot exceed active.steps");
|
|
39
|
+
const ids = new Set();
|
|
40
|
+
const positions = new Set();
|
|
41
|
+
const ranked = ready.map((candidate) => {
|
|
42
|
+
const { node, order, waited } = candidate;
|
|
43
|
+
integer("candidate.order", order, 0);
|
|
44
|
+
integer("candidate.waited", waited, 0);
|
|
45
|
+
if (!Number.isSafeInteger(node.priority))
|
|
46
|
+
throw new RangeError("candidate.priority must be a safe integer");
|
|
47
|
+
if (node.kind !== "step" && node.kind !== "agent" && node.kind !== "merge") {
|
|
48
|
+
throw new RangeError("invalid candidate kind");
|
|
49
|
+
}
|
|
50
|
+
if (typeof node.id !== "string" || node.id === "" || ids.has(node.id)) {
|
|
51
|
+
throw new RangeError("candidate ids must be non-empty and unique");
|
|
52
|
+
}
|
|
53
|
+
if (positions.has(order))
|
|
54
|
+
throw new RangeError("candidate plan positions must be unique");
|
|
55
|
+
ids.add(node.id);
|
|
56
|
+
positions.add(order);
|
|
57
|
+
return { candidate, priority: BigInt(node.priority) + BigInt(waited) };
|
|
58
|
+
});
|
|
59
|
+
ranked.sort((left, right) => left.priority === right.priority
|
|
60
|
+
? left.candidate.order - right.candidate.order
|
|
61
|
+
: left.priority > right.priority
|
|
62
|
+
? -1
|
|
63
|
+
: 1);
|
|
64
|
+
const admitted = [];
|
|
65
|
+
const deferred = [];
|
|
66
|
+
let agents = 0;
|
|
67
|
+
for (const { candidate } of ranked) {
|
|
68
|
+
const isAgent = candidate.node.kind === "agent";
|
|
69
|
+
if (admitted.length >= limits.steps - active.steps || (isAgent && agents >= limits.agents - active.agents)) {
|
|
70
|
+
deferred.push(Object.freeze({ ...candidate, waited: Math.min(Number.MAX_SAFE_INTEGER, candidate.waited + 1) }));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
admitted.push(Object.freeze({ ...candidate }));
|
|
74
|
+
if (isAgent)
|
|
75
|
+
agents++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({ admitted: Object.freeze(admitted), deferred: Object.freeze(deferred), agents });
|
|
79
|
+
};
|
|
80
|
+
return Object.freeze({ limits, admit });
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=Scheduling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduling.js","sourceRoot":"","sources":["../../src/Scheduling.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared ready-work admission policy. This module decides priority, aging,\n * deterministic ties, and step/agent capacity. It does not decide which nodes\n * are ready, evaluate branches, launch effects, or own execution lifetimes.\n * Those decisions must use the compiled graph and the caller's activation\n * state before presenting candidates here.\n *\n * @since 1.0.0\n */\n\nimport type * as Plan from \"./Plan.ts\"\n\n/**\n * Admission limits, independent of graph width and provider capacity.\n * Omitted limits are unbounded within JavaScript's safe-integer range.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface Concurrency {\n readonly steps?: number | undefined\n readonly agents?: number | undefined\n}\n\n/**\n * Currently held permits. Every agent also holds a step permit.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface Usage {\n readonly steps: number\n readonly agents: number\n}\n\n/**\n * The part of a plan node admission reads. Declared once here so the three\n * places that name it, {@link Candidate}, {@link Admission} and\n * {@link Policy.admit}, cannot drift apart.\n */\ntype Schedulable = Pick<Plan.PlanNode, \"id\" | \"kind\" | \"priority\">\n\n/**\n * A ready node's scheduling state. `order` is its position in the compiled\n * plan, not arrival order. `waited` counts capacity-constrained admission\n * passes, not wall-clock time.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface Candidate<N extends Schedulable = Plan.PlanNode> {\n readonly node: N\n readonly order: number\n readonly waited: number\n}\n\n/**\n * One admission decision. Deferred candidates carry their incremented age;\n * admitted candidates retain their age for dispatch diagnostics. No input is\n * mutated and neither array owns or freezes the caller's node values.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface Admission<N extends Schedulable = Plan.PlanNode> {\n readonly admitted: ReadonlyArray<Candidate<N>>\n readonly deferred: ReadonlyArray<Candidate<N>>\n readonly agents: number\n}\n\n/**\n * Pure policy shared by execution coordinators.\n *\n * @since 1.0.0\n * @category models\n */\nexport interface Policy {\n readonly limits: Usage\n readonly admit: <N extends Schedulable>(\n ready: ReadonlyArray<Candidate<N>>,\n active: Usage\n ) => Admission<N>\n}\n\nconst integer = (name: string, value: number, minimum: number): number => {\n if (!Number.isSafeInteger(value) || value < minimum) {\n throw new RangeError(`${name} must be a ${minimum === 1 ? \"positive\" : \"non-negative\"} safe integer`)\n }\n return value\n}\n\n/**\n * Snapshots validated limits. Admission orders by declared priority plus age,\n * using exact integer arithmetic even when their sum exceeds MAX_SAFE_INTEGER.\n * Ties preserve compiled plan order. An agent blocked by its subset cap does\n * not prevent ordinary ready work from using spare step capacity.\n *\n * Invalid candidate/permit state is refused before any decision is returned.\n * At MAX_SAFE_INTEGER the age saturates; a bounded plan cannot require that\n * many capacity-constrained passes to drain finite work.\n *\n * @since 1.0.0\n * @category constructors\n */\nexport const make = (concurrency: Concurrency = {}): Policy => {\n const limits = Object.freeze({\n steps: integer(\"concurrency.steps\", concurrency.steps ?? Number.MAX_SAFE_INTEGER, 1),\n agents: integer(\"concurrency.agents\", concurrency.agents ?? Number.MAX_SAFE_INTEGER, 1)\n })\n const admit: Policy[\"admit\"] = (ready, active) => {\n integer(\"active.steps\", active.steps, 0)\n integer(\"active.agents\", active.agents, 0)\n if (active.agents > active.steps) throw new RangeError(\"active.agents cannot exceed active.steps\")\n const ids = new Set<string>()\n const positions = new Set<number>()\n const ranked = ready.map((candidate) => {\n const { node, order, waited } = candidate\n integer(\"candidate.order\", order, 0)\n integer(\"candidate.waited\", waited, 0)\n if (!Number.isSafeInteger(node.priority)) throw new RangeError(\"candidate.priority must be a safe integer\")\n if (node.kind !== \"step\" && node.kind !== \"agent\" && node.kind !== \"merge\") {\n throw new RangeError(\"invalid candidate kind\")\n }\n if (typeof node.id !== \"string\" || node.id === \"\" || ids.has(node.id)) {\n throw new RangeError(\"candidate ids must be non-empty and unique\")\n }\n if (positions.has(order)) throw new RangeError(\"candidate plan positions must be unique\")\n ids.add(node.id)\n positions.add(order)\n return { candidate, priority: BigInt(node.priority) + BigInt(waited) }\n })\n ranked.sort((left, right) =>\n left.priority === right.priority\n ? left.candidate.order - right.candidate.order\n : left.priority > right.priority\n ? -1\n : 1\n )\n const admitted: Array<typeof ready[number]> = []\n const deferred: Array<typeof ready[number]> = []\n let agents = 0\n for (const { candidate } of ranked) {\n const isAgent = candidate.node.kind === \"agent\"\n if (admitted.length >= limits.steps - active.steps || (isAgent && agents >= limits.agents - active.agents)) {\n deferred.push(Object.freeze({ ...candidate, waited: Math.min(Number.MAX_SAFE_INTEGER, candidate.waited + 1) }))\n } else {\n admitted.push(Object.freeze({ ...candidate }))\n if (isAgent) agents++\n }\n }\n return Object.freeze({ admitted: Object.freeze(admitted), deferred: Object.freeze(deferred), agents })\n }\n return Object.freeze({ limits, admit })\n}\n"]}
|