@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
@@ -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