First pipeline
Build a pipeline that reads a list of strings and normalizes each row. Use Node.js 22.6+ for library code or Bun 1.3.14+ for the CLI.
Install the library
You can also install with pnpm, Yarn, or Bun. Use ESM imports; the library has no runtime dependencies.
Use the CLI
The tubeless executable requires Bun 1.3.14 or later on PATH.
Define a pipeline
Use createSteps to get a typed step function.
Define each step and connect it to its dependencies with dependsOn.
The normalize step receives the load step’s output, with its type inferred automatically.
import { createSteps, definePipeline } from "tubeless";
interface ImportOptions {
lines: readonly string[];
}
const { step } = createSteps<ImportOptions>();
const load = step("load", {
run: (_inputs, context) => context.options.lines,
});
const normalize = step("normalize", {
dependsOn: [load],
run: ({ load: rows }) =>
rows.map((row) => row.trim().toLowerCase()).filter(Boolean),
});
export const ImportPipeline = definePipeline({
id: "import",
steps: [load, normalize],
});
With no selection controls, every step runs. An explicit target selects only that
goal and its required prerequisites. The last step in execution order supplies the default result.
Here, that is normalize. If it produces no output, the result is
undefined. Add a finalizer with requireOutputs when missing
output should fail the run.
View the dependency graph
toMermaid() returns a Mermaid diagram of the dependencies. Solid arrows
show required inputs; dotted arrows show optional inputs and failure gates.
The CLI’s tubeless graph command returns the same diagram.
$ bunx tubeless graph ./examples/minimal-pipeline.ts --markdown --direction LR
```mermaidflowchart LR step0["load"] step1["normalize"] step0 --> step1```
Run or preview the pipeline
Use runOrThrow to receive the final value or an exception. Use run
to inspect step results and errors. Use plan to see which steps would run without
running steps. For CLI scripts, definePipelineCommand(pipeline) infers
flags from the input schema and supplies help, selection, dry runs, and reporting.
Start with examples/automatic-cli.ts for the one-line wrapper.
The terminal shows the advanced file-to-rows adapter in examples/cli-job.ts;
explicit flag definitions and option mapping are only needed for custom inputs.
$ bunx tubeless run ./examples/cli-job.ts -- --source examples/rows.txt --limit 2
Pipeline import: starting (2 steps, dryRun=false) -> load-rows - Read raw input records from the caller. ok load-rows (1ms) -> normalize-rows - Trim, lowercase, drop blanks, and apply --limit. ok normalize-rows (0ms) -> finalize ok finalize (0ms)Pipeline import: done in 1ms (status=completed, steps=2, errors=0)Normalized 2 row(s).
Share a project with CLI and Studio
Register each pipeline exactly once in tubeless.project.ts.
Put schema-backed pipelines directly in the list; use a command entry when
you need explicit parameters or option mapping. A command registers its
underlying pipeline, and project.get(id) keeps its exact types.
import { definePipelineCommand } from "tubeless/cli";
import { defineProject } from "tubeless/project";
import { ImportPipeline } from "./pipeline.js";
export default defineProject("data-jobs", [
definePipelineCommand(ImportPipeline, {
params: { lines: { type: "string", multiple: true } },
}),
], {
name: "Data jobs",
});