@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.
- package/CHANGELOG.md +5914 -0
- package/LICENSE +21 -0
- package/README.md +345 -0
- package/dist/cjs-fallback.cjs +15 -0
- package/dist/column.d.ts +197 -0
- package/dist/column.js +306 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +25 -0
- package/dist/graph.d.ts +89 -0
- package/dist/graph.js +133 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +45 -0
- package/dist/node.d.ts +151 -0
- package/dist/node.js +268 -0
- package/dist/plan/builder.d.ts +138 -0
- package/dist/plan/builder.js +166 -0
- package/dist/plan/fluent.d.ts +93 -0
- package/dist/plan/fluent.js +140 -0
- package/dist/plan/folds.d.ts +25 -0
- package/dist/plan/folds.js +190 -0
- package/dist/plan/graph.d.ts +171 -0
- package/dist/plan/graph.js +658 -0
- package/dist/plan/history.d.ts +61 -0
- package/dist/plan/history.js +82 -0
- package/dist/plan/host.d.ts +173 -0
- package/dist/plan/host.js +234 -0
- package/dist/plan/identity.d.ts +81 -0
- package/dist/plan/identity.js +158 -0
- package/dist/plan/params.d.ts +15 -0
- package/dist/plan/params.js +26 -0
- package/dist/plan/registry.d.ts +162 -0
- package/dist/plan/registry.js +422 -0
- package/dist/plan/run.d.ts +211 -0
- package/dist/plan/run.js +360 -0
- package/dist/plan/slots.d.ts +65 -0
- package/dist/plan/slots.js +114 -0
- package/dist/plan/source.d.ts +49 -0
- package/dist/plan/source.js +54 -0
- package/dist/plan/types.d.ts +376 -0
- package/dist/plan/types.js +20 -0
- package/dist/pool/index.d.ts +15 -0
- package/dist/pool/index.js +12 -0
- package/dist/pool/pool.d.ts +92 -0
- package/dist/pool/pool.js +237 -0
- package/dist/pool/protocol.d.ts +48 -0
- package/dist/pool/protocol.js +9 -0
- package/dist/pool/wire.d.ts +52 -0
- package/dist/pool/wire.js +95 -0
- package/dist/pool/worker.d.ts +22 -0
- package/dist/pool/worker.js +80 -0
- package/dist/port.d.ts +79 -0
- package/dist/port.js +222 -0
- package/dist/source.d.ts +161 -0
- package/dist/source.js +182 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.js +26 -0
- package/package.json +50 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity, lineage, and units — and the [PND-PROCIDENT] decision.
|
|
3
|
+
*
|
|
4
|
+
* ## Identity is content-addressed, and that is forced
|
|
5
|
+
*
|
|
6
|
+
* The investigation weighed content-addressed ids against params-as-Ins
|
|
7
|
+
* and measured the latter far cheaper under a parameter sweep (1 node vs
|
|
8
|
+
* 200, 6 MB of buffers vs 310 MB). But writing `run()` settles it the
|
|
9
|
+
* other way, because the *plan format* decides it:
|
|
10
|
+
*
|
|
11
|
+
* A plan may hold `sma(px, 20)` **and** `sma(px, 50)` at once. They are
|
|
12
|
+
* two entries, they need two nodes, and — decisively — they need two
|
|
13
|
+
* distinct output **column names**. A column is named by its spec's id,
|
|
14
|
+
* so an id that excluded params would collide. Params must be in the id.
|
|
15
|
+
*
|
|
16
|
+
* The sweep result is still real; it is just an argument about
|
|
17
|
+
* **lifetime**, not identity. Dragging a slider mints a node per
|
|
18
|
+
* position, and those nodes stop being referenced by the current plan the
|
|
19
|
+
* moment it moves on. The answer is a retained set plus a budget
|
|
20
|
+
* ([PND-PROCCACHE]) — not a different identity model.
|
|
21
|
+
*
|
|
22
|
+
* So: **content-addressed identity, budgeted lifetime.**
|
|
23
|
+
*
|
|
24
|
+
* ## Two properties that must not regress
|
|
25
|
+
*
|
|
26
|
+
* Both are requirements rather than accidents, because a persisted saved
|
|
27
|
+
* view and a freshly composed request have to land on the same cache
|
|
28
|
+
* entry:
|
|
29
|
+
*
|
|
30
|
+
* - `specId` is **invariant under param key order** — a caller's JSON
|
|
31
|
+
* preserves insertion order and two callers will not agree on one.
|
|
32
|
+
* - An **omitted param collides with its explicit default**, so
|
|
33
|
+
* `{op:'sma'}` and `{op:'sma',params:{period:20}}` are one node.
|
|
34
|
+
*
|
|
35
|
+
* Both are pinned by tests.
|
|
36
|
+
*/
|
|
37
|
+
import { isFold, isPicked, specOf } from './types.js';
|
|
38
|
+
/** Id format version. Bumping it invalidates persisted ids deliberately. */
|
|
39
|
+
const VERSION = 'p1';
|
|
40
|
+
/**
|
|
41
|
+
* Escapes the separators an id is built from, so a string param cannot
|
|
42
|
+
* forge one. `sma(a\)b;…)` stays one id rather than closing early.
|
|
43
|
+
*/
|
|
44
|
+
function esc(v) {
|
|
45
|
+
return String(v).replace(/[\\;,()=+]/g, (c) => `\\${c}`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Canonical, versioned id for a spec — simultaneously the **column
|
|
49
|
+
* name**, the **cache key**, and the **provenance citation**.
|
|
50
|
+
*
|
|
51
|
+
* Params are sorted by key and materialized post-defaults, so two
|
|
52
|
+
* spellings of one computation collide deliberately.
|
|
53
|
+
*/
|
|
54
|
+
export function specId(registry, spec) {
|
|
55
|
+
const op = registry.get(spec.op);
|
|
56
|
+
const params = registry.resolveParams(op, spec.params);
|
|
57
|
+
const p = Object.entries(params)
|
|
58
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
59
|
+
.map(([k, v]) => `${k}=${esc(v)}`)
|
|
60
|
+
.join(',');
|
|
61
|
+
const inputs = spec.inputs
|
|
62
|
+
.map((i) => {
|
|
63
|
+
if (typeof i === 'string')
|
|
64
|
+
return esc(i);
|
|
65
|
+
// `#Lower` rather than a separate field: an input picking a
|
|
66
|
+
// different output is a different computation, and the id is what
|
|
67
|
+
// says so.
|
|
68
|
+
const base = specId(registry, specOf(i));
|
|
69
|
+
return isPicked(i) ? `${base}#${esc(i.output)}` : base;
|
|
70
|
+
})
|
|
71
|
+
.join('+');
|
|
72
|
+
return `${VERSION}:${spec.op}(${inputs};${p})`;
|
|
73
|
+
}
|
|
74
|
+
/** Resolves a reference that may be an inline spec or an id string. */
|
|
75
|
+
export function refToId(registry, ref) {
|
|
76
|
+
return typeof ref === 'string' ? ref : specId(registry, ref);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Human lineage, folded from the plan and the registry.
|
|
80
|
+
*
|
|
81
|
+
* Derived rather than reconstructed by hand — hand-built lineage is
|
|
82
|
+
* exactly what loses the inner `sma` in `ema(sma(x))`, which the RFC
|
|
83
|
+
* cites as a live consumer bug.
|
|
84
|
+
*/
|
|
85
|
+
export function explain(registry, spec) {
|
|
86
|
+
const op = registry.get(spec.op);
|
|
87
|
+
const params = registry.resolveParams(op, spec.params);
|
|
88
|
+
const inputs = spec.inputs
|
|
89
|
+
.map((i) => {
|
|
90
|
+
if (typeof i === 'string')
|
|
91
|
+
return i;
|
|
92
|
+
const base = explain(registry, specOf(i));
|
|
93
|
+
return isPicked(i) ? `${i.output} of ${base}` : base;
|
|
94
|
+
})
|
|
95
|
+
.join(', ');
|
|
96
|
+
if (op.label)
|
|
97
|
+
return op.label(params, inputs);
|
|
98
|
+
const p = Object.entries(params)
|
|
99
|
+
.map(([k, v]) => `${k}=${String(v)}`)
|
|
100
|
+
.join(', ');
|
|
101
|
+
return p ? `${op.name}(${p}) of ${inputs}` : `${op.name} of ${inputs}`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Unit of a spec's output `n` — declared outright, or folded from input 0.
|
|
105
|
+
*
|
|
106
|
+
* `null` means unitless: the consumer supplied no unit for the raw column
|
|
107
|
+
* at the root of the chain. That is reported rather than guessed.
|
|
108
|
+
*/
|
|
109
|
+
export function unitOf(registry, spec, units, outputIndex = 0) {
|
|
110
|
+
const op = registry.get(spec.op);
|
|
111
|
+
const declared = isFold(op)
|
|
112
|
+
? op.unit
|
|
113
|
+
: (op.outputs[outputIndex]?.unit ?? 'inherit');
|
|
114
|
+
if (declared !== 'inherit')
|
|
115
|
+
return declared;
|
|
116
|
+
const src = spec.inputs[0];
|
|
117
|
+
if (src === undefined)
|
|
118
|
+
return null;
|
|
119
|
+
if (typeof src === 'string')
|
|
120
|
+
return units[src] ?? null;
|
|
121
|
+
const upstream = specOf(src);
|
|
122
|
+
const index = isPicked(src)
|
|
123
|
+
? registry
|
|
124
|
+
.outputsOf(registry.get(upstream.op))
|
|
125
|
+
.findIndex((o) => o.id === src.output)
|
|
126
|
+
: 0;
|
|
127
|
+
return unitOf(registry, upstream, units, Math.max(0, index));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Column names a spec produces, in output-declaration order.
|
|
131
|
+
*
|
|
132
|
+
* A single-output op declares suffix `''`, so its column *is* the id; a
|
|
133
|
+
* band's three columns share the id as a prefix, which is the corpus's
|
|
134
|
+
* own convention and needs no mapping layer.
|
|
135
|
+
*/
|
|
136
|
+
export function columnsOf(registry, spec, id) {
|
|
137
|
+
const def = registry.get(spec.op);
|
|
138
|
+
return registry.outputsOf(def).map((o) => id + o.id);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Which params an output depends on, defaulting to all of them.
|
|
142
|
+
*
|
|
143
|
+
* Declaring a narrower set is what lets a change to one param leave
|
|
144
|
+
* another output's version untouched ([PND-PROCSEL]).
|
|
145
|
+
*/
|
|
146
|
+
export function dependsOn(registry, spec, outputIndex) {
|
|
147
|
+
const op = registry.get(spec.op);
|
|
148
|
+
const declared = isFold(op) ? undefined : op.outputs[outputIndex]?.dependsOn;
|
|
149
|
+
return declared ? [...declared] : Object.keys(op.params);
|
|
150
|
+
}
|
|
151
|
+
/** Params reduced to the subset an output depends on — its cache key. */
|
|
152
|
+
export function outputKey(params, keys) {
|
|
153
|
+
return [...keys]
|
|
154
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
155
|
+
.map((k) => `${k}=${esc(params[k])}`)
|
|
156
|
+
.join(',');
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The param vocabulary, in its own module.
|
|
3
|
+
*
|
|
4
|
+
* Split out of the registry because `folds.ts` needs `int` to declare
|
|
5
|
+
* `shape`'s `points`, and the registry needs `folds.ts` to pre-register
|
|
6
|
+
* the standard folds — a cycle that ESM resolves by handing one side an
|
|
7
|
+
* uninitialised binding, which surfaces as `int is not a function` at
|
|
8
|
+
* import time rather than anywhere near the cause.
|
|
9
|
+
*/
|
|
10
|
+
import type { BooleanParam, EnumParam, NumberParam } from './types.js';
|
|
11
|
+
export declare const int: (o: Omit<NumberParam, "kind">) => NumberParam;
|
|
12
|
+
export declare const num: (o: Omit<NumberParam, "kind">) => NumberParam;
|
|
13
|
+
export declare const choice: (o: Omit<EnumParam, "kind">) => EnumParam;
|
|
14
|
+
export declare const flag: (o: Omit<BooleanParam, "kind">) => BooleanParam;
|
|
15
|
+
//# sourceMappingURL=params.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The param vocabulary, in its own module.
|
|
3
|
+
*
|
|
4
|
+
* Split out of the registry because `folds.ts` needs `int` to declare
|
|
5
|
+
* `shape`'s `points`, and the registry needs `folds.ts` to pre-register
|
|
6
|
+
* the standard folds — a cycle that ESM resolves by handing one side an
|
|
7
|
+
* uninitialised binding, which surfaces as `int is not a function` at
|
|
8
|
+
* import time rather than anywhere near the cause.
|
|
9
|
+
*/
|
|
10
|
+
export const int = (o) => ({
|
|
11
|
+
kind: 'integer',
|
|
12
|
+
...o,
|
|
13
|
+
});
|
|
14
|
+
export const num = (o) => ({
|
|
15
|
+
kind: 'number',
|
|
16
|
+
...o,
|
|
17
|
+
});
|
|
18
|
+
export const choice = (o) => ({
|
|
19
|
+
kind: 'enum',
|
|
20
|
+
...o,
|
|
21
|
+
});
|
|
22
|
+
export const flag = (o) => ({
|
|
23
|
+
kind: 'boolean',
|
|
24
|
+
...o,
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=params.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry, and the param vocabulary it is declared in.
|
|
3
|
+
*
|
|
4
|
+
* One declaration, four readers: **param validation**, a **JSON Schema
|
|
5
|
+
* projection** so a tool caller can compose plans, a **UI picker**
|
|
6
|
+
* (family + params + defaults is exactly a grouped menu), and **unit
|
|
7
|
+
* propagation**.
|
|
8
|
+
*
|
|
9
|
+
* The internal param spec is the source of truth and the validator reads
|
|
10
|
+
* it; JSON Schema is a *projection* emitted for callers, not the
|
|
11
|
+
* authority ([PND-PROCREG]). Adopting JSON Schema as the source would
|
|
12
|
+
* mean taking on a schema-validator dependency to do work a dozen lines
|
|
13
|
+
* already do.
|
|
14
|
+
*/
|
|
15
|
+
import { ProcessError } from '../errors.js';
|
|
16
|
+
export { int, num, choice, flag } from './params.js';
|
|
17
|
+
import { type Def, type FoldDef } from './types.js';
|
|
18
|
+
import type { InputDef, ParamDef, Params, ParamValue } from './types.js';
|
|
19
|
+
/** The compile-time vocabulary retained by a registry as definitions are added. */
|
|
20
|
+
export type DefMap = Readonly<Record<string, Def>>;
|
|
21
|
+
type WithDef<Defs extends DefMap, D extends Def> = Omit<Defs, D['name']> & Readonly<Record<D['name'], D>>;
|
|
22
|
+
/** One declared output, as `outputsOf` reports it. */
|
|
23
|
+
type OutputShape = {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly unit: string;
|
|
26
|
+
};
|
|
27
|
+
/** Thrown when a plan names an op the registry does not have. */
|
|
28
|
+
export declare class UnknownOpError extends ProcessError {
|
|
29
|
+
}
|
|
30
|
+
/** Thrown when a param is missing, mistyped, or out of range. */
|
|
31
|
+
export declare class ParamError extends ProcessError {
|
|
32
|
+
}
|
|
33
|
+
/** Op metadata as a picker or a tool catalog wants it. */
|
|
34
|
+
export interface OpDescriptor {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly family: string;
|
|
37
|
+
readonly summary: string;
|
|
38
|
+
readonly params: Readonly<Record<string, ParamDef>>;
|
|
39
|
+
/**
|
|
40
|
+
* The declared inputs, not a count of them.
|
|
41
|
+
*
|
|
42
|
+
* A count is enough to check arity and nothing else. A consumer
|
|
43
|
+
* labelling a two-input op cannot say which side is which, and one
|
|
44
|
+
* showing why a plan was rejected cannot name the unit an input
|
|
45
|
+
* demands — both facts the registry holds and used to drop here. Found
|
|
46
|
+
* by building a UI that wanted to label a node's wiring and could only
|
|
47
|
+
* show how many wires there were.
|
|
48
|
+
*/
|
|
49
|
+
readonly inputs: readonly InputDef[];
|
|
50
|
+
/**
|
|
51
|
+
* `'fold'` for a terminal node — one that ends in a fact rather than a
|
|
52
|
+
* column, and so cannot be another node's input.
|
|
53
|
+
*
|
|
54
|
+
* A consumer needs this to know what surfacing the node will hand it
|
|
55
|
+
* back, and a picker needs it to know which entries can be wired
|
|
56
|
+
* onward. Absent would have meant every reader inferring it from an
|
|
57
|
+
* empty `outputs`, which is exactly the kind of thing the registry
|
|
58
|
+
* exists to state.
|
|
59
|
+
*/
|
|
60
|
+
readonly kind: 'op' | 'fold';
|
|
61
|
+
/** Empty for a fold: a fact has no columns. */
|
|
62
|
+
readonly outputs: readonly {
|
|
63
|
+
readonly suffix: string;
|
|
64
|
+
readonly unit: string;
|
|
65
|
+
}[];
|
|
66
|
+
}
|
|
67
|
+
export declare class Registry<Defs extends DefMap = {}> {
|
|
68
|
+
#private;
|
|
69
|
+
/**
|
|
70
|
+
* Adds a definition and retains its literal shape in the return type.
|
|
71
|
+
*
|
|
72
|
+
* Runtime callers still validate through this registry. The accumulated
|
|
73
|
+
* type exists for the programmable fluent authoring layer, where it turns
|
|
74
|
+
* op names, params, input roles and output suffixes into compile-time facts.
|
|
75
|
+
*/
|
|
76
|
+
define<const D extends Def>(def: D): Registry<WithDef<Defs, D>>;
|
|
77
|
+
has(name: string): boolean;
|
|
78
|
+
/** @throws {UnknownOpError} naming what is available, so an agent can retry. */
|
|
79
|
+
get(name: string): Def;
|
|
80
|
+
/** The entry as a fold, or `undefined` if it produces columns. */
|
|
81
|
+
foldFor(name: string): FoldDef | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Declared outputs, empty for a fold.
|
|
84
|
+
*
|
|
85
|
+
* Every caller that used to reach for `op.outputs` went through here
|
|
86
|
+
* once folds existed, because a fact has none and the alternative was
|
|
87
|
+
* an optional-chain at each of the nine call sites.
|
|
88
|
+
*/
|
|
89
|
+
outputsOf(def: Def): readonly OutputShape[];
|
|
90
|
+
/** Applies defaults, then validates every declared param. */
|
|
91
|
+
resolveParams(op: Def, given?: Readonly<Record<string, ParamValue>>): Params;
|
|
92
|
+
/** Grouped for a picker. */
|
|
93
|
+
byFamily(): Map<string, OpDescriptor[]>;
|
|
94
|
+
describe(): OpDescriptor[];
|
|
95
|
+
/**
|
|
96
|
+
* The tool contract: ops as a discriminated union of param objects.
|
|
97
|
+
*
|
|
98
|
+
* The spec schema is **recursive** — an input is a column name *or*
|
|
99
|
+
* another spec — which is what lets a caller express *EMA of SMA of
|
|
100
|
+
* px* from the schema alone, without being taught a nesting concept.
|
|
101
|
+
* That recursion is the single most load-bearing thing here, and
|
|
102
|
+
* getting it to travel took three attempts.
|
|
103
|
+
*
|
|
104
|
+
* It lives in `$defs`, and the recursion goes through
|
|
105
|
+
* `#/$defs/<name>`. That is the only shape that is actually portable:
|
|
106
|
+
*
|
|
107
|
+
* - `#/items`, the original, dangles the moment the projection is
|
|
108
|
+
* nested inside a larger schema, because a `$ref` resolves against
|
|
109
|
+
* the **document root**. Silently — nothing requires a `$ref` to
|
|
110
|
+
* resolve ([PND-PROCREG], M2).
|
|
111
|
+
* - `#/properties/process/items`, a pointer *into* the host document,
|
|
112
|
+
* fixes that and passes local validators — including OpenAI's own
|
|
113
|
+
* `toStrictJsonSchema` — but the API rejects it: *"reference can
|
|
114
|
+
* only point to definitions defined at the top level of the
|
|
115
|
+
* schema"* ([PND-PROCSCHEMA], M5).
|
|
116
|
+
*
|
|
117
|
+
* So a caller embedding this must lift `$defs` to its own root, where
|
|
118
|
+
* `#/$defs/<name>` resolves from anywhere:
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* const plan = registry.toJsonSchema({ defs: 'spec' });
|
|
122
|
+
* const { $defs, ...body } = plan;
|
|
123
|
+
* const schema = {
|
|
124
|
+
* type: 'object',
|
|
125
|
+
* $defs, // hoisted to the root
|
|
126
|
+
* properties: { process: body },
|
|
127
|
+
* };
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* `$schema` is emitted only at the root — a nested subschema declaring
|
|
131
|
+
* its own dialect is not what a caller means.
|
|
132
|
+
*
|
|
133
|
+
* Two more things learned by calling a real API rather than reading a
|
|
134
|
+
* spec, both cases where a **client-side** strict validator accepted
|
|
135
|
+
* what the server refused:
|
|
136
|
+
*
|
|
137
|
+
* - Unions are `anyOf`, not `oneOf`. Both branch sets here are
|
|
138
|
+
* disjoint — the op union is discriminated by a `const`, and an
|
|
139
|
+
* input is a string or an object, never both — so they are
|
|
140
|
+
* equivalent in meaning, and `anyOf` is the one tool APIs accept
|
|
141
|
+
* (*"'oneOf' is not permitted"*).
|
|
142
|
+
* - A `const` carries its `type` alongside. Redundant to a validator,
|
|
143
|
+
* and required by the same API (*"schema must have a 'type' key"*).
|
|
144
|
+
*/
|
|
145
|
+
toJsonSchema(options?: {
|
|
146
|
+
defs?: string;
|
|
147
|
+
root?: boolean;
|
|
148
|
+
shape?: 'nested' | 'slots';
|
|
149
|
+
}): Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A registry with the standard folds already in it.
|
|
153
|
+
*
|
|
154
|
+
* Pre-registered because `last`, `extremes`, `percentileRank` and `shape`
|
|
155
|
+
* apply to any numeric series and every consumer wants them — not because
|
|
156
|
+
* they are privileged. They are plain defs: `define` over a name to
|
|
157
|
+
* replace one.
|
|
158
|
+
*/
|
|
159
|
+
export declare function createRegistry(options?: {
|
|
160
|
+
folds?: boolean;
|
|
161
|
+
}): Registry<{}>;
|
|
162
|
+
//# sourceMappingURL=registry.d.ts.map
|