API

@stabrise/scaledp/registry

Stage metadata at run time, JSON in and out, and a code generator.

import {
    STAGE_SPECS,
    STAGE_CLASSES,
    getStageSpec,
    createStage,
    pipelineStage,
    pipelineFromDescriptors,
    describeStage,
    describePipeline,
    pipelineCode,
    pipelineModels,
    modelStatus,
    prefetchModels,
    evictModels,
} from '@stabrise/scaledp/registry'

Importing this module pulls in every stage class and no ML runtime — every engine is behind a dynamic import(). That is why it is a separate subpath rather than part of the root barrel.

For the narrative version, see Build a pipeline from data.

Functions

getStageSpec(type)StageSpec | undefined
createStage(descriptor)A live Stage, with descriptor.runtime applied (withRuntime). Throws on an unknown type; the stage's own validators still run.
pipelineStage(descriptor)A PipelineStage: the live stage plus the descriptor's id, enabled, when and barrier
pipelineFromDescriptors(descriptors)A Pipeline, keeping each descriptor's id, enabled, when, barrier and runtime
describeStage(stage)StageDescriptor with fully resolved options — type and options only
describePipeline(pipeline)StageDescriptor[], round-tripping the run settings above
pipelineCode(descriptors, options?)TypeScript source that builds the pipeline

describePipeline writes run settings only where they differ from the default — an id other than the derived one, enabled: false, a when, barrier: true, a non-empty runtime — so a plain pipeline describes as plain descriptors.

STAGE_CLASSES maps type name to constructor — useful for registerStages in a worker entry when you genuinely want all of them.

StageDescriptor

interface StageDescriptor {
    type: string                        // exported class name, e.g. 'PdfToImage'
    options?: Record<string, unknown>   // constructor options; structured-cloneable
    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
    runtime?: StageRuntime              // { executionProviders?: readonly string[] }
}

Re-exported from the core; the same shape the worker protocol sends. id, enabled, when and barrier are explained in Streaming runs; runtime overrides configure({ executionProviders }) for one stage, see Performance.

StageSpec

interface StageSpec {
    type: string
    label: string
    group: 'Read' | 'Detect' | 'Recognise' | 'Understand' | 'Transform'
    subpath: string
    summary: string
    consumes: readonly ColumnKind[]
    produces: ColumnKind
    alsoProduces?: readonly { param: string; kind: ColumnKind }[]
    peer?: string
    cache?: StageCacheSpec
    expands?: boolean
    terminal?: boolean
    defaults: Readonly<Record<string, unknown>>
    params: readonly StageParamSpec[]
}

type ColumnKind = 'bytes' | 'image' | 'boxes' | 'document' | 'ner' | 'orientations' | 'box'

defaults points at the stage's own frozen constant, not a copy. A spec cannot describe a default the stage does not have.

StageParamSpec

interface StageParamSpec {
    key: string
    kind: 'string' | 'number' | 'boolean' | 'enum' | 'stringList' | 'column' | 'columns' | 'color'
    label: string
    help?: string
    min?: number
    max?: number
    step?: number
    arity?: number        // 'columns': fixed length
    minArity?: number     // 'columns': minimum length
    accepts?: ColumnKind[]
    options?: readonly StageParamOption[]
    allowCustom?: boolean
    advanced?: boolean
    required?: boolean
}

interface StageParamOption { value: string; label: string; title?: string; disabled?: boolean }

Enum options are derived from the existing model registries — PADDLE_OCR_PRESETS, NER_MODELS, DETECTOR_MODELS — rather than duplicated, and private NER models come through disabled because using one without configure({ auth }) only produces a mid-pipeline 401.

advanced: true marks plumbing a form can collapse: pathCol, pageCol, keepInputData, propagateError, and an inherited-but-unused inputCol on multi-input stages.

StageCacheSpec

type StageCacheSpec =
    | { kind: 'paddle-preset'; param: string }
    | { kind: 'hf-repo'; param: string; approxBytes?: number }
    | { kind: 'ner-id'; param: string }

What the stage will download, and which parameter names it — enough to ask isCached before a run rather than during it.

pipelineCode

interface PipelineCodeOptions {
    variable?: string    // 'pipeline'
    imports?: boolean    // true
    indent?: number      // 4
}

Emits only options that differ from each stage's defaults, groups imports by subpath (root first, then alphabetical), and wraps one option per line once the call exceeds 100 characters.

Models

What a pipeline will download, before it runs. Every stage reports its own models through Stage.requiredModels(); these gather them for a whole pipeline. The narrative version is Managing models.

pipelineModels(source: Pipeline | readonly StageDescriptor[], options?: PipelineModelsOptions): Promise<PipelineModel[]>
modelStatus(models: readonly PipelineModel[]): Promise<ModelStatus[]>
prefetchModels(models: readonly PipelineModel[], options?: PrefetchOptions): Promise<PrefetchResult[]>
evictModels(models: readonly PipelineModel[]): Promise<void>

interface PipelineModelsOptions {
    enabled?: Readonly<Record<string, boolean>>   // as RunOptions.enabled, by stage id or name
}

interface PipelineModel {
    key: string        // `${repo}@${revision ?? 'main'}`
    label: string      // a name for people
    spec: ModelSpec    // every file of this model any stage needs
    stages: string[]   // ids of the stages that need it
}

interface ModelStatus extends PipelineModel {
    cached: boolean    // every file is in the cache
    bytes: number      // download size; 0 when unknown
}

interface PrefetchOptions {
    onProgress?: (model: PipelineModel, progress: ModelProgress) => void   // each ends with `ready`
    signal?: AbortSignal   // stops waiting; downloads shared with a run carry on into the cache
}

interface PrefetchResult { model: PipelineModel; ok: boolean; error?: string }

pipelineModels returns one entry per repo and revision, merging stages that share it — PaddleOCR's recogniser-only stage and its full stage on one preset are one download. A disabled stage's models are left out; a stage gated by when is included, since whether any page matches is not known before the run.

modelStatus sizes each model with downloadSize and checks it with isCached. prefetchModels never throws for a model that cannot be fetched — a private repo with no token is a normal state — it returns ok: false with the reason; it rejects only when signal aborts. It builds no sessions. evictModels deletes each model's files.

const models = await pipelineModels(descriptors, { enabled: { ner: settings.ner } })
const status = await modelStatus(models)
const missing = status.filter((m) => !m.cached)
const mb = missing.reduce((sum, m) => sum + m.bytes, 0) / 1e6
if (missing.length && confirm(`Download ${mb.toFixed(0)} MB?`)) await prefetchModels(missing)

Prefetching on the main thread and running in a worker needs nothing extra: the cache is shared IndexedDB, and downloads are coordinated across realms, so the worker reads what the page fetched.

Keeping it honest

test/unit/registry.test.ts fails when a stage gains a parameter with no catalogue entry, and the docs site's own generator fails when a stage has no page — so the catalogue, the code and this reference cannot drift apart silently.

On this page