@pond-ts/process
Computations as data, authored like code. In application code you build a graph fluently, typed end to end by your own op vocabulary:
const graph = process(registry, 'ACME_5m');
const close = graph.column('close');
const average = close.sma({ as: 'average', period: 20 });
const bands = average.bands({ as: 'bands', width: 2 });
const result = host.run(
graph.outputs({
average: average.columns(), // packed columns, for a chart
bands: bands.columns(),
latest: average.last(), // a small fact
}),
);
The request that emits is plain JSON — the same
{ op, params, inputs } format a saved view replays and a tool-calling
model composes directly from the registry's schema. One format, one
resolver, one content-addressed cache serving the renderer's columns and
the agent's facts alike. The fluent API is the developer's front door;
the data underneath is the contract.
@pond-ts/process is newly published and experimental: pre-1.0, and
the API is expected to move as friction reports land. Pin an exact
version.
Install — npm install @pond-ts/process pond-ts. Peer-depends on
pond-ts; the pond packages release together, so keep their ranges in
step.
When not to use this
Chaining is pond's mental model and stays the right default:
const out = series
.rolling('5m', { cpu: 'avg' })
.aggregate(Sequence.every('1h'), { cpu: 'max' });
If the pipeline is known when you write the code, chain it and skip this
package. @pond-ts/process exists for the case chaining genuinely cannot
express: when the pipeline itself is data —
- constructed at runtime from saved configuration;
- reshaped by a user in a node editor;
- composed by a model against a declared vocabulary;
- one expensive intermediate fanned out to several consumers wanting different slices;
- persisted, and run again later against refreshed source data.
The three layers
Every page in this section is about one of three roles, and keeping them separate is the design:
- The registry declares which operations exist. It is simultaneously the compile-time vocabulary that types the fluent API, the runtime validator, the metadata behind a picker, and the source of a JSON Schema projection a tool-calling model composes against.
- The request is plain data describing one graph. Authored fluently or composed remotely, it lands on the same data model, whose identity is content-addressed: the same question, however spelled, lands on the same node — which is what makes the id a cache key, a column name, and a provenance citation at once.
- The host outlives requests. It binds datasets, resolves remote sources, keeps each binding's graph warm, and answers requests with columns, facts, lineage, and per-node timings.
Where to go
Developer path first, wire format second:
- Build a processing graph — the end-to-end tutorial: declare a vocabulary, register a remote source, author a branched graph fluently, run it, watch the cache work.
- Authoring graphs — the fluent API reference: slots, typed params and roles, multi-output picking, facts, and the request it emits.
- Requests and responses — the request forms, selecting columns and facts, error policies, and reading the response's own account of what it did.
- Plans and identity — the data model underneath: specs, inputs, picked outputs, content-addressed ids, lineage, units. What a remote composer writes directly.
- The registry — declaring ops and folds, the param vocabulary, definition-time validation, and the JSON Schema projection for remote composers.
- The host — datasets, opaque async sources, revisions, and the lifecycle dials that bound a long-lived process.
- Caching and performance — what the content-addressed cache buys, byte budgets, ranged recompute, and the worker pool.
Relationship to the engine underneath
The plan layer sits on a small pull-based dataflow engine — typed ports, memoized nodes, dirty propagation that stops when values stop changing. That engine is exported today, and the package README documents it, but the RFC's conclusion (tracked as [PND-PROCSUB]) is that the plan layer is the consumer surface and the engine belongs underneath it. Prefer the plan layer unless you are building something the plan layer itself cannot express.