tubeless 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/abort.d.ts +12 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js +49 -0
- package/dist/abort.js.map +1 -0
- package/dist/batch.d.ts +22 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +73 -0
- package/dist/batch.js.map +1 -0
- package/dist/checkpoint.d.ts +33 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +71 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/child-execution.d.ts +30 -0
- package/dist/child-execution.d.ts.map +1 -0
- package/dist/child-execution.js +270 -0
- package/dist/child-execution.js.map +1 -0
- package/dist/cli-command.d.ts +29 -0
- package/dist/cli-command.d.ts.map +1 -0
- package/dist/cli-command.js +314 -0
- package/dist/cli-command.js.map +1 -0
- package/dist/cli-parser.d.ts +28 -0
- package/dist/cli-parser.d.ts.map +1 -0
- package/dist/cli-parser.js +557 -0
- package/dist/cli-parser.js.map +1 -0
- package/dist/cli-pipeline-command.d.ts +84 -0
- package/dist/cli-pipeline-command.d.ts.map +1 -0
- package/dist/cli-pipeline-command.js +185 -0
- package/dist/cli-pipeline-command.js.map +1 -0
- package/dist/cli-types.d.ts +220 -0
- package/dist/cli-types.d.ts.map +1 -0
- package/dist/cli-types.js +19 -0
- package/dist/cli-types.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +8 -0
- package/dist/env.js.map +1 -0
- package/dist/file-utils.d.ts +4 -0
- package/dist/file-utils.d.ts.map +1 -0
- package/dist/file-utils.js +35 -0
- package/dist/file-utils.js.map +1 -0
- package/dist/interactive-reporter.d.ts +38 -0
- package/dist/interactive-reporter.d.ts.map +1 -0
- package/dist/interactive-reporter.js +308 -0
- package/dist/interactive-reporter.js.map +1 -0
- package/dist/lifecycle.d.ts +17 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +77 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/live-ticker.d.ts +21 -0
- package/dist/live-ticker.d.ts.map +1 -0
- package/dist/live-ticker.js +377 -0
- package/dist/live-ticker.js.map +1 -0
- package/dist/mapped-child-progress.d.ts +109 -0
- package/dist/mapped-child-progress.d.ts.map +1 -0
- package/dist/mapped-child-progress.js +133 -0
- package/dist/mapped-child-progress.js.map +1 -0
- package/dist/node.d.ts +5 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +5 -0
- package/dist/node.js.map +1 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +19 -0
- package/dist/paths.js.map +1 -0
- package/dist/pipeline-command-marker.d.ts +5 -0
- package/dist/pipeline-command-marker.d.ts.map +1 -0
- package/dist/pipeline-command-marker.js +21 -0
- package/dist/pipeline-command-marker.js.map +1 -0
- package/dist/pipeline-diagnostics.d.ts +4 -0
- package/dist/pipeline-diagnostics.d.ts.map +1 -0
- package/dist/pipeline-diagnostics.js +17 -0
- package/dist/pipeline-diagnostics.js.map +1 -0
- package/dist/pipeline-module.d.ts +62 -0
- package/dist/pipeline-module.d.ts.map +1 -0
- package/dist/pipeline-module.js +157 -0
- package/dist/pipeline-module.js.map +1 -0
- package/dist/pipeline.d.ts +606 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +1444 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/progress.d.ts +4 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +8 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt-select.d.ts +81 -0
- package/dist/prompt-select.d.ts.map +1 -0
- package/dist/prompt-select.js +197 -0
- package/dist/prompt-select.js.map +1 -0
- package/dist/rate-limit.d.ts +7 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +23 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/render.d.ts +21 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +60 -0
- package/dist/render.js.map +1 -0
- package/dist/reporter-entry.d.ts +3 -0
- package/dist/reporter-entry.d.ts.map +1 -0
- package/dist/reporter-entry.js +3 -0
- package/dist/reporter-entry.js.map +1 -0
- package/dist/reporter.d.ts +50 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +167 -0
- package/dist/reporter.js.map +1 -0
- package/dist/retry.d.ts +28 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +59 -0
- package/dist/retry.js.map +1 -0
- package/dist/run-store-sqlite.d.ts +12 -0
- package/dist/run-store-sqlite.d.ts.map +1 -0
- package/dist/run-store-sqlite.js +187 -0
- package/dist/run-store-sqlite.js.map +1 -0
- package/dist/run-store-ui-http.d.ts +5 -0
- package/dist/run-store-ui-http.d.ts.map +1 -0
- package/dist/run-store-ui-http.js +19 -0
- package/dist/run-store-ui-http.js.map +1 -0
- package/dist/run-store-ui-page.d.ts +2 -0
- package/dist/run-store-ui-page.d.ts.map +1 -0
- package/dist/run-store-ui-page.js +759 -0
- package/dist/run-store-ui-page.js.map +1 -0
- package/dist/run-store-ui-protocol.d.ts +28 -0
- package/dist/run-store-ui-protocol.d.ts.map +1 -0
- package/dist/run-store-ui-protocol.js +84 -0
- package/dist/run-store-ui-protocol.js.map +1 -0
- package/dist/run-store-ui-state.d.ts +21 -0
- package/dist/run-store-ui-state.d.ts.map +1 -0
- package/dist/run-store-ui-state.js +65 -0
- package/dist/run-store-ui-state.js.map +1 -0
- package/dist/run-store-ui.d.ts +25 -0
- package/dist/run-store-ui.d.ts.map +1 -0
- package/dist/run-store-ui.js +217 -0
- package/dist/run-store-ui.js.map +1 -0
- package/dist/run-store.d.ts +109 -0
- package/dist/run-store.d.ts.map +1 -0
- package/dist/run-store.js +345 -0
- package/dist/run-store.js.map +1 -0
- package/dist/testing.d.ts +40 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +83 -0
- package/dist/testing.js.map +1 -0
- package/dist/tracing-internal.d.ts +21 -0
- package/dist/tracing-internal.d.ts.map +1 -0
- package/dist/tracing-internal.js +206 -0
- package/dist/tracing-internal.js.map +1 -0
- package/dist/tracing-json.d.ts +12 -0
- package/dist/tracing-json.d.ts.map +1 -0
- package/dist/tracing-json.js +8 -0
- package/dist/tracing-json.js.map +1 -0
- package/dist/tracing-otel.d.ts +36 -0
- package/dist/tracing-otel.d.ts.map +1 -0
- package/dist/tracing-otel.js +60 -0
- package/dist/tracing-otel.js.map +1 -0
- package/dist/tracing.d.ts +48 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +2 -0
- package/dist/tracing.js.map +1 -0
- package/dist/workbench-bin.d.ts +3 -0
- package/dist/workbench-bin.d.ts.map +1 -0
- package/dist/workbench-bin.js +8 -0
- package/dist/workbench-bin.js.map +1 -0
- package/dist/workbench-graph.d.ts +3 -0
- package/dist/workbench-graph.d.ts.map +1 -0
- package/dist/workbench-graph.js +64 -0
- package/dist/workbench-graph.js.map +1 -0
- package/dist/workbench-inspect.d.ts +3 -0
- package/dist/workbench-inspect.d.ts.map +1 -0
- package/dist/workbench-inspect.js +65 -0
- package/dist/workbench-inspect.js.map +1 -0
- package/dist/workbench-plan.d.ts +3 -0
- package/dist/workbench-plan.d.ts.map +1 -0
- package/dist/workbench-plan.js +72 -0
- package/dist/workbench-plan.js.map +1 -0
- package/dist/workbench-run.d.ts +13 -0
- package/dist/workbench-run.d.ts.map +1 -0
- package/dist/workbench-run.js +115 -0
- package/dist/workbench-run.js.map +1 -0
- package/dist/workbench-shared.d.ts +67 -0
- package/dist/workbench-shared.d.ts.map +1 -0
- package/dist/workbench-shared.js +163 -0
- package/dist/workbench-shared.js.map +1 -0
- package/dist/workbench-studio.d.ts +26 -0
- package/dist/workbench-studio.d.ts.map +1 -0
- package/dist/workbench-studio.js +57 -0
- package/dist/workbench-studio.js.map +1 -0
- package/dist/workbench-ui.d.ts +3 -0
- package/dist/workbench-ui.d.ts.map +1 -0
- package/dist/workbench-ui.js +262 -0
- package/dist/workbench-ui.js.map +1 -0
- package/dist/workbench.d.ts +5 -0
- package/dist/workbench.d.ts.map +1 -0
- package/dist/workbench.js +43 -0
- package/dist/workbench.js.map +1 -0
- package/docs/README.md +31 -0
- package/docs/agent-guide.md +168 -0
- package/docs/api-reference.md +269 -0
- package/docs/api-report.json +298 -0
- package/docs/child-pipeline-composition.md +185 -0
- package/docs/cli.md +118 -0
- package/docs/concepts.md +502 -0
- package/docs/getting-started.md +162 -0
- package/docs/llms.txt +62 -0
- package/docs/recipes.md +51 -0
- package/docs/studio.md +86 -0
- package/examples/best-effort.ts +42 -0
- package/examples/cancellation-and-testing.ts +45 -0
- package/examples/child-pipeline.ts +51 -0
- package/examples/cli-job.ts +86 -0
- package/examples/conditional-step.ts +41 -0
- package/examples/fan-out-progress.ts +53 -0
- package/examples/local-observability.ts +80 -0
- package/examples/publish-with-gates.ts +56 -0
- package/examples/rendering.ts +34 -0
- package/examples/resumable-enrichment.ts +75 -0
- package/examples/tracing.ts +35 -0
- package/examples/tsconfig.json +15 -0
- package/examples/typed-import.ts +54 -0
- package/examples/validated-boundaries.ts +75 -0
- package/package.json +156 -0
package/docs/concepts.md
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# Core concepts
|
|
2
|
+
|
|
3
|
+
## Definition model
|
|
4
|
+
|
|
5
|
+
A pipeline is an ordered declaration of typed steps plus a finalizer.
|
|
6
|
+
`definePipeline` immediately rejects invalid static graphs (duplicate/reserved
|
|
7
|
+
IDs, missing or contradictory dependencies, and cycles). Planning validates
|
|
8
|
+
option-dependent step selection before execution begins.
|
|
9
|
+
|
|
10
|
+
Literal duplicate step IDs are also rejected by TypeScript at the
|
|
11
|
+
`definePipeline` call. Runtime definition validation remains the backstop for
|
|
12
|
+
widened arrays and dynamically assembled definitions.
|
|
13
|
+
|
|
14
|
+
Use one `createSteps<TOptions>()` factory per pipeline. A step object is both its
|
|
15
|
+
definition and its typed dependency token.
|
|
16
|
+
|
|
17
|
+
The ID is the stable machine identity used for dependencies, output keys,
|
|
18
|
+
selection, and tracing. An optional `name` overrides how reporters and printed
|
|
19
|
+
plans display the step without changing that identity.
|
|
20
|
+
|
|
21
|
+
## Dependency choices
|
|
22
|
+
|
|
23
|
+
| Field | Supplies data | Blocks after failure | Typical use |
|
|
24
|
+
| -------------------- | ------------- | -------------------- | ----------------------------------- |
|
|
25
|
+
| `dependsOn` | Required | Yes | Transformation needs upstream value |
|
|
26
|
+
| `optionalDependsOn` | When present | No | Fallback or partial rerun |
|
|
27
|
+
| `skipAfterFailureOf` | No | Yes | Publish/write safety gate |
|
|
28
|
+
|
|
29
|
+
Prefer required dependencies unless partial execution is an intentional part of
|
|
30
|
+
the workflow. A failure gate blocks after either `failed` or `cancelled`; both
|
|
31
|
+
mean the guarded prerequisite did not safely complete.
|
|
32
|
+
|
|
33
|
+
Shorter aliases such as `needs`, `uses`, and `gatedBy` were evaluated and not
|
|
34
|
+
adopted. `uses` does not communicate optionality, and aliases would leave two
|
|
35
|
+
vocabularies for the same graph. The explicit fields above remain canonical.
|
|
36
|
+
|
|
37
|
+
## Three kinds of non-execution
|
|
38
|
+
|
|
39
|
+
- **Structural skip:** a dependency, dry-run rule, filter, abort, or fail-fast
|
|
40
|
+
decision prevents execution. Dependents that require the step do not run.
|
|
41
|
+
- **Policy skip:** a `step.skippable` predicate deliberately decides that work
|
|
42
|
+
is unnecessary. It may publish a value and unlock dependents.
|
|
43
|
+
- **Failure:** the step ran and threw. Fail-fast stops new work by default;
|
|
44
|
+
`continueOnError` lets independent work proceed.
|
|
45
|
+
|
|
46
|
+
`continueOnError` changes scheduling, not success: the structured run result
|
|
47
|
+
remains unsuccessful and `runOrThrow` still throws. Inspect `run()` when a
|
|
48
|
+
best-effort final value is useful despite recorded failures.
|
|
49
|
+
|
|
50
|
+
Any `step.skippable` step has output type `T | undefined`, even if current skip
|
|
51
|
+
paths publish a value. Dependents must handle the absence explicitly.
|
|
52
|
+
|
|
53
|
+
## Selection and finalization
|
|
54
|
+
|
|
55
|
+
Pipeline definitions declare supported downstream goals with step references:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
definePipeline({
|
|
59
|
+
id: "publish",
|
|
60
|
+
steps: [build, validate, publish],
|
|
61
|
+
targets: [publish],
|
|
62
|
+
finalize: requireOutputs([publish], ({ publish }) => publish),
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
At call sites, `targets` accepts only those declared literal IDs. The planner
|
|
67
|
+
recursively selects each target's required inputs and failure gates, while
|
|
68
|
+
leaving optional-only inputs out. `pipeline.targetIds` and CLI `--target`
|
|
69
|
+
discovery expose the same declared set. Internal steps are not public goals;
|
|
70
|
+
`stepIds` remains an exact filter for partial reruns and other advanced
|
|
71
|
+
workflows and does not add prerequisites. Empty selections, unknown,
|
|
72
|
+
undeclared, or duplicate IDs, and a run that supplies both fields fail during
|
|
73
|
+
planning.
|
|
74
|
+
|
|
75
|
+
Every planned step includes `selectionReasons`, a stable discriminated union
|
|
76
|
+
that explains inclusion or omission without requiring callers to reconstruct
|
|
77
|
+
the graph. A target plan can report a direct `target`, a
|
|
78
|
+
`required-dependency`, a `failure-gate`, an excluded `optional-only` input, or
|
|
79
|
+
an `outside-target-closure` omission. Exact filters use `exact` and
|
|
80
|
+
`not-selected`; unfiltered plans use `all`.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const plan = pipeline.plan({ targets: ["publish"] });
|
|
84
|
+
const load = plan.steps.find((step) => step.id === "load");
|
|
85
|
+
|
|
86
|
+
// A shared prerequisite can explain every path that selected it.
|
|
87
|
+
load?.selectionReasons;
|
|
88
|
+
// [{ kind: "required-dependency", dependentId: "build", targetId: "publish" }]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`selected` remains the convenient boolean and `skipReason` describes execution
|
|
92
|
+
disposition such as `filtered`, `dry-run`, or `unmet-dependency`.
|
|
93
|
+
|
|
94
|
+
Render a plan at the presentation boundary instead of duplicating its selection
|
|
95
|
+
reason switch in each command or tool:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { renderPipelinePlan } from "tubeless/render";
|
|
99
|
+
|
|
100
|
+
const plan = pipeline.plan({ targets: ["publish"] });
|
|
101
|
+
const terminalText = renderPipelinePlan(plan);
|
|
102
|
+
const machineJson = renderPipelinePlan(plan, { format: "json" });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Human output explains target and dependency provenance by default; pass
|
|
106
|
+
`{ explain: false }` for a compact disposition-only view. JSON output preserves
|
|
107
|
+
the original structured plan fields for machine consumers.
|
|
108
|
+
|
|
109
|
+
## Step statuses and structured errors
|
|
110
|
+
|
|
111
|
+
Steps have one canonical, enforced lifecycle:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
planned ──┬──> running ──┬──> complete
|
|
115
|
+
│ ├──> skipped
|
|
116
|
+
│ ├──> cancelled
|
|
117
|
+
│ └──> failed
|
|
118
|
+
├──> skipped
|
|
119
|
+
└──> cancelled
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The runtime derives focused hooks from this state machine. Use
|
|
123
|
+
`onStepStart`, `onStepProgress`, `onStepComplete`, `onStepSkip`,
|
|
124
|
+
`onStepCancel`, or `onStepFail` for ordinary pipeline integration; each
|
|
125
|
+
callback receives metadata narrowed to that case. `onStepPlan` is available
|
|
126
|
+
for plan-aware tooling. `onStepStatus` is an additive catch-all for consumers
|
|
127
|
+
that need the complete discriminated lifecycle, such as renderers and event
|
|
128
|
+
stores. Registering it does not replace the focused callbacks.
|
|
129
|
+
|
|
130
|
+
A `running` status may be published repeatedly as progress changes. Every
|
|
131
|
+
selected step ends in exactly one terminal report; cancellation is distinct
|
|
132
|
+
from failure, including selected steps that were cancelled before they started.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const hooks: PipelineHooks = {
|
|
136
|
+
onStepProgress({ progress, step }) {
|
|
137
|
+
renderProgress(step.id, progress);
|
|
138
|
+
},
|
|
139
|
+
onStepSkip({ reason, step }) {
|
|
140
|
+
explainSkip(step.id, reason);
|
|
141
|
+
},
|
|
142
|
+
onStepFail({ error, step }) {
|
|
143
|
+
reportError(step.id, error);
|
|
144
|
+
},
|
|
145
|
+
onStepCancel({ error, step }) {
|
|
146
|
+
reportCancellation(step.id, error);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Every `PipelineError` has package-owned `code`, `phase`, and `kind` fields.
|
|
152
|
+
Branch on those fields rather than message text. `phase` locates the lifecycle
|
|
153
|
+
boundary (`definition`, `planning`, `execution`, or `finalization`); `kind`
|
|
154
|
+
distinguishes definition, selection, step, dependency, cancellation, child, and
|
|
155
|
+
finalization problems. If a thrown error supplied its own machine code, it is
|
|
156
|
+
retained separately as `sourceCode`. Native `Error.cause` chains are copied into
|
|
157
|
+
a bounded, cycle-safe `PipelineErrorCause` tree containing only message, name,
|
|
158
|
+
source code, and nested cause fields, so reports and trace events stay JSON-safe.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const result = await pipeline.run(options);
|
|
162
|
+
const first = result.errors[0];
|
|
163
|
+
|
|
164
|
+
if (first?.code === "TUBELESS_STEP_FAILED" && first.sourceCode === "ENOENT") {
|
|
165
|
+
// A step failed because its underlying operation could not find a file.
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`runOrThrow` raises `PipelineExecutionError` with the full structured result on
|
|
170
|
+
`result` and the original thrown value on native `cause`. Its default message
|
|
171
|
+
identifies the pipeline, phase, package code, step, and deepest normalized cause.
|
|
172
|
+
`PipelineDefinitionError` uses the same diagnostic summary for every rejected
|
|
173
|
+
definition issue. `renderPipelineError` from `tubeless/render` exposes that
|
|
174
|
+
same diagnostic formatting directly and can emit the structured error as JSON.
|
|
175
|
+
|
|
176
|
+
`PipelineStepReport` is the terminal-state union. Successful reports carry
|
|
177
|
+
timing, skipped reports carry `reason` plus optional `message` and
|
|
178
|
+
`dependencyId`, and failed/cancelled reports carry a structured `error`.
|
|
179
|
+
Hooks and tracing consume the same terminal objects rather than reconstructing
|
|
180
|
+
status from separate callbacks.
|
|
181
|
+
|
|
182
|
+
## Versioned run records
|
|
183
|
+
|
|
184
|
+
`run()` returns one versioned `PipelineRun<TResult>`. Every execution has a
|
|
185
|
+
`runId`, terminal `status`, start and finish timestamps, structured errors, and
|
|
186
|
+
one terminal report per planned step. Pass `context.runId` and
|
|
187
|
+
`context.parentRunId` when an external orchestrator owns correlation; otherwise
|
|
188
|
+
core generates an opaque run ID. The same IDs are available in step contexts
|
|
189
|
+
and optional trace exports.
|
|
190
|
+
|
|
191
|
+
An actual step execution receives one `attemptId`. It appears on the
|
|
192
|
+
`PipelineStepContext`, its terminal `PipelineStepReport`, and trace lifecycle
|
|
193
|
+
records. Executed reports also carry start and finish timestamps. Structural
|
|
194
|
+
skips and steps cancelled before starting have neither an attempt ID nor a start
|
|
195
|
+
timestamp because their handlers never ran. `context.reportAttempt()` describes
|
|
196
|
+
retry activity within that execution; it does not create another persisted
|
|
197
|
+
attempt model.
|
|
198
|
+
|
|
199
|
+
The terminal record deliberately does not copy log or progress streams. Use the
|
|
200
|
+
injected logger and hooks for live observation, or tracing when durable event
|
|
201
|
+
export is required. Errors and cancellation remain represented directly in the
|
|
202
|
+
run and terminal step reports. The run currently has `version: 1`, also exported
|
|
203
|
+
as `RUN_MODEL_VERSION`; persist that field and branch on it before decoding a
|
|
204
|
+
stored run. Durations are derived by subtracting the relevant timestamps.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const result = await pipeline.run(options, {
|
|
208
|
+
...defaultPipelineContext(),
|
|
209
|
+
runId: externalJobId,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
result.runId;
|
|
213
|
+
result.status;
|
|
214
|
+
result.steps.find((step) => step.attemptId)?.attemptId;
|
|
215
|
+
result.finishedAtMs - result.startedAtMs;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Local event store and studio
|
|
219
|
+
|
|
220
|
+
The local studio is a composition of existing boundaries, not part of pipeline
|
|
221
|
+
execution:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
pipeline definition → dependency-free executor → trace exporter
|
|
225
|
+
↘ append-only SQLite
|
|
226
|
+
↘ local studio
|
|
227
|
+
↘ injected command launcher
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`openSqlitePipelineRunStore()` implements `PipelineTraceExporter` and appends
|
|
231
|
+
every lifecycle record to one versioned SQLite event table. Database triggers
|
|
232
|
+
reject updates and deletes. Current run state, history, step attempts, progress,
|
|
233
|
+
logs, errors, and observed definition graphs are projections of that immutable
|
|
234
|
+
stream; they are not competing executor models.
|
|
235
|
+
|
|
236
|
+
The SQLite adapter also exposes an explicit `clearHistory()` maintenance reset.
|
|
237
|
+
It deletes the complete event history and compacts the database while restoring
|
|
238
|
+
the append-only triggers in the same transaction; individual event updates and
|
|
239
|
+
deletes remain forbidden. The workbench injects this capability into its
|
|
240
|
+
loopback studio with a destructive confirmation and refuses to clear while it
|
|
241
|
+
knows a browser-launched run is still live. Persisted runs left active by an
|
|
242
|
+
interrupted process can still be cleared after confirmation. A directly embedded
|
|
243
|
+
studio stays history-immutable unless its caller explicitly injects the same
|
|
244
|
+
capability and may report its own known live writers through `isBusy()`.
|
|
245
|
+
|
|
246
|
+
Trace-enabled contexts route calls made through `context.log` into correlated
|
|
247
|
+
`pipeline.log` events while still forwarding them to the injected logger.
|
|
248
|
+
Definition metadata is attached to `step.planned` records, so the studio can
|
|
249
|
+
render observed graphs without importing application pipeline modules. The
|
|
250
|
+
first planned step from a newer run replaces that pipeline ID's observed targets
|
|
251
|
+
and steps, so a changed definition does not retain older structure while a run
|
|
252
|
+
that fails validation before planning does not erase the last usable graph.
|
|
253
|
+
|
|
254
|
+
The main `tubeless` entrypoint never imports SQLite or the UI. The optional
|
|
255
|
+
`tubeless/run-store/sqlite` adapter selects the runtime-provided SQLite
|
|
256
|
+
implementation, and `tubeless/run-store/ui` is a separate HTTP projection.
|
|
257
|
+
It is read-only unless the caller injects a `PipelineRunStudioLauncher`.
|
|
258
|
+
Likewise, ordinary `tubeless run` remains unchanged; pass `--store` to record a run
|
|
259
|
+
and invoke `tubeless ui` only when a browser view is useful.
|
|
260
|
+
|
|
261
|
+
Observed definitions are never treated as executable registrations: an event
|
|
262
|
+
stream does not contain a trusted module path or a command's domain contract.
|
|
263
|
+
`tubeless ui --command ./path/to/command.ts` explicitly loads only marked
|
|
264
|
+
`definePipelineCommand` exports and passes bounded structured form values
|
|
265
|
+
through their validation, option mapping, and execution path. No shell or argv
|
|
266
|
+
round trip is involved. Launch-enabled workbench servers are restricted to
|
|
267
|
+
loopback, while the UI HTTP module stays execution-agnostic through its injected
|
|
268
|
+
capability. Each command exposes an immutable, JSON-safe descriptor derived
|
|
269
|
+
from the same effective schema used by its parser, including built-in flags.
|
|
270
|
+
The studio uses that descriptor for checkboxes, numeric inputs, constrained
|
|
271
|
+
selects, paths, and repeatable values.
|
|
272
|
+
Planning is a separate read-only capability: **Preview plan** passes the current
|
|
273
|
+
dry-run and step/target values to the real pipeline planner and expands the
|
|
274
|
+
result beneath the launch form. Domain parameters remain visible for the
|
|
275
|
+
eventual run but are not interpreted by the planner, no run is recorded, and
|
|
276
|
+
previewing is optional. Opaque `fromPipeline` and `forEachPipeline` steps carry
|
|
277
|
+
`nestedPipeline` metadata so the preview can distinguish ordinary work from a
|
|
278
|
+
single child pipeline or runtime fan-out without flattening child selection.
|
|
279
|
+
|
|
280
|
+
Studio snapshots page through the store once and then fetch only events after
|
|
281
|
+
the last observed sequence. Those pages go through `createPipelineRunProjector`
|
|
282
|
+
from `tubeless/run-store`; a refresh with no newer ids returns the cached
|
|
283
|
+
snapshot instead of re-folding history. Store-local event ids are monotonic and
|
|
284
|
+
may start at `0`. Duplicate or out-of-order ids are ignored. Use
|
|
285
|
+
`projectPipelineRunStore` when the caller already holds a complete event list
|
|
286
|
+
and does not need incremental refresh. History therefore continues past an
|
|
287
|
+
adapter's per-query safety cap without reloading the entire database on every
|
|
288
|
+
refresh.
|
|
289
|
+
|
|
290
|
+
For a reusable catalog, `definePipelineStudio` declares versioned command-module
|
|
291
|
+
references in one dependency-free manifest. Run `tubeless ui ./tubeless.studio.ts` to
|
|
292
|
+
load it. Module paths and the optional execution `cwd` resolve from the manifest
|
|
293
|
+
instead of the caller's shell directory; duplicate or malformed declarations
|
|
294
|
+
fail before the server listens. Presentation-name overrides never change run or
|
|
295
|
+
pipeline identity.
|
|
296
|
+
|
|
297
|
+
Cancellation is classified from an actual abort error or propagated child
|
|
298
|
+
cancellation, not merely from the signal's current state. An unrelated failure
|
|
299
|
+
that races with `abort()` remains a step or finalization failure.
|
|
300
|
+
|
|
301
|
+
Finalizer inputs remain partial because dry-run policy, exact filtering, and
|
|
302
|
+
best-effort execution can omit outputs. Wrap a normal finalizer with
|
|
303
|
+
`requireOutputs([stepA, stepB], callback)` to declare which output slots make a
|
|
304
|
+
valid result. The callback receives those values as required properties, and
|
|
305
|
+
the run fails finalization with the missing step IDs if any slot is absent. A
|
|
306
|
+
successfully published `undefined` still counts as an output; structural
|
|
307
|
+
absence is determined by whether the slot was published.
|
|
308
|
+
|
|
309
|
+
When a pipeline uses `requireOutputs`, every declared target is checked during
|
|
310
|
+
definition construction. Its required-input and failure-gate closure must
|
|
311
|
+
contain all required finalizer steps, so an advertised target cannot complete
|
|
312
|
+
its work and then fail only because it intentionally omitted the pipeline's
|
|
313
|
+
result. Plain finalizers remain appropriate when selected goals intentionally
|
|
314
|
+
produce a partial domain result.
|
|
315
|
+
|
|
316
|
+
### Why targets are declared
|
|
317
|
+
|
|
318
|
+
Three contracts were evaluated after dependency-aware selection shipped:
|
|
319
|
+
|
|
320
|
+
- expose every step as a target and keep the global finalizer;
|
|
321
|
+
- add a separate selected-execution runner returning raw output slots; or
|
|
322
|
+
- declare the pipeline's meaningful public goals and retain one finalized
|
|
323
|
+
domain result.
|
|
324
|
+
|
|
325
|
+
Declared targets were selected because they preserve the existing
|
|
326
|
+
`run`/`runOrThrow` result contract, add only one short definition field, keep
|
|
327
|
+
steps as implementation details, and let definition validation prove
|
|
328
|
+
`requireOutputs` compatibility. A second runner would create competing
|
|
329
|
+
execution/result semantics, while goal-specific finalizers add ceremony that
|
|
330
|
+
current production pipelines do not need. Exact `stepIds` remains the explicit
|
|
331
|
+
escape hatch for low-level partial work.
|
|
332
|
+
|
|
333
|
+
## Dry runs
|
|
334
|
+
|
|
335
|
+
Every step has one normal `run` handler and an optional dry-run policy:
|
|
336
|
+
|
|
337
|
+
- Omit `dryRun` when the normal handler is safe and useful during a dry run.
|
|
338
|
+
- Set `dryRun: "skip"` for filesystem writes, database mutation, publication,
|
|
339
|
+
email, and other external side effects. The step is reported as structurally
|
|
340
|
+
skipped and its required dependents do not run.
|
|
341
|
+
- Provide a `dryRun(inputs, context)` handler to substitute a side-effect-free
|
|
342
|
+
preview. It must return the same output type as `run`, so dependents can use
|
|
343
|
+
the preview normally.
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const publish = step("publish", {
|
|
347
|
+
dependsOn: [build],
|
|
348
|
+
dryRun: ({ build }) => ({ id: `preview:${build.id}` }),
|
|
349
|
+
run: ({ build }) => publishArtifact(build),
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Dry run is not a rollback mechanism. Read-only validation and resolution steps
|
|
354
|
+
may run normally so the preview remains useful.
|
|
355
|
+
|
|
356
|
+
## Validated boundaries
|
|
357
|
+
|
|
358
|
+
`tubeless` implements the small Standard Schema V1 structural contract and
|
|
359
|
+
does not import a validation library. Pass a domain-options schema to
|
|
360
|
+
`createSteps(schema)`, an `outputSchema` to an ordinary step, or a
|
|
361
|
+
`resultSchema` to `definePipeline`:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
const step = createSteps(optionsSchema);
|
|
365
|
+
|
|
366
|
+
const parse = step("parse", {
|
|
367
|
+
outputSchema: parsedRowsSchema,
|
|
368
|
+
run: (_inputs, context) => readRows(context.options.source),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const pipeline = definePipeline({
|
|
372
|
+
id: "validated-import",
|
|
373
|
+
steps: [parse],
|
|
374
|
+
resultSchema,
|
|
375
|
+
finalize: requireOutputs([parse], ({ parse }) => summarize(parse)),
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Schema input and output types are inferred independently. Callers pass the
|
|
380
|
+
options schema's input type, steps read its validated output type, a step's
|
|
381
|
+
`run` and dry-run handler return its schema input type, dependents receive its
|
|
382
|
+
schema output type, and `runOrThrow` returns the result schema's output type.
|
|
383
|
+
Built-in run controls are separate from domain-options validation, so strict
|
|
384
|
+
object schemas do not need to know about `dryRun`, `targets`, or other executor
|
|
385
|
+
policy. Callers pass those controls alongside domain fields; the executor removes
|
|
386
|
+
the reserved control keys before validation while preserving class methods,
|
|
387
|
+
getters, inherited and non-enumerable properties, and symbol keys. Steps receive
|
|
388
|
+
the validated output object directly; the executor neither flattens it nor
|
|
389
|
+
overlays control fields onto it.
|
|
390
|
+
|
|
391
|
+
Options are validated once after structural planning and before any step
|
|
392
|
+
starts. Step values are validated before publication, including values from a
|
|
393
|
+
custom dry-run handler or policy skip. Finalized values are validated before a
|
|
394
|
+
run is marked successful. Sync and async Standard Schema validators are both
|
|
395
|
+
supported during `run`; `plan()` stays synchronous and does not invoke schemas.
|
|
396
|
+
|
|
397
|
+
Failures retain normalized issue messages and paths on `PipelineError.issues`.
|
|
398
|
+
Use the stable codes `TUBELESS_OPTIONS_VALIDATION_FAILED`,
|
|
399
|
+
`TUBELESS_STEP_OUTPUT_VALIDATION_FAILED`, and
|
|
400
|
+
`TUBELESS_FINAL_RESULT_VALIDATION_FAILED` to distinguish boundaries. A step-output
|
|
401
|
+
failure is reported on that step; final-result failure uses
|
|
402
|
+
`__finalize__`. Pipeline definitions cannot mix steps from different
|
|
403
|
+
options-schema factory scopes.
|
|
404
|
+
|
|
405
|
+
## Child pipelines
|
|
406
|
+
|
|
407
|
+
Use `step.fromPipeline` for one child run and `step.forEachPipeline` when runtime
|
|
408
|
+
items each need the same child. Children are opaque to the parent's plan and
|
|
409
|
+
hooks; their activity is summarized as progress on the parent step. See
|
|
410
|
+
[child-pipeline composition](./child-pipeline-composition.md) for propagation and
|
|
411
|
+
selection boundaries.
|
|
412
|
+
|
|
413
|
+
## Mermaid diagrams
|
|
414
|
+
|
|
415
|
+
Call `pipeline.toMermaid()` to generate a static flowchart without run options
|
|
416
|
+
or execution. Nodes use `name ?? id`; generated internal node identifiers keep
|
|
417
|
+
arbitrary user-facing text out of Mermaid syntax. Required dependencies render
|
|
418
|
+
as solid arrows, while optional inputs and failure gates render as labeled
|
|
419
|
+
dotted arrows. Use `direction` to change layout and `includeDescriptions` to add
|
|
420
|
+
operational descriptions to node labels.
|
|
421
|
+
|
|
422
|
+
## Module workbench
|
|
423
|
+
|
|
424
|
+
Command-by-command usage lives in [the CLI](./cli.md). The optional local UI is
|
|
425
|
+
documented in [the studio](./studio.md).
|
|
426
|
+
|
|
427
|
+
`tubeless inspect` and `tubeless graph` load a pipeline module without executing
|
|
428
|
+
steps or requiring domain options. `tubeless plan` accepts either a pipeline or a
|
|
429
|
+
marked `definePipelineCommand` and prefers the command when both are exported.
|
|
430
|
+
`tubeless run` deliberately accepts only a command created by
|
|
431
|
+
`definePipelineCommand`, because that export carries the application-owned
|
|
432
|
+
parser, validation, option mapping, reporter, and result summary needed for
|
|
433
|
+
safe execution. A raw pipeline is not executable through the workbench.
|
|
434
|
+
Inspect still requires a pipeline export.
|
|
435
|
+
|
|
436
|
+
`definePipelineCommand` defaults to a same-name mapping when its validated flag
|
|
437
|
+
values structurally satisfy the pipeline's domain options. Command-only values
|
|
438
|
+
such as `resume`, `step`, and `target` are removed before execution;
|
|
439
|
+
bridge-owned selection and failure controls are applied separately. If flags
|
|
440
|
+
do not satisfy required domain options, `mapOptions` remains required at
|
|
441
|
+
compile time. Keep explicit mapping for renamed fields, file loading, adaptive
|
|
442
|
+
defaults, prompts, or other derived values.
|
|
443
|
+
|
|
444
|
+
The returned command's `descriptor` is the non-terminal view of that contract:
|
|
445
|
+
name, description, and immutable parameter metadata for flags, types, defaults,
|
|
446
|
+
choices, numeric constraints, repeatability, paths, and positional support. It
|
|
447
|
+
is presentation-neutral; consumers still submit argv through `parse` or `run`
|
|
448
|
+
so one validation and execution path remains authoritative.
|
|
449
|
+
`command.plan()` always exposes structural planning from `dryRun`, `step`, and
|
|
450
|
+
`target` input only. It deliberately skips domain parsing, option mapping,
|
|
451
|
+
execution, and persistence. Use `command.plan()` or `tubeless plan`; do not
|
|
452
|
+
simulate planning with `--plan`.
|
|
453
|
+
|
|
454
|
+
Keep the workbench's module-selection arguments before the `--` boundary and
|
|
455
|
+
the command's application arguments after it:
|
|
456
|
+
|
|
457
|
+
```sh
|
|
458
|
+
tubeless run --export PublishCommand ./scripts/publish.ts -- --source input.json --target publish
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
The workbench forwards SIGINT through the command context and classifies exits
|
|
462
|
+
as validation, planning, execution, or cancellation without parsing diagnostic
|
|
463
|
+
messages.
|
|
464
|
+
|
|
465
|
+
## Runtime context
|
|
466
|
+
|
|
467
|
+
Use the step context instead of global facilities:
|
|
468
|
+
|
|
469
|
+
- `context.options` for validated domain options and `context.dryRun` for the active mode.
|
|
470
|
+
- `context.log` so interactive reporters can preserve output.
|
|
471
|
+
- `context.signal` for cooperative cancellation.
|
|
472
|
+
- `context.sleep` for cancellation-aware, testable delays.
|
|
473
|
+
- `context.reportProgress` and `context.reportAttempt` for observability.
|
|
474
|
+
- `context.cwd` for caller-controlled path resolution.
|
|
475
|
+
|
|
476
|
+
Callers may inject the logger, clock, sleep function, signal, hooks, and tracing.
|
|
477
|
+
This makes a pipeline embeddable and deterministic under test.
|
|
478
|
+
|
|
479
|
+
## Deterministic testing
|
|
480
|
+
|
|
481
|
+
`createPipelineTestRuntime` from `tubeless/testing` packages the ordinary
|
|
482
|
+
runtime injection points into a framework-neutral harness. Its default sleep
|
|
483
|
+
advances a monotonic clock immediately, its logger stays silent while capturing
|
|
484
|
+
calls, and its canonical status hook records lifecycle events and latest
|
|
485
|
+
progress. The harness owns an `AbortController` and delegates `run`,
|
|
486
|
+
`runOrThrow`, and `plan` to the real pipeline, preserving option and result
|
|
487
|
+
inference without implementing alternate execution semantics.
|
|
488
|
+
|
|
489
|
+
Create one runtime per test. Customize `sleep` when a test needs to pause,
|
|
490
|
+
interleave, or advance time differently; call `test.abort()` to exercise the
|
|
491
|
+
normal cancellation path. Assertions remain application- or framework-owned.
|
|
492
|
+
|
|
493
|
+
## Stable boundaries
|
|
494
|
+
|
|
495
|
+
- Keep step IDs stable, use `name` only for a friendlier display label, and keep
|
|
496
|
+
descriptions operationally useful.
|
|
497
|
+
- Generate documentation from `pipeline.toMermaid()` instead of hand-maintaining
|
|
498
|
+
a second copy of the graph.
|
|
499
|
+
- Return small domain results; inspect `run()` reports for execution metadata.
|
|
500
|
+
- Put remote API mechanics in retry/rate-limit helpers, not in the executor.
|
|
501
|
+
- Put CLI parsing at the script edge with `definePipelineCommand`.
|
|
502
|
+
- Keep application-owned telemetry SDKs outside the dependency-free core.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
Library consumers install the package and import it from Node.js 22 or later:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install tubeless
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The same package works with `pnpm add tubeless`, `yarn add tubeless`, and
|
|
12
|
+
`bun add tubeless`. The README quick start is a complete first program.
|
|
13
|
+
|
|
14
|
+
The `tubeless` CLI is separate: it requires Bun 1.3.14 or later.
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
bunx tubeless --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`npx tubeless` is not supported. Command-by-command usage is in
|
|
21
|
+
[the CLI](./cli.md).
|
|
22
|
+
|
|
23
|
+
## Runtime support
|
|
24
|
+
|
|
25
|
+
Library imports (`tubeless` and every subpath) are ESM-only and require
|
|
26
|
+
Node.js 22 or later. They are dependency-free compiled JavaScript.
|
|
27
|
+
|
|
28
|
+
The `tubeless` CLI requires Bun 1.3.14 or later. Its shebang is
|
|
29
|
+
`#!/usr/bin/env bun`, and Bun loads TypeScript pipeline modules directly.
|
|
30
|
+
`npx tubeless` is not a supported way to run the binary.
|
|
31
|
+
|
|
32
|
+
Supported operating systems are Linux and macOS. Windows is untested.
|
|
33
|
+
|
|
34
|
+
## 1. Define domain options
|
|
35
|
+
|
|
36
|
+
Describe only domain input. Built-in controls are added automatically to the
|
|
37
|
+
typed run object, while `plan(controls)` accepts controls alone.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
interface ImportOptions {
|
|
41
|
+
lines: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lines = [" First row ", "", "Second row"];
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 2. Create typed steps
|
|
48
|
+
|
|
49
|
+
Call `createSteps` once for the pipeline. Declare dependencies with step object
|
|
50
|
+
references; their output types become the dependent step's input types.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createSteps } from "tubeless";
|
|
54
|
+
|
|
55
|
+
const step = createSteps<ImportOptions>();
|
|
56
|
+
|
|
57
|
+
const load = step("load", {
|
|
58
|
+
description: "Read source rows",
|
|
59
|
+
run: (_inputs, context) => context.options.lines,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const normalize = step("normalize", {
|
|
63
|
+
dependsOn: [load],
|
|
64
|
+
description: "Normalize non-empty rows",
|
|
65
|
+
run: ({ load: rows }) => rows.map((row) => row.trim().toLowerCase()).filter(Boolean),
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
IDs should remain stable: plans, reports, hooks, tracing, and CLI selection all
|
|
70
|
+
use them.
|
|
71
|
+
|
|
72
|
+
## 3. Define the result
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { definePipeline, requireOutputs } from "tubeless";
|
|
76
|
+
|
|
77
|
+
const ImportPipeline = definePipeline({
|
|
78
|
+
id: "import",
|
|
79
|
+
steps: [load, normalize],
|
|
80
|
+
targets: [normalize],
|
|
81
|
+
finalize: requireOutputs([normalize], ({ normalize }) => ({
|
|
82
|
+
count: normalize.length,
|
|
83
|
+
rows: normalize,
|
|
84
|
+
})),
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`finalize` receives the outputs that completed. `requireOutputs` declares which
|
|
89
|
+
ones a valid result needs, makes them non-optional in the callback, and reports
|
|
90
|
+
a finalization error when a dry run, exact filter, or failure leaves one absent.
|
|
91
|
+
Use a plain finalizer when partial output is itself a valid result.
|
|
92
|
+
|
|
93
|
+
`targets` on the definition declares the pipeline's supported downstream goals.
|
|
94
|
+
Only those literal IDs are accepted by target selection and shown by pipeline
|
|
95
|
+
commands. When `requireOutputs` is used, definition construction rejects a
|
|
96
|
+
declared target whose dependency closure cannot produce the required result.
|
|
97
|
+
|
|
98
|
+
## 4. Visualize the static graph
|
|
99
|
+
|
|
100
|
+
`toMermaid` returns dependency-free Mermaid flowchart source without planning or
|
|
101
|
+
running the pipeline. It uses `name` when present and otherwise displays the
|
|
102
|
+
stable step ID.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const diagram = ImportPipeline.toMermaid({ direction: "LR" });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Required data dependencies use solid arrows. Optional inputs and failure gates
|
|
109
|
+
use labeled dotted arrows. Set `includeDescriptions: true` when the extra node
|
|
110
|
+
text is useful. The same source is available from `tubeless graph`; see
|
|
111
|
+
[the CLI](./cli.md).
|
|
112
|
+
|
|
113
|
+
## 5. Pick an execution method
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const value = await ImportPipeline.runOrThrow({ lines });
|
|
117
|
+
const report = await ImportPipeline.run({ lines, continueOnError: true });
|
|
118
|
+
const plan = ImportPipeline.plan({ dryRun: true });
|
|
119
|
+
const normalizePlan = ImportPipeline.plan({ targets: ["normalize"] });
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `runOrThrow` returns the finalized value only when the run succeeds and throws
|
|
123
|
+
`PipelineExecutionError` for every unsuccessful run. `continueOnError` may let
|
|
124
|
+
independent steps finish, but it never makes their failures successful.
|
|
125
|
+
- `run` always returns the structured pipeline result.
|
|
126
|
+
- `plan` describes executor selection without requiring or validating domain options.
|
|
127
|
+
- `targets` selects declared downstream goals plus their required inputs and
|
|
128
|
+
failure gates. `stepIds` is an exact low-level filter and cannot be combined
|
|
129
|
+
with `targets`. See [core concepts](./concepts.md) for selection provenance.
|
|
130
|
+
|
|
131
|
+
Library callers invoke the pipeline directly. Scripts should use
|
|
132
|
+
`definePipelineCommand` so help, `--target`, `--step`, dry run, and
|
|
133
|
+
cancellation stay generated instead of hand-parsed. See [the CLI](./cli.md)
|
|
134
|
+
and [`cli-job.ts`](../examples/cli-job.ts).
|
|
135
|
+
|
|
136
|
+
## 6. Test with deterministic runtime plumbing
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { createPipelineTestRuntime } from "tubeless/testing";
|
|
140
|
+
|
|
141
|
+
const test = createPipelineTestRuntime({ cwd: "/workspace" });
|
|
142
|
+
const result = await test.run(ImportPipeline, { lines });
|
|
143
|
+
|
|
144
|
+
test.clock.now();
|
|
145
|
+
test.logs;
|
|
146
|
+
test.statuses;
|
|
147
|
+
test.latestProgress;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The default sleep advances the test clock immediately. The harness also owns
|
|
151
|
+
an abort controller and provides typed `runOrThrow` and `plan` helpers. It has
|
|
152
|
+
no test-framework dependency; use ordinary assertions against its structured
|
|
153
|
+
captures and pipeline results.
|
|
154
|
+
|
|
155
|
+
## Next
|
|
156
|
+
|
|
157
|
+
Continue with the [recipe index](./recipes.md), or read
|
|
158
|
+
[core concepts](./concepts.md) before implementing failure-sensitive writes.
|
|
159
|
+
|
|
160
|
+
For untrusted boundary values, use the Standard Schema support in
|
|
161
|
+
[`validated-boundaries.ts`](../examples/validated-boundaries.ts). Persist and
|
|
162
|
+
inspect local runs with [the studio](./studio.md).
|