@sixb/pipeline-worker 0.0.1

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.
@@ -0,0 +1,98 @@
1
+ import type {
2
+ DatasetDefinition,
3
+ DatasetRow,
4
+ PipelineDefinition,
5
+ PipelineStepDefinition,
6
+ PipelineStepRunContext,
7
+ } from "@sixb/core"
8
+ import type { DatasetVersion } from "@sixb/core/lake-storage"
9
+ import { PipelineWorkerError, throwIfAborted } from "./errors"
10
+ import { createStepInputs, type ResolvedStepInput } from "./step-inputs"
11
+ import type { PipelineJob, PipelineLogSession, PipelineWorkerContext } from "./types"
12
+
13
+ export async function executeRunStep(input: {
14
+ readonly runtime: PipelineWorkerContext
15
+ readonly pipeline: PipelineDefinition
16
+ readonly step: PipelineStepDefinition
17
+ readonly job: PipelineJob
18
+ readonly signal: AbortSignal
19
+ readonly logSession: PipelineLogSession
20
+ readonly outputDataset: DatasetDefinition
21
+ readonly resolvedInputs: readonly ResolvedStepInput[]
22
+ }): Promise<{
23
+ readonly version: DatasetVersion
24
+ readonly versionCreated: boolean
25
+ readonly rowsWritten: number
26
+ }> {
27
+ const { runtime, pipeline, step, job, signal, outputDataset, resolvedInputs } = input
28
+
29
+ if (step.executor.kind !== "run") {
30
+ throw new PipelineWorkerError(
31
+ `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses SQL execution, which is not supported by the run step executor.`
32
+ )
33
+ }
34
+
35
+ const logger = input.logSession.withContext({ stepId: step.id })
36
+ let rowsWritten = 0
37
+ const write = await runtime.lakeStorage.beginWrite({
38
+ dataset: outputDataset,
39
+ mode: step.mode,
40
+ inputs: resolvedInputs.map((resolved) => resolved.ref),
41
+ producer: {
42
+ kind: "pipeline",
43
+ id: pipeline.id,
44
+ runId: job.id,
45
+ stepId: step.id,
46
+ },
47
+ })
48
+
49
+ try {
50
+ const context: PipelineStepRunContext = {
51
+ projectId: runtime.id,
52
+ pipelineId: pipeline.id,
53
+ stepId: step.id,
54
+ runId: job.id,
55
+ signal,
56
+ logger,
57
+ inputs: createStepInputs(runtime.lakeStorage, resolvedInputs),
58
+ output: {
59
+ async writeRows(rows) {
60
+ await write.writeRows(
61
+ countRowsWritten(rows, signal, () => {
62
+ rowsWritten += 1
63
+ })
64
+ )
65
+ },
66
+ },
67
+ }
68
+
69
+ await step.executor.handler(context)
70
+ throwIfAborted(signal)
71
+
72
+ const commit = await write.commit({
73
+ commitMessage: `pipeline ${pipeline.id} step ${step.id} run ${job.id}`,
74
+ })
75
+ const { outcome, ...version } = commit
76
+
77
+ return {
78
+ version,
79
+ versionCreated: outcome === "created",
80
+ rowsWritten,
81
+ }
82
+ } catch (error) {
83
+ await write.abort().catch(() => {})
84
+ throw error
85
+ }
86
+ }
87
+
88
+ async function* countRowsWritten(
89
+ rows: Iterable<DatasetRow> | AsyncIterable<DatasetRow>,
90
+ signal: AbortSignal,
91
+ onWritten: () => void
92
+ ): AsyncIterable<DatasetRow> {
93
+ for await (const row of rows) {
94
+ throwIfAborted(signal)
95
+ yield row
96
+ onWritten()
97
+ }
98
+ }
@@ -0,0 +1,115 @@
1
+ import type {
2
+ DatasetDefinition,
3
+ LakeStorage,
4
+ LakeStorageWithSql,
5
+ PipelineDefinition,
6
+ PipelineStepDefinition,
7
+ } from "@sixb/core"
8
+ import type {
9
+ DatasetVersion,
10
+ DatasetWriteMode,
11
+ LakeSqlTransformCapabilities,
12
+ } from "@sixb/core/lake-storage"
13
+ import { PipelineWorkerError, throwIfAborted } from "./errors"
14
+ import type { ResolvedStepInput } from "./step-inputs"
15
+ import type { PipelineJob, PipelineWorkerContext } from "./types"
16
+
17
+ function hasSqlExecutor(lakeStorage: LakeStorage): lakeStorage is LakeStorageWithSql {
18
+ const sql = "sql" in lakeStorage ? lakeStorage.sql : null
19
+
20
+ return (
21
+ typeof sql === "object" && sql !== null && "execute" in sql && typeof sql.execute === "function"
22
+ )
23
+ }
24
+
25
+ function assertSqlModeSupported(input: {
26
+ readonly capabilities: LakeSqlTransformCapabilities
27
+ readonly dialect: string
28
+ readonly mode: DatasetWriteMode
29
+ readonly pipelineId: string
30
+ readonly stepId: string
31
+ }): void {
32
+ const supported =
33
+ input.mode === "append"
34
+ ? input.capabilities.supportsAppend
35
+ : input.capabilities.supportsSnapshot
36
+ if (supported) return
37
+
38
+ throw new PipelineWorkerError(
39
+ `[SixbPipelineWorker] Pipeline '${input.pipelineId}' step '${input.stepId}' writes in ` +
40
+ `'${input.mode}' mode, which the ${input.dialect} SQL executor does not support.`
41
+ )
42
+ }
43
+
44
+ export async function executeSqlStep(input: {
45
+ readonly runtime: PipelineWorkerContext
46
+ readonly pipeline: PipelineDefinition
47
+ readonly step: PipelineStepDefinition
48
+ readonly job: PipelineJob
49
+ readonly signal: AbortSignal
50
+ readonly outputDataset: DatasetDefinition
51
+ readonly resolvedInputs: readonly ResolvedStepInput[]
52
+ }): Promise<{
53
+ readonly version: DatasetVersion
54
+ readonly versionCreated: boolean
55
+ readonly rowsWritten?: number
56
+ }> {
57
+ const { runtime, pipeline, step, job, signal, outputDataset, resolvedInputs } = input
58
+
59
+ if (step.executor.kind !== "sql") {
60
+ throw new PipelineWorkerError(
61
+ `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses run execution, which is not supported by the SQL step executor.`
62
+ )
63
+ }
64
+
65
+ if (!hasSqlExecutor(runtime.lakeStorage)) {
66
+ throw new PipelineWorkerError(
67
+ `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' requires SQL transform support, but lake storage does not provide lakeStorage.sql.execute(...).`
68
+ )
69
+ }
70
+
71
+ // `LakeSqlExecutor.capabilities` is a required field on the contract that nothing read,
72
+ // so a provider declaring `supportsAppend: false` was asked to be honest and then
73
+ // ignored — the unsupported mode failed somewhere inside the provider instead, in
74
+ // whatever words that provider happened to use.
75
+ assertSqlModeSupported({
76
+ capabilities: runtime.lakeStorage.sql.capabilities,
77
+ dialect: runtime.lakeStorage.sql.dialect,
78
+ mode: step.mode,
79
+ pipelineId: pipeline.id,
80
+ stepId: step.id,
81
+ })
82
+
83
+ throwIfAborted(signal)
84
+
85
+ const commit = await runtime.lakeStorage.sql.execute({
86
+ sources: Object.fromEntries(
87
+ resolvedInputs.map((input) => [
88
+ input.name,
89
+ {
90
+ dataset: input.dataset,
91
+ versionId: input.version.versionId,
92
+ },
93
+ ])
94
+ ),
95
+ sql: step.executor.sql,
96
+ target: outputDataset,
97
+ mode: step.mode,
98
+ producer: {
99
+ kind: "pipeline",
100
+ id: pipeline.id,
101
+ runId: job.id,
102
+ stepId: step.id,
103
+ },
104
+ commitMessage: `pipeline ${pipeline.id} step ${step.id} run ${job.id}`,
105
+ })
106
+
107
+ throwIfAborted(signal)
108
+ const { outcome, ...version } = commit
109
+
110
+ return {
111
+ version,
112
+ versionCreated: outcome === "created",
113
+ rowsWritten: version.rowCount,
114
+ }
115
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { PipelineWorkerSixb } from "./types"
2
+ export { PipelineWorker } from "./worker"
@@ -0,0 +1,130 @@
1
+ import { resolveLogsRuntime } from "@sixb/core/internal/logging"
2
+ import type { DatasetVersion } from "@sixb/core/lake-storage"
3
+ import type { PipelineRunRecord } from "@sixb/core/storage"
4
+ import {
5
+ createPipelineBookkeepingError,
6
+ requireFinishedAt,
7
+ requirePipeline,
8
+ statusForFailure,
9
+ throwIfAborted,
10
+ toPipelineRunFailure,
11
+ } from "./errors"
12
+ import { runStep } from "./run-step"
13
+ import type { PipelineRunResult, PipelineStepRunResult, RunPipelineJobInput } from "./types"
14
+
15
+ export class PipelineRunAlreadyStartedError extends Error {
16
+ override readonly name = "PipelineRunAlreadyStartedError"
17
+
18
+ constructor(readonly run: PipelineRunRecord) {
19
+ super(`[SixbPipelineWorker] Pipeline run '${run.id}' has already started.`)
20
+ }
21
+ }
22
+
23
+ export async function runPipelineJob(input: RunPipelineJobInput): Promise<PipelineRunResult> {
24
+ const { runtime, job } = input
25
+ const signal = input.signal ?? new AbortController().signal
26
+ const pipeline = requirePipeline(runtime.getPipelineById(job.pipelineId), job)
27
+ const logSession = resolveLogsRuntime(runtime.id, runtime.logs).startExecution({
28
+ kind: "pipeline",
29
+ id: job.id,
30
+ })
31
+ const steps: PipelineStepRunResult[] = []
32
+ let startedRun: PipelineRunRecord | undefined
33
+ let finalVersion: DatasetVersion | undefined
34
+ let finished = false
35
+
36
+ throwIfAborted(signal)
37
+
38
+ try {
39
+ try {
40
+ startedRun = await runtime.pipelineRunsStorage.start({
41
+ projectId: runtime.id,
42
+ id: job.id,
43
+ pipelineId: pipeline.id,
44
+ })
45
+ } catch (error) {
46
+ const existing = await runtime.pipelineRunsStorage.getById({
47
+ projectId: runtime.id,
48
+ id: job.id,
49
+ })
50
+ if (existing?.pipelineId === pipeline.id) {
51
+ throw new PipelineRunAlreadyStartedError(existing)
52
+ }
53
+ throw error
54
+ }
55
+ await input.onRunStarted?.(startedRun)
56
+
57
+ for (const [stepIndex, node] of pipeline.graph.nodes.entries()) {
58
+ const stepResult = await runStep({
59
+ runtime,
60
+ pipeline,
61
+ step: node.step,
62
+ stepIndex,
63
+ job,
64
+ signal,
65
+ logSession,
66
+ onStepStarted: input.onStepStarted,
67
+ onStepFinished: input.onStepFinished,
68
+ })
69
+
70
+ steps.push(stepResult)
71
+ finalVersion = stepResult.version
72
+ // This is the durability boundary for per-step output events.
73
+ await input.onStepCommitted?.(stepResult)
74
+ }
75
+
76
+ let run: PipelineRunRecord
77
+ try {
78
+ run = await runtime.pipelineRunsStorage.finish({
79
+ projectId: runtime.id,
80
+ id: job.id,
81
+ status: "succeeded",
82
+ output: finalVersion
83
+ ? {
84
+ datasetId: finalVersion.datasetId,
85
+ versionId: finalVersion.versionId,
86
+ }
87
+ : undefined,
88
+ })
89
+ } catch (error) {
90
+ if (finalVersion) {
91
+ throw createPipelineBookkeepingError({
92
+ pipelineId: pipeline.id,
93
+ runId: job.id,
94
+ version: finalVersion,
95
+ cause: error,
96
+ })
97
+ }
98
+ throw error
99
+ }
100
+ finished = true
101
+
102
+ return {
103
+ run: {
104
+ ...run,
105
+ finishedAt: requireFinishedAt(job.id, run.finishedAt),
106
+ },
107
+ steps,
108
+ version: finalVersion,
109
+ }
110
+ } catch (error) {
111
+ if (startedRun && !finished) {
112
+ const status = statusForFailure(signal, error)
113
+ try {
114
+ const run = await runtime.pipelineRunsStorage.finish({
115
+ projectId: runtime.id,
116
+ id: job.id,
117
+ status,
118
+ error: toPipelineRunFailure(error),
119
+ })
120
+ if (status === "failed" && run.status === "failed") input.onRunFailed?.(error, run)
121
+ } catch {
122
+ // The run did not transition to the requested terminal status.
123
+ }
124
+ }
125
+
126
+ throw error
127
+ } finally {
128
+ await logSession.flush()
129
+ }
130
+ }
@@ -0,0 +1,154 @@
1
+ import type { DatasetDefinition, PipelineDefinition, PipelineStepDefinition } from "@sixb/core"
2
+ import type { DatasetVersion } from "@sixb/core/lake-storage"
3
+ import type { PipelineStepRunRecord } from "@sixb/core/storage"
4
+ import {
5
+ createStepBookkeepingError,
6
+ requireRegisteredDataset,
7
+ statusForFailure,
8
+ throwIfAborted,
9
+ toPipelineRunFailure,
10
+ } from "./errors"
11
+ import { executeRunStep } from "./execute-run-step"
12
+ import { executeSqlStep } from "./execute-sql-step"
13
+ import { type ResolvedStepInput, resolveStepInputs } from "./step-inputs"
14
+ import type {
15
+ PipelineJob,
16
+ PipelineLogSession,
17
+ PipelineStepFinishedHandler,
18
+ PipelineStepRunResult,
19
+ PipelineStepStartedHandler,
20
+ PipelineWorkerContext,
21
+ } from "./types"
22
+
23
+ type StepExecutionResult = {
24
+ readonly version: DatasetVersion
25
+ readonly versionCreated: boolean
26
+ readonly rowsWritten?: number
27
+ }
28
+
29
+ export async function runStep(input: {
30
+ readonly runtime: PipelineWorkerContext
31
+ readonly pipeline: PipelineDefinition
32
+ readonly step: PipelineStepDefinition
33
+ readonly stepIndex: number
34
+ readonly job: PipelineJob
35
+ readonly signal: AbortSignal
36
+ readonly logSession: PipelineLogSession
37
+ readonly onStepStarted?: PipelineStepStartedHandler
38
+ readonly onStepFinished?: PipelineStepFinishedHandler
39
+ }): Promise<PipelineStepRunResult> {
40
+ const { runtime, pipeline, step, stepIndex, job, signal } = input
41
+ const lifecycleContext = {
42
+ stepIndex,
43
+ totalSteps: pipeline.graph.nodes.length,
44
+ }
45
+
46
+ throwIfAborted(signal)
47
+ const resolvedInputs = await resolveStepInputs({ runtime, pipeline, step })
48
+ const inputRefs = resolvedInputs.map((resolved) => resolved.ref)
49
+ const outputDataset = requireRegisteredDataset({
50
+ dataset: runtime.getDatasetById(step.output.id),
51
+ pipelineId: pipeline.id,
52
+ stepId: step.id,
53
+ role: "output",
54
+ datasetId: step.output.id,
55
+ })
56
+
57
+ await runtime.lakeStorage.createDataset(outputDataset)
58
+
59
+ const stepRunId = `${job.id}:step:${stepIndex + 1}:${step.id}`
60
+ const startedStepRun = await runtime.pipelineRunsStorage.startStep({
61
+ projectId: runtime.id,
62
+ id: stepRunId,
63
+ pipelineRunId: job.id,
64
+ pipelineId: pipeline.id,
65
+ stepId: step.id,
66
+ datasetId: outputDataset.id,
67
+ mode: step.mode,
68
+ inputs: inputRefs,
69
+ })
70
+ await input.onStepStarted?.(startedStepRun, lifecycleContext)
71
+
72
+ let committedVersion: DatasetVersion | undefined
73
+ let rowsWritten: number | undefined
74
+
75
+ try {
76
+ const execution = await executeStep({
77
+ runtime,
78
+ pipeline,
79
+ step,
80
+ job,
81
+ signal,
82
+ logSession: input.logSession,
83
+ outputDataset,
84
+ resolvedInputs,
85
+ })
86
+ committedVersion = execution.version
87
+ rowsWritten = execution.rowsWritten
88
+
89
+ let stepRun: PipelineStepRunRecord
90
+ try {
91
+ stepRun = await runtime.pipelineRunsStorage.finishStep({
92
+ projectId: runtime.id,
93
+ id: stepRunId,
94
+ status: "succeeded",
95
+ output: {
96
+ datasetId: committedVersion.datasetId,
97
+ versionId: committedVersion.versionId,
98
+ },
99
+ rowsWritten,
100
+ })
101
+ } catch (error) {
102
+ throw createStepBookkeepingError({
103
+ pipelineId: pipeline.id,
104
+ stepId: step.id,
105
+ runId: stepRunId,
106
+ version: committedVersion,
107
+ cause: error,
108
+ })
109
+ }
110
+ await input.onStepFinished?.(stepRun, lifecycleContext)
111
+
112
+ return {
113
+ run: stepRun,
114
+ version: committedVersion,
115
+ versionCreated: execution.versionCreated,
116
+ }
117
+ } catch (error) {
118
+ if (!committedVersion) {
119
+ const failedStepRun = await runtime.pipelineRunsStorage
120
+ .finishStep({
121
+ projectId: runtime.id,
122
+ id: stepRunId,
123
+ status: statusForFailure(signal, error),
124
+ rowsWritten,
125
+ error: toPipelineRunFailure(error),
126
+ })
127
+ .catch(() => null)
128
+
129
+ if (failedStepRun) {
130
+ await input.onStepFinished?.(failedStepRun, lifecycleContext)
131
+ }
132
+ }
133
+
134
+ throw error
135
+ }
136
+ }
137
+
138
+ async function executeStep(input: {
139
+ readonly runtime: PipelineWorkerContext
140
+ readonly pipeline: PipelineDefinition
141
+ readonly step: PipelineStepDefinition
142
+ readonly job: PipelineJob
143
+ readonly signal: AbortSignal
144
+ readonly logSession: PipelineLogSession
145
+ readonly outputDataset: DatasetDefinition
146
+ readonly resolvedInputs: readonly ResolvedStepInput[]
147
+ }): Promise<StepExecutionResult> {
148
+ switch (input.step.executor.kind) {
149
+ case "run":
150
+ return executeRunStep(input)
151
+ case "sql":
152
+ return executeSqlStep(input)
153
+ }
154
+ }
@@ -0,0 +1,84 @@
1
+ import type {
2
+ DatasetDefinition,
3
+ PipelineDefinition,
4
+ PipelineStepDefinition,
5
+ PipelineStepInput,
6
+ PipelineStepRunContext,
7
+ } from "@sixb/core"
8
+ import type {
9
+ DatasetVersion,
10
+ DatasetVersionRef,
11
+ ReadDatasetRowsInput,
12
+ } from "@sixb/core/lake-storage"
13
+ import { PipelineWorkerError, requireRegisteredDataset } from "./errors"
14
+ import type { PipelineWorkerContext } from "./types"
15
+
16
+ export interface ResolvedStepInput {
17
+ readonly name: string
18
+ readonly dataset: DatasetDefinition
19
+ readonly version: DatasetVersion
20
+ readonly ref: DatasetVersionRef
21
+ }
22
+
23
+ export async function resolveStepInputs(input: {
24
+ readonly runtime: PipelineWorkerContext
25
+ readonly pipeline: PipelineDefinition
26
+ readonly step: PipelineStepDefinition
27
+ }): Promise<readonly ResolvedStepInput[]> {
28
+ const { runtime, pipeline, step } = input
29
+ const resolved: ResolvedStepInput[] = []
30
+
31
+ for (const [name, declaredDataset] of Object.entries(step.inputs)) {
32
+ const dataset = requireRegisteredDataset({
33
+ dataset: runtime.getDatasetById(declaredDataset.id),
34
+ pipelineId: pipeline.id,
35
+ stepId: step.id,
36
+ role: "input",
37
+ name,
38
+ datasetId: declaredDataset.id,
39
+ })
40
+
41
+ const version = await runtime.lakeStorage.getLatestVersion(dataset.id)
42
+ if (!version) {
43
+ throw new PipelineWorkerError(
44
+ `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' input '${name}' dataset '${dataset.id}' has no committed version.`
45
+ )
46
+ }
47
+
48
+ resolved.push({
49
+ name,
50
+ dataset,
51
+ version,
52
+ ref: {
53
+ datasetId: version.datasetId,
54
+ versionId: version.versionId,
55
+ },
56
+ })
57
+ }
58
+
59
+ return resolved
60
+ }
61
+
62
+ export function createStepInputs(
63
+ lakeStorage: PipelineWorkerContext["lakeStorage"],
64
+ resolved: readonly ResolvedStepInput[]
65
+ ): PipelineStepRunContext["inputs"] {
66
+ const inputs: Record<string, PipelineStepInput> = {}
67
+
68
+ for (const input of resolved) {
69
+ inputs[input.name] = {
70
+ dataset: input.dataset,
71
+ version: input.version,
72
+ readRows(readInput: Omit<ReadDatasetRowsInput, "datasetId" | "versionId"> = {}) {
73
+ // Step readers stay pinned even if newer versions commit while the handler runs.
74
+ return lakeStorage.readRows({
75
+ ...readInput,
76
+ datasetId: input.dataset.id,
77
+ versionId: input.version.versionId,
78
+ })
79
+ },
80
+ }
81
+ }
82
+
83
+ return inputs
84
+ }
package/src/types.ts ADDED
@@ -0,0 +1,88 @@
1
+ import type {
2
+ DatasetDefinition,
3
+ DomainEventLog,
4
+ LakeStorage,
5
+ PipelineDefinition,
6
+ Queues,
7
+ Storage,
8
+ } from "@sixb/core"
9
+ import type { LogsRuntime } from "@sixb/core/internal/logging"
10
+ import type { DatasetVersion } from "@sixb/core/lake-storage"
11
+ import type {
12
+ PipelineRunRecord,
13
+ PipelineRunStorage,
14
+ PipelineStepRunRecord,
15
+ } from "@sixb/core/storage"
16
+
17
+ export type PipelineLogSession = ReturnType<LogsRuntime["startExecution"]>
18
+
19
+ export interface PipelineWorkerContext {
20
+ readonly id: string
21
+ readonly pipelineRunsStorage: PipelineRunStorage
22
+ readonly lakeStorage: LakeStorage
23
+ readonly logs?: LogsRuntime
24
+ getDatasetById(datasetId: string): DatasetDefinition | null
25
+ getPipelineById(pipelineId: string): PipelineDefinition | null
26
+ }
27
+
28
+ export interface PipelineWorkerSixb {
29
+ readonly id: string
30
+ readonly events?: DomainEventLog
31
+ readonly logs?: LogsRuntime
32
+ readonly lakeStorage: LakeStorage
33
+ readonly queues: Queues
34
+ readonly storage: Storage
35
+ listPipelines(): readonly PipelineDefinition[]
36
+ getPipelineById(pipelineId: string): PipelineDefinition | null
37
+ getDatasetById(datasetId: string): DatasetDefinition | null
38
+ }
39
+
40
+ export interface PipelineJob {
41
+ readonly id: string
42
+ readonly pipelineId: string
43
+ }
44
+
45
+ export interface RunPipelineJobInput {
46
+ readonly runtime: PipelineWorkerContext
47
+ readonly job: PipelineJob
48
+ readonly signal?: AbortSignal
49
+ readonly onRunStarted?: PipelineRunStartedHandler
50
+ readonly onRunFailed?: PipelineRunFailedHandler
51
+ readonly onStepStarted?: PipelineStepStartedHandler
52
+ readonly onStepFinished?: PipelineStepFinishedHandler
53
+ readonly onStepCommitted?: PipelineStepCommittedHandler
54
+ }
55
+
56
+ export type PipelineStepCommittedHandler = (result: PipelineStepRunResult) => Promise<void> | void
57
+
58
+ export type PipelineRunStartedHandler = (run: PipelineRunRecord) => Promise<void> | void
59
+
60
+ export type PipelineRunFailedHandler = (error: unknown, run: PipelineRunRecord) => void
61
+
62
+ export interface PipelineStepLifecycleContext {
63
+ readonly stepIndex: number
64
+ readonly totalSteps: number
65
+ }
66
+
67
+ export type PipelineStepStartedHandler = (
68
+ step: PipelineStepRunRecord,
69
+ context: PipelineStepLifecycleContext
70
+ ) => Promise<void> | void
71
+
72
+ export type PipelineStepFinishedHandler = (
73
+ step: PipelineStepRunRecord,
74
+ context: PipelineStepLifecycleContext
75
+ ) => Promise<void> | void
76
+
77
+ export interface PipelineStepRunResult {
78
+ readonly run: PipelineStepRunRecord
79
+ readonly version: DatasetVersion
80
+ /** True only when this step created the returned dataset version. */
81
+ readonly versionCreated: boolean
82
+ }
83
+
84
+ export interface PipelineRunResult {
85
+ readonly run: PipelineRunRecord
86
+ readonly steps: readonly PipelineStepRunResult[]
87
+ readonly version?: DatasetVersion
88
+ }