@pond-ts/process 0.54.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +5914 -0
  2. package/LICENSE +21 -0
  3. package/README.md +345 -0
  4. package/dist/cjs-fallback.cjs +15 -0
  5. package/dist/column.d.ts +197 -0
  6. package/dist/column.js +306 -0
  7. package/dist/errors.d.ts +22 -0
  8. package/dist/errors.js +25 -0
  9. package/dist/graph.d.ts +89 -0
  10. package/dist/graph.js +133 -0
  11. package/dist/index.d.ts +59 -0
  12. package/dist/index.js +45 -0
  13. package/dist/node.d.ts +151 -0
  14. package/dist/node.js +268 -0
  15. package/dist/plan/builder.d.ts +138 -0
  16. package/dist/plan/builder.js +166 -0
  17. package/dist/plan/fluent.d.ts +93 -0
  18. package/dist/plan/fluent.js +140 -0
  19. package/dist/plan/folds.d.ts +25 -0
  20. package/dist/plan/folds.js +190 -0
  21. package/dist/plan/graph.d.ts +171 -0
  22. package/dist/plan/graph.js +658 -0
  23. package/dist/plan/history.d.ts +61 -0
  24. package/dist/plan/history.js +82 -0
  25. package/dist/plan/host.d.ts +173 -0
  26. package/dist/plan/host.js +234 -0
  27. package/dist/plan/identity.d.ts +81 -0
  28. package/dist/plan/identity.js +158 -0
  29. package/dist/plan/params.d.ts +15 -0
  30. package/dist/plan/params.js +26 -0
  31. package/dist/plan/registry.d.ts +162 -0
  32. package/dist/plan/registry.js +422 -0
  33. package/dist/plan/run.d.ts +211 -0
  34. package/dist/plan/run.js +360 -0
  35. package/dist/plan/slots.d.ts +65 -0
  36. package/dist/plan/slots.js +114 -0
  37. package/dist/plan/source.d.ts +49 -0
  38. package/dist/plan/source.js +54 -0
  39. package/dist/plan/types.d.ts +376 -0
  40. package/dist/plan/types.js +20 -0
  41. package/dist/pool/index.d.ts +15 -0
  42. package/dist/pool/index.js +12 -0
  43. package/dist/pool/pool.d.ts +92 -0
  44. package/dist/pool/pool.js +237 -0
  45. package/dist/pool/protocol.d.ts +48 -0
  46. package/dist/pool/protocol.js +9 -0
  47. package/dist/pool/wire.d.ts +52 -0
  48. package/dist/pool/wire.js +95 -0
  49. package/dist/pool/worker.d.ts +22 -0
  50. package/dist/pool/worker.js +80 -0
  51. package/dist/port.d.ts +79 -0
  52. package/dist/port.js +222 -0
  53. package/dist/source.d.ts +161 -0
  54. package/dist/source.js +182 -0
  55. package/dist/types.d.ts +77 -0
  56. package/dist/types.js +26 -0
  57. package/package.json +50 -0
package/dist/node.d.ts ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Nodes: units of computation with typed ports, and the pull-based
3
+ * evaluation that drives them.
4
+ *
5
+ * ## Evaluation model
6
+ *
7
+ * Two mechanisms, doing two different jobs:
8
+ *
9
+ * - **Dirty marking (push)** tells a node "something upstream moved,
10
+ * revalidate before answering." It propagates from the changed node
11
+ * through every downstream edge, cutting off at nodes already marked,
12
+ * so a change costs O(affected nodes) regardless of graph size. A
13
+ * clean node answers `get()` from cache without walking anything.
14
+ *
15
+ * - **Version stamps (pull)** tell a node whether the change was real.
16
+ * Each outlet's version increments only when a recomputed value
17
+ * actually differs, and each node records the input versions it last
18
+ * computed against. A dirty node whose input versions all match skips
19
+ * `compute` entirely and stays cached.
20
+ *
21
+ * The second is what makes the layer worth having over plain function
22
+ * calls: setting a source that happens to produce an identical
23
+ * downstream value stops the cascade there, so expensive transforms
24
+ * further down never run. Dirty marking alone (as in a naive dataflow
25
+ * graph) would recompute the whole subtree on every touch.
26
+ */
27
+ import { Inlet, Outlet } from './port.js';
28
+ import type { PortSpec, PortSpecMap, PortValue, PortValues } from './types.js';
29
+ /** The inlet map exposed as `node.in`. */
30
+ export type InletsFor<In extends PortSpecMap> = {
31
+ readonly [K in keyof In]: Inlet<PortValue<In[K]>>;
32
+ };
33
+ /** The outlet map exposed as `node.out`. */
34
+ export type OutletsFor<Out extends PortSpecMap> = {
35
+ readonly [K in keyof Out]: Outlet<PortValue<Out[K]>>;
36
+ };
37
+ /** Declares a reusable node type. Passed to {@link defineNode}. */
38
+ export interface NodeSpec<In extends PortSpecMap, Out extends PortSpecMap> {
39
+ /** Stable identifier for this node type, used in errors and `toJSON()`. */
40
+ readonly kind: string;
41
+ readonly inputs: In;
42
+ readonly outputs: Out;
43
+ /**
44
+ * Produces every declared output from the current input values.
45
+ *
46
+ * Called only when an input version actually changed. Must be a pure
47
+ * function of its inputs — the engine caches the result and will not
48
+ * call it again until something upstream moves.
49
+ */
50
+ compute(inputs: PortValues<In>): PortValues<Out>;
51
+ }
52
+ /**
53
+ * A unit of computation with typed input and output ports.
54
+ *
55
+ * Construct via {@link defineNode} or {@link derive} rather than
56
+ * directly — both wire the port maps up from the spec.
57
+ */
58
+ export declare class Node<In extends PortSpecMap = PortSpecMap, Out extends PortSpecMap = PortSpecMap> {
59
+ #private;
60
+ /** Unique within the process. Stable across a graph's lifetime. */
61
+ readonly id: string;
62
+ /** The node type's name, from its spec. */
63
+ readonly kind: string;
64
+ /** Typed input ports, keyed as declared. */
65
+ readonly in: InletsFor<In>;
66
+ /** Typed output ports, keyed as declared. */
67
+ readonly out: OutletsFor<Out>;
68
+ constructor(spec: NodeSpec<In, Out>);
69
+ /** Whether this node will revalidate on the next pull. */
70
+ get dirty(): boolean;
71
+ /**
72
+ * The error thrown by the most recent `compute`, or `undefined` if the
73
+ * last evaluation succeeded (or none has run). Cached alongside the
74
+ * value: an errored node rethrows without re-running `compute` until
75
+ * an input changes, so a broken node stays cheap to poll — useful when
76
+ * rendering a graph where one node is misconfigured.
77
+ */
78
+ get error(): unknown;
79
+ /**
80
+ * Forces this node to recompute on the next pull, even if no input
81
+ * version changed, and marks everything downstream dirty.
82
+ *
83
+ * Needed when a node's output depends on something the graph can't
84
+ * see — a settable source value, an external mutable resource.
85
+ */
86
+ invalidate(): void;
87
+ }
88
+ /** Creates instances of a declared node type. */
89
+ export interface NodeFactory<In extends PortSpecMap, Out extends PortSpecMap> {
90
+ (): Node<In, Out>;
91
+ /** The `kind` of the nodes this factory produces. */
92
+ readonly kind: string;
93
+ }
94
+ /**
95
+ * Declares a reusable node type and returns a factory for it.
96
+ *
97
+ * ```ts
98
+ * const Stats = defineNode({
99
+ * kind: 'stats',
100
+ * inputs: { series: port<TimeSeries<Schema>>() },
101
+ * outputs: { mean: port<number>(), max: port<number>() },
102
+ * compute: ({ series }) => ({
103
+ * mean: series.column('cpu').mean(),
104
+ * max: series.column('cpu').max(),
105
+ * }),
106
+ * });
107
+ *
108
+ * const stats = Stats();
109
+ * raw.out.value.connect(stats.in.series);
110
+ * stats.out.mean.get();
111
+ * ```
112
+ *
113
+ * For a node type that needs per-instance configuration, close over it:
114
+ * `const smoother = (span: number) => defineNode({ ... })();`
115
+ */
116
+ export declare function defineNode<In extends PortSpecMap, Out extends PortSpecMap>(spec: NodeSpec<In, Out>): NodeFactory<In, Out>;
117
+ /** Extracts the value type carried by an {@link Outlet}. */
118
+ export type OutletValue<O> = O extends Outlet<infer T> ? T : never;
119
+ /** The spec map implied by a record of source outlets. */
120
+ export type SpecsForOutlets<M> = {
121
+ [K in keyof M]: PortSpec<OutletValue<M[K]>>;
122
+ };
123
+ /** The single-output spec map produced by {@link derive}. */
124
+ export type DerivedOutput<R> = {
125
+ readonly value: PortSpec<R>;
126
+ };
127
+ /**
128
+ * Builds a single-output node and wires it to its sources in one step —
129
+ * the shape most graph edges take.
130
+ *
131
+ * ```ts
132
+ * const smoothed = derive({ series: raw.out.value }, ({ series }) =>
133
+ * series.smooth('cpu', { method: 'ema', alpha: 0.3 }),
134
+ * );
135
+ * smoothed.out.value.get();
136
+ * ```
137
+ *
138
+ * Equivalent to {@link defineNode} with one output named `value`, an
139
+ * input per source key, and the connections already made. Reach for
140
+ * `defineNode` when a node has several outputs, or when the node type is
141
+ * reused across graphs and deserves a name.
142
+ */
143
+ export declare function derive<Sources extends Readonly<Record<string, Outlet<any>>>, R>(sources: Sources, compute: (inputs: {
144
+ [K in keyof Sources]: OutletValue<Sources[K]>;
145
+ }) => R, options?: {
146
+ /** Node type name for errors and `toJSON()`. Defaults to `'derive'`. */
147
+ readonly kind?: string;
148
+ /** Change test for the output. Defaults to `Object.is`. */
149
+ readonly equals?: (a: R, b: R) => boolean;
150
+ }): Node<SpecsForOutlets<Sources>, DerivedOutput<R>>;
151
+ //# sourceMappingURL=node.d.ts.map
package/dist/node.js ADDED
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Nodes: units of computation with typed ports, and the pull-based
3
+ * evaluation that drives them.
4
+ *
5
+ * ## Evaluation model
6
+ *
7
+ * Two mechanisms, doing two different jobs:
8
+ *
9
+ * - **Dirty marking (push)** tells a node "something upstream moved,
10
+ * revalidate before answering." It propagates from the changed node
11
+ * through every downstream edge, cutting off at nodes already marked,
12
+ * so a change costs O(affected nodes) regardless of graph size. A
13
+ * clean node answers `get()` from cache without walking anything.
14
+ *
15
+ * - **Version stamps (pull)** tell a node whether the change was real.
16
+ * Each outlet's version increments only when a recomputed value
17
+ * actually differs, and each node records the input versions it last
18
+ * computed against. A dirty node whose input versions all match skips
19
+ * `compute` entirely and stays cached.
20
+ *
21
+ * The second is what makes the layer worth having over plain function
22
+ * calls: setting a source that happens to produce an identical
23
+ * downstream value stops the cascade there, so expensive transforms
24
+ * further down never run. Dirty marking alone (as in a naive dataflow
25
+ * graph) would recompute the whole subtree on every touch.
26
+ */
27
+ import { CycleError, MissingOutputError, ProcessError } from './errors.js';
28
+ import { Inlet, Outlet } from './port.js';
29
+ let nextNodeId = 1;
30
+ /**
31
+ * A unit of computation with typed input and output ports.
32
+ *
33
+ * Construct via {@link defineNode} or {@link derive} rather than
34
+ * directly — both wire the port maps up from the spec.
35
+ */
36
+ export class Node {
37
+ /** Unique within the process. Stable across a graph's lifetime. */
38
+ id;
39
+ /** The node type's name, from its spec. */
40
+ kind;
41
+ /** Typed input ports, keyed as declared. */
42
+ in;
43
+ /** Typed output ports, keyed as declared. */
44
+ out;
45
+ #compute;
46
+ #inlets;
47
+ #outlets;
48
+ #dirty = true;
49
+ #computed = false;
50
+ #evaluating = false;
51
+ #inputVersions = new Map();
52
+ #error = undefined;
53
+ #hasError = false;
54
+ constructor(spec) {
55
+ this.id = `n${nextNodeId++}`;
56
+ this.kind = spec.kind;
57
+ this.#compute = (inputs) => spec.compute(inputs);
58
+ // Null-prototype maps: a port legitimately named `__proto__` would
59
+ // otherwise hit the prototype setter instead of defining a key,
60
+ // leaving the node with no ports at all and a hijacked `node.in`.
61
+ const inlets = Object.create(null);
62
+ for (const [name, portSpec] of Object.entries(spec.inputs)) {
63
+ inlets[name] = new Inlet(this, name, portSpec.defaultValue);
64
+ }
65
+ const outlets = Object.create(null);
66
+ for (const [name, portSpec] of Object.entries(spec.outputs)) {
67
+ outlets[name] = new Outlet(this, name, portSpec.equals);
68
+ }
69
+ this.in = Object.freeze(inlets);
70
+ this.out = Object.freeze(outlets);
71
+ this.#inlets = Object.values(inlets);
72
+ this.#outlets = Object.values(outlets);
73
+ }
74
+ /** Whether this node will revalidate on the next pull. */
75
+ get dirty() {
76
+ return this.#dirty;
77
+ }
78
+ /**
79
+ * The error thrown by the most recent `compute`, or `undefined` if the
80
+ * last evaluation succeeded (or none has run). Cached alongside the
81
+ * value: an errored node rethrows without re-running `compute` until
82
+ * an input changes, so a broken node stays cheap to poll — useful when
83
+ * rendering a graph where one node is misconfigured.
84
+ */
85
+ get error() {
86
+ return this.#error;
87
+ }
88
+ /**
89
+ * Forces this node to recompute on the next pull, even if no input
90
+ * version changed, and marks everything downstream dirty.
91
+ *
92
+ * Needed when a node's output depends on something the graph can't
93
+ * see — a settable source value, an external mutable resource.
94
+ */
95
+ invalidate() {
96
+ this.#computed = false;
97
+ this.markDirty();
98
+ }
99
+ /** @internal Marks stale and propagates downstream, cutting off at nodes already dirty. */
100
+ markDirty() {
101
+ if (this.#dirty)
102
+ return;
103
+ this.#dirty = true;
104
+ for (const outlet of this.#outlets)
105
+ outlet.invalidateDownstream();
106
+ }
107
+ /** @internal */
108
+ inletList() {
109
+ return this.#inlets;
110
+ }
111
+ /** @internal */
112
+ outletList() {
113
+ return this.#outlets;
114
+ }
115
+ /** @internal Brings this node's outlets up to date. See the module docstring. */
116
+ ensureFresh() {
117
+ // The re-entrancy check must precede the clean-node early return.
118
+ // `#dirty` is cleared before `compute` runs, so a compute that pulls
119
+ // its own node would otherwise take the early return and be handed
120
+ // the *previous* cached value — a silently stale result instead of
121
+ // the cycle it actually is.
122
+ if (this.#evaluating) {
123
+ throw new CycleError(`Node '${this.kind}' was pulled while it was still evaluating — its compute re-entered the graph`);
124
+ }
125
+ if (!this.#dirty) {
126
+ if (this.#hasError)
127
+ throw this.#error;
128
+ return;
129
+ }
130
+ this.#evaluating = true;
131
+ try {
132
+ // Refresh upstream first, then compare versions: a dirty mark says
133
+ // "maybe stale", the versions say whether anything really moved.
134
+ const versions = new Map();
135
+ let changed = !this.#computed;
136
+ for (const inlet of this.#inlets) {
137
+ try {
138
+ inlet.source?.node.ensureFresh();
139
+ }
140
+ catch (error) {
141
+ // Upstream failed. Drop any error cached from this node's own
142
+ // last compute: it is no longer why this node can't produce a
143
+ // value, and leaving it would point a graph inspector at the
144
+ // wrong node. The failing upstream keeps its own `error`.
145
+ this.#error = undefined;
146
+ this.#hasError = false;
147
+ throw error;
148
+ }
149
+ const version = inlet.sourceVersion();
150
+ versions.set(inlet.name, version);
151
+ if (this.#inputVersions.get(inlet.name) !== version)
152
+ changed = true;
153
+ }
154
+ this.#dirty = false;
155
+ if (!changed) {
156
+ if (this.#hasError)
157
+ throw this.#error;
158
+ return;
159
+ }
160
+ this.#inputVersions = versions;
161
+ this.#computed = true;
162
+ // Reading inputs, computing, and storing outputs share one catch:
163
+ // all three can fail, and a node that fails any of them must end
164
+ // up in the same state — error cached, rethrown on every pull
165
+ // until an input changes. Catching only around `compute` left a
166
+ // node that threw while reading an unconnected input marked clean
167
+ // with no value and no error, so the second pull reported a
168
+ // misleading "produced no value" instead of the real cause.
169
+ try {
170
+ const inputs = {};
171
+ for (const inlet of this.#inlets)
172
+ inputs[inlet.name] = inlet.get();
173
+ const produced = this.#compute(inputs);
174
+ if (produced === null || typeof produced !== 'object') {
175
+ throw new MissingOutputError(`Node '${this.kind}' compute must return an object of output values`);
176
+ }
177
+ const values = produced;
178
+ for (const outlet of this.#outlets) {
179
+ // `hasOwn`, not `in`: `in` walks the prototype chain, so an
180
+ // output named `toString` or `valueOf` would silently pass the
181
+ // guard and publish an `Object.prototype` member as its value.
182
+ if (!Object.hasOwn(values, outlet.name)) {
183
+ throw new MissingOutputError(`Node '${this.kind}' compute did not return output '${outlet.name}'`);
184
+ }
185
+ outlet.produce(values[outlet.name]);
186
+ }
187
+ this.#error = undefined;
188
+ this.#hasError = false;
189
+ }
190
+ catch (error) {
191
+ this.#error = error;
192
+ this.#hasError = true;
193
+ throw error;
194
+ }
195
+ }
196
+ finally {
197
+ this.#evaluating = false;
198
+ }
199
+ }
200
+ }
201
+ /**
202
+ * Declares a reusable node type and returns a factory for it.
203
+ *
204
+ * ```ts
205
+ * const Stats = defineNode({
206
+ * kind: 'stats',
207
+ * inputs: { series: port<TimeSeries<Schema>>() },
208
+ * outputs: { mean: port<number>(), max: port<number>() },
209
+ * compute: ({ series }) => ({
210
+ * mean: series.column('cpu').mean(),
211
+ * max: series.column('cpu').max(),
212
+ * }),
213
+ * });
214
+ *
215
+ * const stats = Stats();
216
+ * raw.out.value.connect(stats.in.series);
217
+ * stats.out.mean.get();
218
+ * ```
219
+ *
220
+ * For a node type that needs per-instance configuration, close over it:
221
+ * `const smoother = (span: number) => defineNode({ ... })();`
222
+ */
223
+ export function defineNode(spec) {
224
+ const factory = () => new Node(spec);
225
+ return Object.assign(factory, { kind: spec.kind });
226
+ }
227
+ /**
228
+ * Builds a single-output node and wires it to its sources in one step —
229
+ * the shape most graph edges take.
230
+ *
231
+ * ```ts
232
+ * const smoothed = derive({ series: raw.out.value }, ({ series }) =>
233
+ * series.smooth('cpu', { method: 'ema', alpha: 0.3 }),
234
+ * );
235
+ * smoothed.out.value.get();
236
+ * ```
237
+ *
238
+ * Equivalent to {@link defineNode} with one output named `value`, an
239
+ * input per source key, and the connections already made. Reach for
240
+ * `defineNode` when a node has several outputs, or when the node type is
241
+ * reused across graphs and deserves a name.
242
+ */
243
+ export function derive(sources, compute, options = {}) {
244
+ const inputs = {};
245
+ for (const name of Object.keys(sources))
246
+ inputs[name] = {};
247
+ const outputValue = options.equals
248
+ ? { equals: options.equals }
249
+ : {};
250
+ const node = new Node({
251
+ kind: options.kind ?? 'derive',
252
+ inputs: inputs,
253
+ outputs: { value: outputValue },
254
+ compute: (values) => ({
255
+ value: compute(values),
256
+ }),
257
+ });
258
+ for (const [name, outlet] of Object.entries(sources)) {
259
+ const inlet = node.in[name];
260
+ if (inlet === undefined) {
261
+ // Unreachable: the inputs above are built from these same keys.
262
+ throw new ProcessError(`Node '${node.kind}' has no input named '${name}'`);
263
+ }
264
+ outlet.connect(inlet);
265
+ }
266
+ return node;
267
+ }
268
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1,138 @@
1
+ /**
2
+ * A programmable API that **emits** a plan — [PND-PROCBUILD].
3
+ *
4
+ * Plans-as-data is right for a wire format and required for a cache key,
5
+ * but it is not how an application wants to *author* a graph. A consumer
6
+ * building studies over its own metrics should not be assembling nested
7
+ * JSON by hand.
8
+ *
9
+ * ```ts
10
+ * const g = plan('ACME_5m').as('bands_and_stretch');
11
+ * const bb = g.add('bb', 'bollinger', { period: 20 }, ['px']);
12
+ * const z = g.add('z', 'zscore', { period: 20 }, ['px']);
13
+ *
14
+ * g.expose('upper_band', bb.last({ output: 'Upper' }));
15
+ * g.expose('stretch', z.percentileRank());
16
+ * g.expose('band_series', bb.columns());
17
+ *
18
+ * host.run(g.toJSON());
19
+ * ```
20
+ *
21
+ * **Slots are what make this possible**, which is why this depends on
22
+ * [PND-PROCSLOT] rather than standing alone: a builder needs a stable
23
+ * handle to refer back to, and a content-addressed id cannot be one — it
24
+ * changes the moment a param does. A slot is exactly a handle, and
25
+ * passing `bb` rather than the string `'bb'` is what turns a typo into a
26
+ * compile error.
27
+ *
28
+ * **The builder emits a plan; it does not replace one.** `toJSON()`
29
+ * produces the same envelope a model would compose, so there is one
30
+ * resolution path, one cache, and one thing to test — a graph built in
31
+ * code and one composed by a model land on the same nodes.
32
+ *
33
+ * It deliberately knows nothing about the registry: no op-name checking,
34
+ * no param typing. That keeps it a pure authoring layer with no
35
+ * dependency on the op corpus, and leaves the resolver as the single
36
+ * place a bad plan is diagnosed. Typing params off `ParamDef` is the
37
+ * open question in the ticket, not a gap this file is hiding.
38
+ */
39
+ import { ProcessError } from '../errors.js';
40
+ import type { Select } from './run.js';
41
+ import type { SourceRef } from './source.js';
42
+ import type { Slots } from './slots.js';
43
+ import type { ParamValue } from './types.js';
44
+ /** Thrown when a graph is built wrong — before it is ever sent. */
45
+ export declare class BuilderError extends ProcessError {
46
+ }
47
+ /**
48
+ * The derived slot for a fold over `on` — a function of the node, the
49
+ * fold, **and its params**. Params used to be omitted, so
50
+ * `shape({points: 20})` followed by `shape({points: 100})` landed on one
51
+ * slot and the second call silently kept 20. Sorted by key so two
52
+ * spellings of one param set collide deliberately, the same rule
53
+ * `specId` applies one layer down.
54
+ *
55
+ * One asymmetry is inherent: this builder holds no registry, so it
56
+ * cannot resolve defaults — `shape()` and `shape({points: 40})` derive
57
+ * two slots here even though `specId` resolves them to one computation.
58
+ * That is safe: resolution collapses them to one node, and the response
59
+ * labels it with the first slot that named it. The registry-bound
60
+ * fluent layer canonicalizes params before calling this, so it does not
61
+ * split.
62
+ */
63
+ export declare function foldSlot(on: string, op: string, params?: Readonly<Record<string, ParamValue>>): string;
64
+ /** One named output of a multi-output node. */
65
+ export interface OutputHandle {
66
+ readonly slot: string;
67
+ readonly output: string;
68
+ }
69
+ /** What a node's inputs may be: a source column, a node, or one named output. */
70
+ export type InputRef = string | NodeHandle | OutputHandle;
71
+ /**
72
+ * A reference to a node in the graph under construction.
73
+ *
74
+ * The fold methods **add a node** and hand back a handle to it, rather
75
+ * than producing a selector as they used to. That is the whole shape of
76
+ * [PND-PROCFOLD] at the authoring layer: `z.percentileRank()` always
77
+ * read like an op call, and now it is one.
78
+ *
79
+ * The derived slot is `<slot>:<fold>` — with the fold's params folded in
80
+ * when it has any, e.g. `<slot>:shape(points=100)`. Deterministic in all
81
+ * three, so calling `.last()` twice on the same node returns the same
82
+ * node rather than colliding, while `shape({points: 20})` and
83
+ * `shape({points: 100})` are two nodes rather than the second silently
84
+ * answering with the first's 20. Safe against a source column name,
85
+ * which cannot contain a colon.
86
+ */
87
+ export interface NodeHandle {
88
+ readonly slot: string;
89
+ /** Latest defined value, with when. */
90
+ last(): NodeHandle;
91
+ /** Minimum and maximum, each with when. */
92
+ extremes(): NodeHandle;
93
+ /** Where the latest value sits in its own history. */
94
+ percentileRank(): NodeHandle;
95
+ /** A bounded sample of the whole series. */
96
+ shape(options?: {
97
+ points?: number;
98
+ }): NodeHandle;
99
+ }
100
+ /** The envelope a builder produces — what `Host.run` takes. */
101
+ export interface BuiltRequest<From extends string | SourceRef = string> {
102
+ readonly from: From;
103
+ readonly as?: string;
104
+ readonly nodes: Slots;
105
+ readonly outputs: Readonly<Record<string, Select>>;
106
+ }
107
+ export declare class PlanBuilder<From extends string | SourceRef = string> {
108
+ #private;
109
+ constructor(from: From);
110
+ /** Names the result, so a later request can refer back to it. */
111
+ as(name: string): this;
112
+ /**
113
+ * Adds a node under a caller-chosen slot name.
114
+ *
115
+ * The name is required rather than derived, because it *is* the stable
116
+ * identity — deriving `sma2` from a counter would renumber the moment
117
+ * a node is inserted above it, which is the property slots exist to
118
+ * provide.
119
+ *
120
+ * @throws {BuilderError} if the slot is already taken.
121
+ */
122
+ add(slot: string, op: string, params: Readonly<Record<string, ParamValue>> | undefined, inputs: readonly InputRef[]): NodeHandle;
123
+ /**
124
+ * Surfaces a node under the caller's own name.
125
+ *
126
+ * Takes a handle, because a selector's only remaining job is to point
127
+ * at a node — what comes back is whatever that node produces.
128
+ *
129
+ * @throws {BuilderError} if the name is already used.
130
+ */
131
+ expose(name: string, node: NodeHandle, options?: {
132
+ output?: string;
133
+ }): this;
134
+ /** The envelope. Plain JSON — nothing here survives into the request. */
135
+ toJSON(): BuiltRequest<From>;
136
+ }
137
+ export declare function plan<const From extends string | SourceRef>(from: From): PlanBuilder<From>;
138
+ //# sourceMappingURL=builder.d.ts.map