@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,376 @@
1
+ /**
2
+ * Plan-layer types — [PND-DEMOM0].
3
+ *
4
+ * A plan is **data**: a DAG of `{ op, params, inputs }` specs that can
5
+ * arrive as JSON from a saved view or an agent. This file defines that
6
+ * shape and the op declarations it resolves against.
7
+ */
8
+ import type { ColumnView, RangeOutput } from '../column.js';
9
+ import type { Column, TimeSeries, SeriesSchema } from 'pond-ts';
10
+ /** A JSON-safe param value. Params arrive off a wire, not from code. */
11
+ export type ParamValue = string | number | boolean;
12
+ /** One node in a plan: an op, its params, and where its inputs come from. */
13
+ export interface Spec {
14
+ readonly op: string;
15
+ readonly params?: Readonly<Record<string, ParamValue>>;
16
+ /** A raw source column name, or a nested spec. Plural from the start. */
17
+ readonly inputs: readonly Input[];
18
+ }
19
+ /**
20
+ * One named output of an upstream spec.
21
+ *
22
+ * A nested input used to read output 0 and nothing else, so `sma` of a
23
+ * Bollinger band could only ever smooth `Upper` — a limitation nobody
24
+ * hit while `select.output` existed to pick one at the end. Folds made
25
+ * it visible: a fold *is* the end, so with no way to say which output it
26
+ * reads, the middle and lower bands became unreachable.
27
+ */
28
+ export interface PickedOutput {
29
+ readonly from: Spec;
30
+ /** The output's declared suffix, e.g. `'Lower'`. */
31
+ readonly output: string;
32
+ }
33
+ /** An input is a raw column name, another spec, or one output of one. */
34
+ export type Input = string | Spec | PickedOutput;
35
+ /** Narrows an input to the picked-output form. */
36
+ export declare function isPicked(input: Input): input is PickedOutput;
37
+ /** The spec an input refers to, ignoring which output it picks. */
38
+ export declare function specOf(input: Spec | PickedOutput): Spec;
39
+ /** A DAG. Declaration order is free; nesting is inline. */
40
+ export type Plan = readonly Spec[];
41
+ /** How a request refers to a resolved spec: inline, or by its id. */
42
+ export type SpecRef = string | Spec;
43
+ export interface NumberParam {
44
+ readonly kind: 'number' | 'integer';
45
+ readonly default: number;
46
+ /** The legal range. Outside it is an error. */
47
+ readonly min?: number;
48
+ readonly max?: number;
49
+ /**
50
+ * The **useful** range — `[lo, hi]`, within `[min, max]`.
51
+ *
52
+ * `min`/`max` answer "would this be rejected?", which is a different
53
+ * question from "what would anyone actually pick?", and the gap between
54
+ * them is usually enormous: a `period` legal to 5000 is interesting
55
+ * below about 200, so a slider drawn on the legal range spends 96% of
56
+ * its travel where nobody goes. Declaring the useful range is what lets
57
+ * a control be drawn on it and a model be told it.
58
+ *
59
+ * Purely advisory — nothing rejects a value outside it, since a bound
60
+ * that rejects is what `min`/`max` already are.
61
+ */
62
+ readonly suggest?: readonly [number, number];
63
+ readonly label?: string;
64
+ }
65
+ export interface EnumParam {
66
+ readonly kind: 'enum';
67
+ readonly default: string;
68
+ readonly of: readonly string[];
69
+ readonly label?: string;
70
+ }
71
+ export interface BooleanParam {
72
+ readonly kind: 'boolean';
73
+ readonly default: boolean;
74
+ readonly label?: string;
75
+ }
76
+ export type ParamDef = NumberParam | EnumParam | BooleanParam;
77
+ /** The resolved param record an op's `run` receives, post-defaults. */
78
+ export type Params = Readonly<Record<string, ParamValue>>;
79
+ /**
80
+ * A unit is either declared outright or inherited from input 0.
81
+ *
82
+ * Units are an **input to resolution**, not a property of a series: pond
83
+ * series do not carry units, consumers do. Passing them in is what lets
84
+ * an op demand a typed input (an annualiser wanting `variance`) and what
85
+ * lets a response report concrete units.
86
+ */
87
+ export type UnitSpec = 'inherit' | string;
88
+ /** Consumer-supplied units for the bound source's raw columns. */
89
+ export type Units = Readonly<Record<string, string>>;
90
+ export interface InputDef {
91
+ /** Name the op's `run` reads this input by. */
92
+ readonly role: string;
93
+ /** A unit this input must already carry, if the op demands one. */
94
+ readonly unit?: string;
95
+ }
96
+ export interface OutputDef {
97
+ /**
98
+ * Suffix appended to the spec's id to name this output's column.
99
+ *
100
+ * `''` for a single-output op, so the column *is* the id. A band
101
+ * declares `'Upper' | 'Middle' | 'Lower'`, matching the corpus's own
102
+ * `prefix` convention — one spec, three columns, moved and deleted as
103
+ * a unit.
104
+ */
105
+ readonly id: string;
106
+ readonly unit: UnitSpec;
107
+ /**
108
+ * Params this output depends on. When declared, a change to a param
109
+ * *not* listed here leaves this output's version untouched, so
110
+ * everything downstream of it skips ([PND-PROCSEL]). Omit to mean
111
+ * "depends on everything", which is correct but never skips.
112
+ */
113
+ readonly dependsOn?: readonly string[];
114
+ }
115
+ /** What an op's `run` is handed. */
116
+ export interface OpContext {
117
+ /**
118
+ * The bound source, widened to carry this op's input columns under the
119
+ * names in {@link inputs}. Prepared by the plan layer so an op can call
120
+ * the corpus normally — the studies take `(series, { column })`.
121
+ */
122
+ readonly series: TimeSeries<SeriesSchema>;
123
+ /** Column name per input role. */
124
+ readonly inputs: Readonly<Record<string, string>>;
125
+ readonly params: Params;
126
+ /** This spec's id — the output column name, or a band's prefix. */
127
+ readonly id: string;
128
+ }
129
+ /**
130
+ * What a ranged recompute is given — [PND-PROCRANGE].
131
+ *
132
+ * ## The previous output is an argument, not state
133
+ *
134
+ * The obvious way to make recompute incremental is to let a node reach
135
+ * for its own last output and patch it. That makes `compute` a function
136
+ * of its inputs *and* of history, which is a real loss: two callers with
137
+ * the same data but different edit sequences can disagree, and `explain`
138
+ * stops describing what a value actually depends on.
139
+ *
140
+ * Passing {@link previous} in as an argument keeps the op a pure
141
+ * function — of more things than before, but declared things. The
142
+ * mutable part stays in the graph, which is a cache and was already
143
+ * impure.
144
+ *
145
+ * ## Why this is safe now and was not before
146
+ *
147
+ * Incremental recompute is only honest if a patched result equals a
148
+ * from-scratch one. Until [PND-PROCKERN] it did not: a ranged fill over
149
+ * the rolling kernel differed on **every cell**, because the accumulator
150
+ * carried a different rounding history. That is fixed for `avg`/`stdev`,
151
+ * which is why an op **opts in** rather than getting this by default —
152
+ * an op whose kernel is not range-exact must not declare `runRange`, or
153
+ * its answers become dependent on the sequence of edits that produced
154
+ * them. `median`, percentiles, `min` and `max` are in that category
155
+ * today.
156
+ */
157
+ export interface RangeContext extends OpContext {
158
+ /** First row that must be recomputed, already widened by the lookback. */
159
+ readonly from: number;
160
+ /** One past the last row — the series length. */
161
+ readonly to: number;
162
+ /**
163
+ * This node's previous output, one entry per declared output.
164
+ *
165
+ * Shorter than the current series when rows were appended. An op
166
+ * copies what it keeps and fills `[from, to)`.
167
+ */
168
+ readonly previous: readonly Column[];
169
+ /**
170
+ * The same outputs as **zero-copy views**, positionally aligned with
171
+ * {@link previous} — `undefined` for any that is not packed numeric.
172
+ *
173
+ * This is what makes ranged recompute worth doing, and reading
174
+ * {@link previous} cell by cell instead is the difference between a
175
+ * `memcpy` and a walk. An op carries `[0, from)` forward unchanged, so
176
+ * that prefix should move as a block:
177
+ *
178
+ * ```ts
179
+ * const prior = ctx.previousView[0];
180
+ * const out = new Float64Array(ctx.to);
181
+ * if (prior) out.set(prior.values.subarray(0, ctx.from));
182
+ * for (let i = ctx.from; i < ctx.to; i += 1) out[i] = …;
183
+ * ```
184
+ *
185
+ * Measured over 500k rows × 5 studies: the boxed form — a `.at(i)`
186
+ * per cell into an `Array` — left ranged recompute at **4×** against a
187
+ * full pass. The same op reading this view runs at **31×**. The graph
188
+ * was never the bottleneck; the prefix walk was.
189
+ *
190
+ * Borrowed, like any {@link ColumnView}: read within the call, never
191
+ * retain.
192
+ */
193
+ readonly previousView: readonly (ColumnView | undefined)[];
194
+ /**
195
+ * Prepared output buffers, one per declared output, each of length
196
+ * {@link to} and already carrying `[0, from)` from the previous
197
+ * result — values **and** validity, copied as blocks.
198
+ *
199
+ * **This is the fast path, and also the correct one.** Writing here
200
+ * and returning nothing lets the graph seal the buffers directly.
201
+ * Rebuilding the whole output instead means carrying the prefix by
202
+ * hand, and the obvious way to do that is silently wrong: packed
203
+ * storage holds `0` at a missing cell rather than `NaN`, so copying
204
+ * only the values turns every warm-up gap into a defined zero — 1,875
205
+ * wrong cells on a 5-study pass, caught by comparing against a
206
+ * from-scratch run rather than by any type.
207
+ *
208
+ * ```ts
209
+ * runRange: (ctx) => {
210
+ * const out = ctx.out[0]!;
211
+ * for (let i = ctx.from; i < ctx.to; i += 1) {
212
+ * const v = compute(i);
213
+ * if (v === undefined) out.clear(i);
214
+ * else out.set(i, v);
215
+ * }
216
+ * // no return
217
+ * }
218
+ * ```
219
+ */
220
+ readonly out: readonly RangeOutput[];
221
+ }
222
+ /**
223
+ * An op's result: one entry per declared output, in declaration order.
224
+ *
225
+ * A `Column` is returned as-is (already packed); loose values are packed
226
+ * once by the plan layer rather than retained boxed ([PND-PROCCOL]).
227
+ */
228
+ export type OpResult = Column | ArrayLike<number | undefined> | readonly (Column | ArrayLike<number | undefined>)[];
229
+ export interface OpDef {
230
+ /** Discriminates against {@link FoldDef}. Optional, because an op is the default. */
231
+ readonly kind?: 'op';
232
+ readonly name: string;
233
+ readonly family: string;
234
+ readonly summary: string;
235
+ readonly params: Readonly<Record<string, ParamDef>>;
236
+ readonly inputs: readonly InputDef[];
237
+ readonly outputs: readonly OutputDef[];
238
+ /**
239
+ * Human lineage fragment, e.g. `SMA(20) of iv21`. Optional — a generic
240
+ * `op(params) of inputs` is derived when absent, but a label reads far
241
+ * better in a legend chip or a graph node.
242
+ */
243
+ readonly label?: (params: Params, inputs: string) => string;
244
+ /**
245
+ * Rows of history this op needs **before** a requested range for its
246
+ * output over that range to be fully defined — [PND-PROCHIST].
247
+ *
248
+ * A count-window study of `period` bars needs `period - 1`. Declaring
249
+ * it lets {@link requiredHistory} derive the minimum safe tail for a
250
+ * whole plan, so a consumer slicing a hot leading edge stops guessing:
251
+ * an 8-study stack over 500k rows costs 765 ms/tick, and the same stack
252
+ * over a 5,000-row tail 5.4 ms/tick. The window is the difference
253
+ * between 1.3 ticks/sec and interactive.
254
+ *
255
+ * **An IIR op has no exact finite warm-up** — an EMA depends on every
256
+ * row before it, decaying but never reaching zero. `4 * period` is the
257
+ * usual engineering answer and each such op should declare it rather
258
+ * than have the folder assume one, because the multiplier is a claim
259
+ * about acceptable error and only the op knows what it is.
260
+ *
261
+ * Omitted means **unknown, not zero**, and {@link requiredHistory}
262
+ * reports that rather than returning a number a caller would slice
263
+ * against. An op that genuinely needs no history — anything
264
+ * element-wise — should say `() => 0`.
265
+ */
266
+ readonly lookback?: (params: Params) => number;
267
+ readonly run: (ctx: OpContext) => OpResult;
268
+ /**
269
+ * Recompute only `[from, to)`, given the previous output —
270
+ * [PND-PROCRANGE]. Optional, and **opt-in for a reason**.
271
+ *
272
+ * The graph calls this instead of {@link run} when it knows which rows
273
+ * changed and this node has a previous output to patch; otherwise it
274
+ * falls back to a full {@link run}, so declaring nothing is always
275
+ * correct and merely slower.
276
+ *
277
+ * **Only declare it if a patched result is bit-identical to a
278
+ * from-scratch one.** That holds for the range-exact rolling kernel
279
+ * ([PND-PROCKERN]) and does not hold for `median`, percentiles, `min`
280
+ * or `max`, which still sweep whole-series. An op that declares this
281
+ * without that property makes its answers depend on the sequence of
282
+ * edits that produced them — which is invisible in a test that only
283
+ * ever computes from scratch.
284
+ *
285
+ * Requires {@link lookback}, since that is what widens an upstream
286
+ * dirty range into this node's. Without it the graph cannot know how
287
+ * far back a change reaches and will not range.
288
+ */
289
+ readonly runRange?: (ctx: RangeContext) => OpResult | void;
290
+ }
291
+ /**
292
+ * What a fold returns: the body of a fact.
293
+ *
294
+ * Deliberately loose. `last` answers with a value and a timestamp,
295
+ * `extremes` with two of each, `percentileRank` with a fraction and a
296
+ * sentence explaining it. Forcing those into one shape would mean
297
+ * inventing a lowest common denominator that suits none of them.
298
+ */
299
+ export type FactBody = Readonly<Record<string, unknown>>;
300
+ export interface FoldContext {
301
+ /**
302
+ * Dense values per input role, gaps as `undefined`.
303
+ *
304
+ * Prepared by the graph rather than by each fold, and — the point of
305
+ * the original exercise — prepared **inside the memo**, so densifying
306
+ * a 150,000-row column happens once per version rather than once per
307
+ * request.
308
+ *
309
+ * **Prefer {@link FoldContext.numeric}.** This is the boxed form, and
310
+ * it is now **lazy**: touching a role allocates an `Array` of that
311
+ * column's length and fills it, which is the single largest heap cost
312
+ * in the graph ([PND-PROCCOL]). `latest` reads one cell and used to
313
+ * pay for 500,000 of them. Untouched roles cost nothing, so a fold
314
+ * that never reads this never allocates.
315
+ */
316
+ readonly values: Readonly<Record<string, readonly (number | undefined)[]>>;
317
+ /**
318
+ * A **zero-copy** columnar view of one input role — no allocation, at
319
+ * any length.
320
+ *
321
+ * `values` is the column's own storage and a cell is meaningful only
322
+ * where `defined(i)`; both are borrowed and must not be retained past
323
+ * the fold. `undefined` when the role's column is not packed numeric
324
+ * (a string column, or a value an op returned boxed), which is the
325
+ * caller's cue to fall back to {@link FoldContext.values}.
326
+ *
327
+ * Reading is **not faster** this way — a buffer walk reaches parity
328
+ * with the boxed array and `Column.scan()` is 4.7× slower than either.
329
+ * What changes is that nothing is allocated to do it.
330
+ */
331
+ numeric(role: string): ColumnView | undefined;
332
+ /**
333
+ * Timestamp at a row index.
334
+ *
335
+ * A function rather than an array because a fold reports two or three
336
+ * rows out of 150,000, and materializing the key column to answer that
337
+ * was most of what a reduction used to cost.
338
+ */
339
+ readonly at: (index: number) => number;
340
+ readonly params: Params;
341
+ /** This fold's id — the fact's own cache key and citation. */
342
+ readonly id: string;
343
+ }
344
+ /**
345
+ * A **fold** — a node that ends in a fact rather than a column.
346
+ *
347
+ * Reductions used to be a fixed enum on the selector, computed after the
348
+ * graph had finished: `last` rescanned 150,000 values on a total cache
349
+ * hit, and `percentileRank` densified and filtered twice, every request,
350
+ * forever. Measured at 10.85 ms of an 11.6 ms warm run — the graph
351
+ * memoized every intermediate and then recomputed the only part anyone
352
+ * actually read.
353
+ *
354
+ * A fold is an ordinary registry entry with an ordinary content-addressed
355
+ * id, so it caches, it carries provenance, and a consumer adds one by
356
+ * calling `define` rather than by editing this library. What it cannot do
357
+ * is feed anything: a fold produces a fact, so it is always a leaf, and
358
+ * naming one as an op's input is rejected at compile time.
359
+ */
360
+ export interface FoldDef {
361
+ readonly kind: 'fold';
362
+ readonly name: string;
363
+ readonly family: string;
364
+ readonly summary: string;
365
+ readonly params: Readonly<Record<string, ParamDef>>;
366
+ readonly inputs: readonly InputDef[];
367
+ /** The unit the fact carries. `'inherit'` takes input 0's. */
368
+ readonly unit: UnitSpec;
369
+ readonly label?: (params: Params, inputs: string) => string;
370
+ readonly fold: (ctx: FoldContext) => FactBody;
371
+ }
372
+ /** Anything the registry holds. */
373
+ export type Def = OpDef | FoldDef;
374
+ /** Narrows a registry entry to the terminal kind. */
375
+ export declare function isFold(def: Def): def is FoldDef;
376
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Plan-layer types — [PND-DEMOM0].
3
+ *
4
+ * A plan is **data**: a DAG of `{ op, params, inputs }` specs that can
5
+ * arrive as JSON from a saved view or an agent. This file defines that
6
+ * shape and the op declarations it resolves against.
7
+ */
8
+ /** Narrows an input to the picked-output form. */
9
+ export function isPicked(input) {
10
+ return typeof input !== 'string' && 'from' in input;
11
+ }
12
+ /** The spec an input refers to, ignoring which output it picks. */
13
+ export function specOf(input) {
14
+ return 'from' in input ? input.from : input;
15
+ }
16
+ /** Narrows a registry entry to the terminal kind. */
17
+ export function isFold(def) {
18
+ return def.kind === 'fold';
19
+ }
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@pond-ts/process/pool` — whole requests across resident workers.
3
+ *
4
+ * A separate entry point because it is **Node-only** (it imports
5
+ * `node:worker_threads`); the package index stays runtime-neutral.
6
+ *
7
+ * See {@link HostPool} for which of the two parallelism shapes this is,
8
+ * and why this one comes first.
9
+ */
10
+ export { HostPool } from './pool.js';
11
+ export type { HostPoolOptions } from './pool.js';
12
+ export type { PoolSetup, PoolSetupConfig } from './protocol.js';
13
+ export { toWire, fromWire } from './wire.js';
14
+ export type { WireResult, WireColumn } from './wire.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@pond-ts/process/pool` — whole requests across resident workers.
3
+ *
4
+ * A separate entry point because it is **Node-only** (it imports
5
+ * `node:worker_threads`); the package index stays runtime-neutral.
6
+ *
7
+ * See {@link HostPool} for which of the two parallelism shapes this is,
8
+ * and why this one comes first.
9
+ */
10
+ export { HostPool } from './pool.js';
11
+ export { toWire, fromWire } from './wire.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * `HostPool` — whole requests across resident workers ([PND-PROCPAR]).
3
+ *
4
+ * ## Which parallelism this is
5
+ *
6
+ * The worker-threads assessment
7
+ * (`docs/notes/worker-threads-assessment-2026-07.md`) found two distinct
8
+ * wins and warned against conflating them:
9
+ *
10
+ * - **Latency** of one composite query — split its nodes across workers.
11
+ * Measured 2.42× on a 5-study stack, and it needs an engine change: a
12
+ * node's value can only be produced by its own `compute`, so a result
13
+ * computed elsewhere has nowhere to land. Still ahead of us.
14
+ * - **Throughput** under concurrent queries — run *whole* requests, each
15
+ * single-threaded, on a pool of resident hosts. Near-linear, no
16
+ * decomposition, no numeric-semantics questions, **and no engine
17
+ * change at all**.
18
+ *
19
+ * This is the second. It is deliberately first: the agent workload is
20
+ * many overlapping questions, so throughput is what it feels, and this
21
+ * shape cannot get an answer wrong — each request runs the same
22
+ * single-threaded code it runs today, in a different isolate.
23
+ *
24
+ * ## What makes it cheap
25
+ *
26
+ * Each worker holds a **long-lived `Host`**, so compiled nodes and
27
+ * cached columns survive between requests exactly as they do in-process.
28
+ * The pool is only a router. What crosses the boundary is a plan
29
+ * (JSON by construction) and a result whose columns travel as
30
+ * transferable buffers rather than boxed values.
31
+ *
32
+ * ## What it is not
33
+ *
34
+ * Not a cache-sharing scheme. Each worker warms its own graph, so N
35
+ * workers hold up to N copies of a hot column and a question already
36
+ * answered by worker 2 is cold on worker 3. That is the honest cost of
37
+ * the simple shape: it buys throughput, not deduplication. Route related
38
+ * requests to the same worker (see {@link HostPool.run}'s `affinity`) if
39
+ * repeat-hit rate matters more than even spread.
40
+ */
41
+ import type { AsyncEnvelope } from '../plan/host.js';
42
+ import type { RunResult } from '../plan/run.js';
43
+ export interface HostPoolOptions {
44
+ /**
45
+ * Module specifier for the worker's setup module — a `URL`, or an
46
+ * absolute path/specifier a worker can `import()`.
47
+ *
48
+ * It must export a setup function (as `default` or `setup`) returning
49
+ * `{ registry, units?, sources?, datasets? }`. A module, not a value,
50
+ * because a registry is functions and functions do not survive
51
+ * structured clone.
52
+ */
53
+ readonly setup: URL | string;
54
+ /** Workers to start. Default: `availableParallelism() - 1`, min 1. */
55
+ readonly size?: number;
56
+ /** Passed to the setup function, so one module can serve several pools. */
57
+ readonly setupOptions?: unknown;
58
+ }
59
+ export declare class HostPool {
60
+ #private;
61
+ private constructor();
62
+ /**
63
+ * Starts the pool and waits for every worker to be listening.
64
+ *
65
+ * Setup modules are imported lazily *inside* each worker on its first
66
+ * request, so `start` resolving does not mean the registry loaded — a
67
+ * broken setup module surfaces as that first request's error, where a
68
+ * caller can read it, rather than as a start-up crash with no
69
+ * request to attach it to.
70
+ */
71
+ static start(options: HostPoolOptions): Promise<HostPool>;
72
+ /** Workers in the pool. */
73
+ get size(): number;
74
+ /** Requests dispatched and not yet answered. */
75
+ get inFlight(): number;
76
+ /**
77
+ * Runs one envelope on the least-busy worker.
78
+ *
79
+ * `assemble` is forced off — the pool answers `columns`, and the caller
80
+ * assembles a `TimeSeries` from them if it wants one. Assembling
81
+ * worker-side would build an object that cannot cross the boundary.
82
+ *
83
+ * Pass `affinity` to pin related requests to one worker. Requests
84
+ * sharing an affinity key land on the same host, so its warm nodes are
85
+ * reused — worth it when a caller re-asks overlapping questions about
86
+ * one dataset, and pointless when every request is unrelated.
87
+ */
88
+ run(envelope: AsyncEnvelope, affinity?: string): Promise<RunResult>;
89
+ /** Terminates every worker. Outstanding requests reject. */
90
+ close(): Promise<void>;
91
+ }
92
+ //# sourceMappingURL=pool.d.ts.map