@sixb/pipeline-worker 0.1.0 → 0.1.2
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/dist/errors.d.ts +17 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/events.d.ts +5 -11
- package/dist/events.d.ts.map +1 -1
- package/dist/execute-run-step.d.ts.map +1 -1
- package/dist/execute-sql-step.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +293 -139
- package/dist/index.js.map +11 -11
- package/dist/run-pipeline-job.d.ts +1 -1
- package/dist/run-pipeline-job.d.ts.map +1 -1
- package/dist/run-step.d.ts.map +1 -1
- package/dist/step-inputs.d.ts +1 -0
- package/dist/step-inputs.d.ts.map +1 -1
- package/dist/types.d.ts +16 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +7 -6
- package/dist/worker.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/errors.ts +85 -32
- package/src/events.ts +34 -28
- package/src/execute-run-step.ts +13 -3
- package/src/execute-sql-step.ts +39 -7
- package/src/index.ts +1 -1
- package/src/run-pipeline-job.ts +60 -17
- package/src/run-step.ts +59 -15
- package/src/step-inputs.ts +17 -5
- package/src/types.ts +22 -14
- package/src/worker.ts +99 -73
package/src/errors.ts
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
import type { DatasetDefinition, PipelineDefinition } from "@sixb/core"
|
|
2
|
+
import { createSixbError, isSixbError, summarizeErrorMessage } from "@sixb/core/internal/errors"
|
|
2
3
|
import type { DatasetVersion } from "@sixb/core/lake-storage"
|
|
3
|
-
import type {
|
|
4
|
+
import type { PipelineRunStatus } from "@sixb/core/storage"
|
|
4
5
|
import type { PipelineJob } from "./types"
|
|
5
6
|
|
|
6
|
-
export class PipelineWorkerError extends Error {
|
|
7
|
-
readonly name = "PipelineWorkerError"
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function toPipelineRunFailure(error: unknown): PipelineRunFailure {
|
|
11
|
-
if (error instanceof Error) {
|
|
12
|
-
return {
|
|
13
|
-
name: error.name,
|
|
14
|
-
message: error.message,
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
message: String(error),
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
7
|
export function throwIfAborted(signal: AbortSignal): void {
|
|
24
8
|
if (signal.aborted) {
|
|
25
9
|
throw createAbortError()
|
|
@@ -33,39 +17,94 @@ export function statusForFailure(
|
|
|
33
17
|
return signal.aborted || isAbortError(error) ? "cancelled" : "failed"
|
|
34
18
|
}
|
|
35
19
|
|
|
36
|
-
export function requireFinishedAt(
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
export function requireFinishedAt(input: {
|
|
21
|
+
readonly pipelineId: string
|
|
22
|
+
readonly runId: string
|
|
23
|
+
readonly finishedAt: Date | undefined
|
|
24
|
+
}): Date {
|
|
25
|
+
if (input.finishedAt) {
|
|
26
|
+
return input.finishedAt
|
|
39
27
|
}
|
|
40
28
|
|
|
41
|
-
throw
|
|
42
|
-
|
|
29
|
+
throw createSixbError(
|
|
30
|
+
"internal.unexpected",
|
|
31
|
+
`[SixbPipelineWorker] Pipeline run '${input.runId}' finished without a finishedAt timestamp.`,
|
|
32
|
+
{ details: { pipelineId: input.pipelineId, runId: input.runId } }
|
|
43
33
|
)
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
export function createStepBookkeepingError(options: {
|
|
47
37
|
readonly pipelineId: string
|
|
38
|
+
readonly pipelineRunId: string
|
|
48
39
|
readonly stepId: string
|
|
49
|
-
readonly
|
|
40
|
+
readonly stepRunId: string
|
|
50
41
|
readonly version: DatasetVersion
|
|
51
42
|
readonly cause: unknown
|
|
52
43
|
}): Error {
|
|
53
44
|
// The dataset version is already durable; retrying the whole pipeline could duplicate appends.
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
{
|
|
45
|
+
return createSixbError(
|
|
46
|
+
"internal.unexpected",
|
|
47
|
+
`[SixbPipelineWorker] Pipeline '${options.pipelineId}' step '${options.stepId}' committed dataset version '${options.version.versionId}', but failed to finalize step run '${options.stepRunId}'. The dataset commit may already have succeeded and the step run record may need repair.`,
|
|
48
|
+
{
|
|
49
|
+
cause: options.cause,
|
|
50
|
+
details: {
|
|
51
|
+
pipelineId: options.pipelineId,
|
|
52
|
+
pipelineRunId: options.pipelineRunId,
|
|
53
|
+
stepId: options.stepId,
|
|
54
|
+
stepRunId: options.stepRunId,
|
|
55
|
+
datasetId: options.version.datasetId,
|
|
56
|
+
versionId: options.version.versionId,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
57
59
|
)
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
export function createPipelineStepFailure(options: {
|
|
63
|
+
readonly pipelineId: string
|
|
64
|
+
readonly pipelineRunId: string
|
|
65
|
+
readonly stepId: string
|
|
66
|
+
readonly stepRunId?: string
|
|
67
|
+
readonly cause: unknown
|
|
68
|
+
}) {
|
|
69
|
+
return createSixbError(
|
|
70
|
+
"pipeline.step_failed",
|
|
71
|
+
summarizeErrorMessage(options.cause, "Pipeline step execution failed."),
|
|
72
|
+
{
|
|
73
|
+
cause: options.cause,
|
|
74
|
+
details: {
|
|
75
|
+
pipelineId: options.pipelineId,
|
|
76
|
+
pipelineRunId: options.pipelineRunId,
|
|
77
|
+
stepId: options.stepId,
|
|
78
|
+
...(options.stepRunId ? { stepRunId: options.stepRunId } : {}),
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function unwrapPipelineStepFailure(error: unknown): unknown {
|
|
85
|
+
return isSixbError(error) && error.code === "pipeline.step_failed" && error.cause !== undefined
|
|
86
|
+
? error.cause
|
|
87
|
+
: error
|
|
88
|
+
}
|
|
89
|
+
|
|
60
90
|
export function createPipelineBookkeepingError(options: {
|
|
61
91
|
readonly pipelineId: string
|
|
62
92
|
readonly runId: string
|
|
63
93
|
readonly version: DatasetVersion
|
|
64
94
|
readonly cause: unknown
|
|
65
95
|
}): Error {
|
|
66
|
-
return
|
|
96
|
+
return createSixbError(
|
|
97
|
+
"internal.unexpected",
|
|
67
98
|
`[SixbPipelineWorker] Pipeline '${options.pipelineId}' committed final dataset version '${options.version.versionId}', but failed to finalize pipeline run '${options.runId}'. The dataset commit may already have succeeded and the pipeline run record may need repair.`,
|
|
68
|
-
{
|
|
99
|
+
{
|
|
100
|
+
cause: options.cause,
|
|
101
|
+
details: {
|
|
102
|
+
pipelineId: options.pipelineId,
|
|
103
|
+
runId: options.runId,
|
|
104
|
+
datasetId: options.version.datasetId,
|
|
105
|
+
versionId: options.version.versionId,
|
|
106
|
+
},
|
|
107
|
+
}
|
|
69
108
|
)
|
|
70
109
|
}
|
|
71
110
|
|
|
@@ -74,7 +113,11 @@ export function requirePipeline(
|
|
|
74
113
|
job: PipelineJob
|
|
75
114
|
): PipelineDefinition {
|
|
76
115
|
if (!pipeline) {
|
|
77
|
-
throw
|
|
116
|
+
throw createSixbError(
|
|
117
|
+
"internal.unexpected",
|
|
118
|
+
`[SixbPipelineWorker] Unknown pipeline '${job.pipelineId}'.`,
|
|
119
|
+
{ details: { pipelineId: job.pipelineId, runId: job.id } }
|
|
120
|
+
)
|
|
78
121
|
}
|
|
79
122
|
|
|
80
123
|
return pipeline
|
|
@@ -83,6 +126,7 @@ export function requirePipeline(
|
|
|
83
126
|
export function requireRegisteredDataset(options: {
|
|
84
127
|
readonly dataset: DatasetDefinition | null
|
|
85
128
|
readonly pipelineId: string
|
|
129
|
+
readonly pipelineRunId: string
|
|
86
130
|
readonly stepId: string
|
|
87
131
|
readonly role: "input" | "output"
|
|
88
132
|
readonly name?: string
|
|
@@ -94,8 +138,17 @@ export function requireRegisteredDataset(options: {
|
|
|
94
138
|
|
|
95
139
|
const namedRole = options.role === "input" ? `input '${options.name ?? "unknown"}'` : "output"
|
|
96
140
|
|
|
97
|
-
throw
|
|
98
|
-
|
|
141
|
+
throw createSixbError(
|
|
142
|
+
"internal.unexpected",
|
|
143
|
+
`[SixbPipelineWorker] Pipeline '${options.pipelineId}' step '${options.stepId}' ${namedRole} references unknown dataset '${options.datasetId}'.`,
|
|
144
|
+
{
|
|
145
|
+
details: {
|
|
146
|
+
pipelineId: options.pipelineId,
|
|
147
|
+
pipelineRunId: options.pipelineRunId,
|
|
148
|
+
stepId: options.stepId,
|
|
149
|
+
datasetId: options.datasetId,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
99
152
|
)
|
|
100
153
|
}
|
|
101
154
|
|
package/src/events.ts
CHANGED
|
@@ -10,8 +10,12 @@ const SOURCE = "SixbPipelineWorker"
|
|
|
10
10
|
|
|
11
11
|
export async function emitPipelineRunStarted(
|
|
12
12
|
events: DomainEventLog | undefined,
|
|
13
|
-
run: Pick<PipelineRunRecord, "id" | "pipelineId" | "startedAt"
|
|
13
|
+
run: Pick<PipelineRunRecord, "id" | "pipelineId" | "startedAt">,
|
|
14
|
+
correlationId: string
|
|
14
15
|
): Promise<void> {
|
|
16
|
+
if (!run.startedAt) {
|
|
17
|
+
throw new Error(`[SixbPipelineWorker] Pipeline run '${run.id}' started without a timestamp.`)
|
|
18
|
+
}
|
|
15
19
|
await events?.emit(
|
|
16
20
|
{
|
|
17
21
|
events: [
|
|
@@ -24,6 +28,7 @@ export async function emitPipelineRunStarted(
|
|
|
24
28
|
},
|
|
25
29
|
},
|
|
26
30
|
],
|
|
31
|
+
correlationId,
|
|
27
32
|
},
|
|
28
33
|
{ source: SOURCE }
|
|
29
34
|
)
|
|
@@ -35,7 +40,8 @@ export async function emitPipelineRunStepStarted(
|
|
|
35
40
|
PipelineStepRunRecord,
|
|
36
41
|
"id" | "pipelineRunId" | "pipelineId" | "stepId" | "datasetId" | "startedAt"
|
|
37
42
|
>,
|
|
38
|
-
context: PipelineStepLifecycleContext
|
|
43
|
+
context: PipelineStepLifecycleContext,
|
|
44
|
+
correlationId: string
|
|
39
45
|
): Promise<void> {
|
|
40
46
|
await events?.emit(
|
|
41
47
|
{
|
|
@@ -54,6 +60,7 @@ export async function emitPipelineRunStepStarted(
|
|
|
54
60
|
},
|
|
55
61
|
},
|
|
56
62
|
],
|
|
63
|
+
correlationId,
|
|
57
64
|
},
|
|
58
65
|
{ source: SOURCE }
|
|
59
66
|
)
|
|
@@ -74,7 +81,8 @@ export async function emitPipelineRunStepFinished(
|
|
|
74
81
|
| "rowsWritten"
|
|
75
82
|
| "error"
|
|
76
83
|
>,
|
|
77
|
-
context: PipelineStepLifecycleContext
|
|
84
|
+
context: PipelineStepLifecycleContext,
|
|
85
|
+
correlationId: string
|
|
78
86
|
): Promise<void> {
|
|
79
87
|
await events?.emit(
|
|
80
88
|
{
|
|
@@ -93,10 +101,11 @@ export async function emitPipelineRunStepFinished(
|
|
|
93
101
|
finishedAt: requireFinishedAt(step.id, step.finishedAt).toISOString(),
|
|
94
102
|
...(step.output ? { versionId: step.output.versionId } : {}),
|
|
95
103
|
...(step.rowsWritten !== undefined ? { rowsWritten: step.rowsWritten } : {}),
|
|
96
|
-
...(step.error ? { error: step.error
|
|
104
|
+
...(step.error ? { error: step.error } : {}),
|
|
97
105
|
},
|
|
98
106
|
},
|
|
99
107
|
],
|
|
108
|
+
correlationId,
|
|
100
109
|
},
|
|
101
110
|
{ source: SOURCE }
|
|
102
111
|
)
|
|
@@ -105,7 +114,8 @@ export async function emitPipelineRunStepFinished(
|
|
|
105
114
|
export async function emitDatasetVersionCommitted(
|
|
106
115
|
events: DomainEventLog | undefined,
|
|
107
116
|
job: PipelineJob,
|
|
108
|
-
step: PipelineStepRunResult
|
|
117
|
+
step: PipelineStepRunResult,
|
|
118
|
+
correlationId: string
|
|
109
119
|
): Promise<void> {
|
|
110
120
|
if (!step.versionCreated) return
|
|
111
121
|
|
|
@@ -128,6 +138,7 @@ export async function emitDatasetVersionCommitted(
|
|
|
128
138
|
},
|
|
129
139
|
},
|
|
130
140
|
],
|
|
141
|
+
correlationId,
|
|
131
142
|
},
|
|
132
143
|
{ source: SOURCE }
|
|
133
144
|
)
|
|
@@ -135,37 +146,32 @@ export async function emitDatasetVersionCommitted(
|
|
|
135
146
|
|
|
136
147
|
export async function emitPipelineRunFinished(
|
|
137
148
|
events: DomainEventLog | undefined,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
readonly pipelineId: string
|
|
141
|
-
readonly status: "succeeded" | "failed" | "cancelled"
|
|
142
|
-
readonly datasetId?: string
|
|
143
|
-
readonly versionId?: string
|
|
144
|
-
}
|
|
149
|
+
run: Pick<PipelineRunRecord, "id" | "pipelineId" | "status" | "output" | "error">,
|
|
150
|
+
correlationId: string
|
|
145
151
|
): Promise<void> {
|
|
146
152
|
await events?.emit(
|
|
147
|
-
{
|
|
153
|
+
{
|
|
154
|
+
events: [{ type: "pipeline.run.finished", payload: buildPipelineRunFinishedPayload(run) }],
|
|
155
|
+
correlationId,
|
|
156
|
+
},
|
|
148
157
|
{ source: SOURCE }
|
|
149
158
|
)
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
function buildPipelineRunFinishedPayload(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
readonly status: "succeeded" | "failed" | "cancelled"
|
|
156
|
-
readonly datasetId?: string
|
|
157
|
-
readonly versionId?: string
|
|
158
|
-
}) {
|
|
161
|
+
function buildPipelineRunFinishedPayload(
|
|
162
|
+
run: Pick<PipelineRunRecord, "id" | "pipelineId" | "status" | "output" | "error">
|
|
163
|
+
) {
|
|
159
164
|
return {
|
|
160
|
-
pipelineId:
|
|
161
|
-
runId:
|
|
162
|
-
status:
|
|
163
|
-
...(
|
|
165
|
+
pipelineId: run.pipelineId,
|
|
166
|
+
runId: run.id,
|
|
167
|
+
status: requireTerminalStatus(run.status, `Pipeline run '${run.id}'`),
|
|
168
|
+
...(run.output
|
|
164
169
|
? {
|
|
165
|
-
datasetId:
|
|
166
|
-
versionId:
|
|
170
|
+
datasetId: run.output.datasetId,
|
|
171
|
+
versionId: run.output.versionId,
|
|
167
172
|
}
|
|
168
173
|
: {}),
|
|
174
|
+
...(run.error ? { error: run.error } : {}),
|
|
169
175
|
}
|
|
170
176
|
}
|
|
171
177
|
|
|
@@ -180,8 +186,8 @@ function requireFinishedAt(stepRunId: string, finishedAt: Date | undefined): Dat
|
|
|
180
186
|
function requireTerminalStatus(
|
|
181
187
|
status: PipelineRunStatus,
|
|
182
188
|
context: string
|
|
183
|
-
): Exclude<PipelineRunStatus, "running"> {
|
|
184
|
-
if (status === "running") {
|
|
189
|
+
): Exclude<PipelineRunStatus, "queued" | "running"> {
|
|
190
|
+
if (status === "queued" || status === "running") {
|
|
185
191
|
throw new Error(`[SixbPipelineWorker] ${context} is still running.`)
|
|
186
192
|
}
|
|
187
193
|
|
package/src/execute-run-step.ts
CHANGED
|
@@ -5,8 +5,9 @@ import type {
|
|
|
5
5
|
PipelineStepDefinition,
|
|
6
6
|
PipelineStepRunContext,
|
|
7
7
|
} from "@sixb/core"
|
|
8
|
+
import { createSixbError } from "@sixb/core/internal/errors"
|
|
8
9
|
import type { DatasetVersion } from "@sixb/core/lake-storage"
|
|
9
|
-
import {
|
|
10
|
+
import { throwIfAborted } from "./errors"
|
|
10
11
|
import { createStepInputs, type ResolvedStepInput } from "./step-inputs"
|
|
11
12
|
import type { PipelineJob, PipelineLogSession, PipelineWorkerContext } from "./types"
|
|
12
13
|
|
|
@@ -27,8 +28,17 @@ export async function executeRunStep(input: {
|
|
|
27
28
|
const { runtime, pipeline, step, job, signal, outputDataset, resolvedInputs } = input
|
|
28
29
|
|
|
29
30
|
if (step.executor.kind !== "run") {
|
|
30
|
-
throw
|
|
31
|
-
|
|
31
|
+
throw createSixbError(
|
|
32
|
+
"internal.unexpected",
|
|
33
|
+
`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses SQL execution, which is not supported by the run step executor.`,
|
|
34
|
+
{
|
|
35
|
+
details: {
|
|
36
|
+
pipelineId: pipeline.id,
|
|
37
|
+
pipelineRunId: job.id,
|
|
38
|
+
stepId: step.id,
|
|
39
|
+
datasetId: outputDataset.id,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
32
42
|
)
|
|
33
43
|
}
|
|
34
44
|
|
package/src/execute-sql-step.ts
CHANGED
|
@@ -5,12 +5,13 @@ import type {
|
|
|
5
5
|
PipelineDefinition,
|
|
6
6
|
PipelineStepDefinition,
|
|
7
7
|
} from "@sixb/core"
|
|
8
|
+
import { createSixbError } from "@sixb/core/internal/errors"
|
|
8
9
|
import type {
|
|
9
10
|
DatasetVersion,
|
|
10
11
|
DatasetWriteMode,
|
|
11
12
|
LakeSqlTransformCapabilities,
|
|
12
13
|
} from "@sixb/core/lake-storage"
|
|
13
|
-
import {
|
|
14
|
+
import { throwIfAborted } from "./errors"
|
|
14
15
|
import type { ResolvedStepInput } from "./step-inputs"
|
|
15
16
|
import type { PipelineJob, PipelineWorkerContext } from "./types"
|
|
16
17
|
|
|
@@ -27,7 +28,9 @@ function assertSqlModeSupported(input: {
|
|
|
27
28
|
readonly dialect: string
|
|
28
29
|
readonly mode: DatasetWriteMode
|
|
29
30
|
readonly pipelineId: string
|
|
31
|
+
readonly pipelineRunId: string
|
|
30
32
|
readonly stepId: string
|
|
33
|
+
readonly datasetId: string
|
|
31
34
|
}): void {
|
|
32
35
|
const supported =
|
|
33
36
|
input.mode === "append"
|
|
@@ -35,9 +38,18 @@ function assertSqlModeSupported(input: {
|
|
|
35
38
|
: input.capabilities.supportsSnapshot
|
|
36
39
|
if (supported) return
|
|
37
40
|
|
|
38
|
-
throw
|
|
41
|
+
throw createSixbError(
|
|
42
|
+
"internal.unexpected",
|
|
39
43
|
`[SixbPipelineWorker] Pipeline '${input.pipelineId}' step '${input.stepId}' writes in ` +
|
|
40
|
-
`'${input.mode}' mode, which the ${input.dialect} SQL executor does not support
|
|
44
|
+
`'${input.mode}' mode, which the ${input.dialect} SQL executor does not support.`,
|
|
45
|
+
{
|
|
46
|
+
details: {
|
|
47
|
+
pipelineId: input.pipelineId,
|
|
48
|
+
pipelineRunId: input.pipelineRunId,
|
|
49
|
+
stepId: input.stepId,
|
|
50
|
+
datasetId: input.datasetId,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
41
53
|
)
|
|
42
54
|
}
|
|
43
55
|
|
|
@@ -57,14 +69,32 @@ export async function executeSqlStep(input: {
|
|
|
57
69
|
const { runtime, pipeline, step, job, signal, outputDataset, resolvedInputs } = input
|
|
58
70
|
|
|
59
71
|
if (step.executor.kind !== "sql") {
|
|
60
|
-
throw
|
|
61
|
-
|
|
72
|
+
throw createSixbError(
|
|
73
|
+
"internal.unexpected",
|
|
74
|
+
`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses run execution, which is not supported by the SQL step executor.`,
|
|
75
|
+
{
|
|
76
|
+
details: {
|
|
77
|
+
pipelineId: pipeline.id,
|
|
78
|
+
pipelineRunId: job.id,
|
|
79
|
+
stepId: step.id,
|
|
80
|
+
datasetId: outputDataset.id,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
62
83
|
)
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
if (!hasSqlExecutor(runtime.lakeStorage)) {
|
|
66
|
-
throw
|
|
67
|
-
|
|
87
|
+
throw createSixbError(
|
|
88
|
+
"internal.unexpected",
|
|
89
|
+
`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' requires SQL transform support, but lake storage does not provide lakeStorage.sql.execute(...).`,
|
|
90
|
+
{
|
|
91
|
+
details: {
|
|
92
|
+
pipelineId: pipeline.id,
|
|
93
|
+
pipelineRunId: job.id,
|
|
94
|
+
stepId: step.id,
|
|
95
|
+
datasetId: outputDataset.id,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
68
98
|
)
|
|
69
99
|
}
|
|
70
100
|
|
|
@@ -77,7 +107,9 @@ export async function executeSqlStep(input: {
|
|
|
77
107
|
dialect: runtime.lakeStorage.sql.dialect,
|
|
78
108
|
mode: step.mode,
|
|
79
109
|
pipelineId: pipeline.id,
|
|
110
|
+
pipelineRunId: job.id,
|
|
80
111
|
stepId: step.id,
|
|
112
|
+
datasetId: outputDataset.id,
|
|
81
113
|
})
|
|
82
114
|
|
|
83
115
|
throwIfAborted(signal)
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { PipelineWorkerHost } from "./types"
|
|
2
2
|
export { PipelineWorker } from "./worker"
|
package/src/run-pipeline-job.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { captureSixbFailure } from "@sixb/core/internal/errors"
|
|
2
|
+
import { resolveLoggingService } from "@sixb/core/internal/logging"
|
|
2
3
|
import type { DatasetVersion } from "@sixb/core/lake-storage"
|
|
3
|
-
import type
|
|
4
|
+
import { PIPELINE_RUN_FAILURE_CODES, type PipelineRunRecord } from "@sixb/core/storage"
|
|
4
5
|
import {
|
|
5
6
|
createPipelineBookkeepingError,
|
|
6
7
|
requireFinishedAt,
|
|
7
8
|
requirePipeline,
|
|
8
9
|
statusForFailure,
|
|
9
10
|
throwIfAborted,
|
|
10
|
-
|
|
11
|
+
unwrapPipelineStepFailure,
|
|
11
12
|
} from "./errors"
|
|
12
13
|
import { runStep } from "./run-step"
|
|
13
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
PipelineRunFinishedHandler,
|
|
16
|
+
PipelineRunResult,
|
|
17
|
+
PipelineStepRunResult,
|
|
18
|
+
RunPipelineJobInput,
|
|
19
|
+
} from "./types"
|
|
14
20
|
|
|
15
21
|
export class PipelineRunAlreadyStartedError extends Error {
|
|
16
22
|
override readonly name = "PipelineRunAlreadyStartedError"
|
|
@@ -21,10 +27,15 @@ export class PipelineRunAlreadyStartedError extends Error {
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export async function runPipelineJob(input: RunPipelineJobInput): Promise<PipelineRunResult> {
|
|
24
|
-
const { runtime,
|
|
30
|
+
const { runtime, run: durableRun } = input
|
|
31
|
+
const job = { id: durableRun.id, pipelineId: durableRun.pipelineId }
|
|
25
32
|
const signal = input.signal ?? new AbortController().signal
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
if (durableRun.projectId !== runtime.id) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`[SixbPipelineWorker] Pipeline run '${durableRun.id}' belongs to project '${durableRun.projectId}', not '${runtime.id}'.`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
const logSession = resolveLoggingService(runtime.id, runtime.logging).startExecution({
|
|
28
39
|
kind: "pipeline",
|
|
29
40
|
id: job.id,
|
|
30
41
|
})
|
|
@@ -40,20 +51,24 @@ export async function runPipelineJob(input: RunPipelineJobInput): Promise<Pipeli
|
|
|
40
51
|
startedRun = await runtime.pipelineRunsStorage.start({
|
|
41
52
|
projectId: runtime.id,
|
|
42
53
|
id: job.id,
|
|
43
|
-
pipelineId: pipeline.id,
|
|
44
54
|
})
|
|
45
55
|
} catch (error) {
|
|
46
56
|
const existing = await runtime.pipelineRunsStorage.getById({
|
|
47
57
|
projectId: runtime.id,
|
|
48
58
|
id: job.id,
|
|
49
59
|
})
|
|
50
|
-
if (
|
|
60
|
+
if (
|
|
61
|
+
existing?.pipelineId === durableRun.pipelineId &&
|
|
62
|
+
existing.executionId === durableRun.executionId
|
|
63
|
+
) {
|
|
51
64
|
throw new PipelineRunAlreadyStartedError(existing)
|
|
52
65
|
}
|
|
53
66
|
throw error
|
|
54
67
|
}
|
|
55
68
|
await input.onRunStarted?.(startedRun)
|
|
56
69
|
|
|
70
|
+
const pipeline = requirePipeline(runtime.pipelines.getById(job.pipelineId), job)
|
|
71
|
+
|
|
57
72
|
for (const [stepIndex, node] of pipeline.graph.nodes.entries()) {
|
|
58
73
|
const stepResult = await runStep({
|
|
59
74
|
runtime,
|
|
@@ -97,34 +112,62 @@ export async function runPipelineJob(input: RunPipelineJobInput): Promise<Pipeli
|
|
|
97
112
|
}
|
|
98
113
|
throw error
|
|
99
114
|
}
|
|
100
|
-
|
|
115
|
+
const finishedRun = {
|
|
116
|
+
...run,
|
|
117
|
+
finishedAt: requireFinishedAt({
|
|
118
|
+
pipelineId: pipeline.id,
|
|
119
|
+
runId: job.id,
|
|
120
|
+
finishedAt: run.finishedAt,
|
|
121
|
+
}),
|
|
122
|
+
}
|
|
101
123
|
|
|
124
|
+
finished = true
|
|
125
|
+
await notifyRunFinished(input.onRunFinished, finishedRun)
|
|
102
126
|
return {
|
|
103
|
-
run:
|
|
104
|
-
...run,
|
|
105
|
-
finishedAt: requireFinishedAt(job.id, run.finishedAt),
|
|
106
|
-
},
|
|
127
|
+
run: finishedRun,
|
|
107
128
|
steps,
|
|
108
129
|
version: finalVersion,
|
|
109
130
|
}
|
|
110
131
|
} catch (error) {
|
|
132
|
+
const reportedError = unwrapPipelineStepFailure(error)
|
|
111
133
|
if (startedRun && !finished) {
|
|
112
134
|
const status = statusForFailure(signal, error)
|
|
113
135
|
try {
|
|
136
|
+
const failure = captureSixbFailure(error, {
|
|
137
|
+
allowedCodes: PIPELINE_RUN_FAILURE_CODES,
|
|
138
|
+
defaultCode: status === "cancelled" ? "runtime.cancelled" : "internal.unexpected",
|
|
139
|
+
details: { pipelineId: durableRun.pipelineId, runId: job.id },
|
|
140
|
+
})
|
|
114
141
|
const run = await runtime.pipelineRunsStorage.finish({
|
|
115
142
|
projectId: runtime.id,
|
|
116
143
|
id: job.id,
|
|
117
144
|
status,
|
|
118
|
-
error:
|
|
145
|
+
error: failure,
|
|
119
146
|
})
|
|
120
|
-
if (status === "failed" && run.status === "failed")
|
|
147
|
+
if (status === "failed" && run.status === "failed") {
|
|
148
|
+
input.onRunFailed?.(reportedError, run, failure)
|
|
149
|
+
}
|
|
150
|
+
await notifyRunFinished(input.onRunFinished, run)
|
|
121
151
|
} catch {
|
|
122
152
|
// The run did not transition to the requested terminal status.
|
|
123
153
|
}
|
|
124
154
|
}
|
|
125
155
|
|
|
126
|
-
throw
|
|
156
|
+
throw reportedError
|
|
127
157
|
} finally {
|
|
128
158
|
await logSession.flush()
|
|
129
159
|
}
|
|
130
160
|
}
|
|
161
|
+
|
|
162
|
+
async function notifyRunFinished(
|
|
163
|
+
handler: PipelineRunFinishedHandler | undefined,
|
|
164
|
+
run: PipelineRunRecord
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
try {
|
|
167
|
+
await handler?.(run)
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// The built-in event log reports lost batches itself. Anything reaching here is a broken
|
|
170
|
+
// invariant in a custom lifecycle handler and must not change an already durable outcome.
|
|
171
|
+
console.error("[SixbPipelineWorker] Pipeline run lifecycle handler failed:", error)
|
|
172
|
+
}
|
|
173
|
+
}
|