Pipeline and Stage
The two classes everything else is built on.
Pipeline
class Pipeline {
constructor(stages: readonly (Stage | PipelineStage)[])
readonly stages: Stage[]
readonly entries: readonly PipelineEntry[]
transform(input: PipelineInput, options?: PipelineOptions & RunOptions): Promise<Row[]> // stage order
run(input: PipelineInput, options?: RunOptions & PipelineOptions): Promise<Row[]> // page order
events(input: PipelineInput, options?: Omit<RunOptions, 'onEvent'>): AsyncIterable<PipelineEvent> & { result: Promise<Row[]> }
dispose(): Promise<void>
}
interface PipelineStage {
stage: Stage
id?: string // default: the stage name, or `Name#index` when it repeats
enabled?: boolean // default true; RunOptions.enabled overrides per run
when?: Condition // run on matching rows only
barrier?: boolean // wait for every page before this stage
}
interface PipelineEntry { // a PipelineStage, resolved
readonly stage: Stage
readonly id: string
readonly index: number // position, counting disabled stages
readonly timingKey: string // key in execution_time.stages
readonly enabled: boolean
readonly when?: Condition
readonly barrier: boolean
}A StageDescriptor with these fields, plus runtime, builds the same thing from
JSON — see pipelineFromDescriptors.
interface PipelineOptions {
signal?: AbortSignal
onStage?: (name: string, ms: number, rows: number) => void
}
interface RunOptions {
order?: 'page' | 'stage' // run(): 'page', transform(): 'stage'
signal?: AbortSignal
pagesInFlight?: number // default 1
enabled?: Record<string, boolean> // by stage id or name
vars?: Record<string, Json> // for `{ var }` conditions
stages?: Record<string, StageRunOptions> // by stage id or name
initRetry?: 'run' | 'row' // default 'run'
rowUpdates?: { payload?: 'delta' | 'full' | 'none'; stages?: string[]; stripBinary?: boolean }
onEvent?: (event: PipelineEvent) => void
onRow?: (update: RowUpdate) => void
}
interface StageRunOptions {
maxConcurrency?: number // can only lower the stage's own
}Both orders return the same rows in the same order; every returned row gets
execution_time. A stage is initialised lazily, just before the first row that
reaches it, and a failed init lands on that stage's rows rather than throwing.
See Streaming runs for events, row updates, when
and barriers.
entries is fixed when the pipeline is constructed, and that is what runs:
stages is a copy taken from it, so pushing onto stages adds nothing to a run. To add
an annotation pass, build a new pipeline —
new Pipeline([...pipeline.entries, new ImageDrawBoxes()]) keeps every entry's
settings. Use RunOptions.enabled to switch a stage off without rebuilding.
Stage
abstract class Stage<P extends BaseStageParams = BaseStageParams> {
abstract readonly name: string
readonly uid: number // unique per instance
constructor(readonly params: P)
async init(ctx?: InitContext): Promise<void> // lazy, idempotent; ctx.onProgress for this stage
protected abstract apply(input, row, ctx): Promise<unknown>
protected expand(input, row, ctx): Promise<Row[] | null>
protected abstract onError(message: string, row: Row): unknown
async dispose(): Promise<void>
transform(rows: Row[], ctx: StageContext): Promise<Row[]>
// Scheduling hooks, all with safe defaults
get scope(): 'row' | 'group' | 'document' // 'row'; 'document' makes the stage a barrier
get maxConcurrency(): number // 1; raise for pure or network-bound stages
resources(): ResourceClaim[] // []; shared sessions a call holds
planPages(row, ctx): Promise<number[] | null> // null; PDF stages list their pages
requiredModels(): Promise<ModelRequirement[]> // []; the models init() will download
writes(): string[] // [outputCol]; what a row update carries
reads(): string[] // [inputCol, ...inputCols]
runtime: StageRuntime // { executionProviders? }
withRuntime(runtime: StageRuntime | undefined): this
failRows(rows: readonly Row[], message: string, ms?: number): Row[] // every row as if apply had failed
}requiredModels() lists, from the params alone, the model files the stage will
fetch when it first runs — nothing is downloaded. Engine-free stages return [].
It is what pipelineModels gathers, and a custom
stage that loads a model should override it so the pipeline's downloads can be
shown, prefetched and evicted before a run. See
Managing models.
runtime is read when the stage first builds its session, so set it (or call
withRuntime) before the stage runs. Thread count is not in it: onnxruntime-web
fixes one per realm, so stages that need different counts go in different
workers — see Performance.
failRows is what the scheduler uses when a stage cannot start at all (a failed
init): each row gets the stage's onError value, honouring keepInputData, and
is charged ms spread across the rows.
name is also the StageDescriptor.type. The full contract, including why
onError must return a well-formed empty schema instance, is in
The stage lifecycle.
Rows and input
type Row = Record<string, unknown>
type PipelineInput =
| Uint8Array | ArrayBuffer | Blob | File | string | Row | Row[]
interface InitContext { stageId?: string; signal?: AbortSignal; onProgress?: (p: ModelProgress) => void }
interface StageContext extends InitContext { index: number; runId?: string }import { toRows } from '@stabrise/scaledp'toRows is exported for anyone building rows by hand. Arrays pass through
shallow-copied; a string is fetched and throws on a non-ok response; bytes
become { content, path: 'memory' }; a File uses its name as the path.
Timing columns
const EXECUTION_TIME_COL = 'execution_time'
const ROW_TIME_COL = 'row_time'
interface ExecutionTime { stages: Record<string, number>; total: number }
interface RowTime { stages: Record<string, number>; total: number }See Timings for what each measures and why they differ.
Params
import { BASE_STAGE_DEFAULTS, resolveParams, assertInRange, assertPositiveInt } from '@stabrise/scaledp'
interface BaseStageParams {
inputCol: string // 'content'
outputCol: string // 'output'
pathCol: string // 'path'
pageCol: string // 'page'
keepInputData: boolean // false
propagateError: boolean // false
}
type Validator<T> = { [K in keyof T]?: (value: T[K], all: T) => void }
resolveParams<T>(defaults: Readonly<T>, options?: Partial<T>, validators?: Validator<T>): TresolveParams ignores undefined values, so spreading an optional config never
erases a default. Validators run against the resolved object and throw
RangeError.
Errors
import {
ScaleDpError, ImageError, OcrError, DetectionError, NerError, ConfigError,
formatException,
} from '@stabrise/scaledp'All extend ScaleDpError, which carries the stage it came from.
formatException(stage, error) produces the string that lands in an output
schema's exception. See The error contract.