TUBELESS v0.3.8

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

$ npm install tubeless

You can also install with pnpm, Yarn, or Bun. Use ESM imports; the library has no runtime dependencies.

Use the CLI

$ bunx tubeless --help

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.

graph workbench
    $ 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.

run workbench
    $ 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",
});