@sixb/pipeline-worker 0.1.1 → 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 +3 -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/dist/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/worker.ts
|
|
3
3
|
import { reportRunFailure } from "@sixb/core/internal/error-reporting";
|
|
4
|
+
import { bindDurablePrimitiveExecution } from "@sixb/core/internal/primitive-execution";
|
|
4
5
|
import { QueueWorker } from "@sixb/core/internal/workers";
|
|
6
|
+
import { PIPELINE_RUN_FAILURE_CODES as PIPELINE_RUN_FAILURE_CODES3 } from "@sixb/core/storage";
|
|
5
7
|
|
|
6
8
|
// src/events.ts
|
|
7
9
|
var SOURCE = "SixbPipelineWorker";
|
|
8
|
-
async function emitPipelineRunStarted(events, run) {
|
|
10
|
+
async function emitPipelineRunStarted(events, run, correlationId) {
|
|
11
|
+
if (!run.startedAt) {
|
|
12
|
+
throw new Error(`[SixbPipelineWorker] Pipeline run '${run.id}' started without a timestamp.`);
|
|
13
|
+
}
|
|
9
14
|
await events?.emit({
|
|
10
15
|
events: [
|
|
11
16
|
{
|
|
@@ -16,10 +21,11 @@ async function emitPipelineRunStarted(events, run) {
|
|
|
16
21
|
startedAt: run.startedAt.toISOString()
|
|
17
22
|
}
|
|
18
23
|
}
|
|
19
|
-
]
|
|
24
|
+
],
|
|
25
|
+
correlationId
|
|
20
26
|
}, { source: SOURCE });
|
|
21
27
|
}
|
|
22
|
-
async function emitPipelineRunStepStarted(events, step, context) {
|
|
28
|
+
async function emitPipelineRunStepStarted(events, step, context, correlationId) {
|
|
23
29
|
await events?.emit({
|
|
24
30
|
events: [
|
|
25
31
|
{
|
|
@@ -35,10 +41,11 @@ async function emitPipelineRunStepStarted(events, step, context) {
|
|
|
35
41
|
startedAt: step.startedAt.toISOString()
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
|
-
]
|
|
44
|
+
],
|
|
45
|
+
correlationId
|
|
39
46
|
}, { source: SOURCE });
|
|
40
47
|
}
|
|
41
|
-
async function emitPipelineRunStepFinished(events, step, context) {
|
|
48
|
+
async function emitPipelineRunStepFinished(events, step, context, correlationId) {
|
|
42
49
|
await events?.emit({
|
|
43
50
|
events: [
|
|
44
51
|
{
|
|
@@ -55,13 +62,14 @@ async function emitPipelineRunStepFinished(events, step, context) {
|
|
|
55
62
|
finishedAt: requireFinishedAt(step.id, step.finishedAt).toISOString(),
|
|
56
63
|
...step.output ? { versionId: step.output.versionId } : {},
|
|
57
64
|
...step.rowsWritten !== undefined ? { rowsWritten: step.rowsWritten } : {},
|
|
58
|
-
...step.error ? { error: step.error
|
|
65
|
+
...step.error ? { error: step.error } : {}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
|
-
]
|
|
68
|
+
],
|
|
69
|
+
correlationId
|
|
62
70
|
}, { source: SOURCE });
|
|
63
71
|
}
|
|
64
|
-
async function emitDatasetVersionCommitted(events, job, step) {
|
|
72
|
+
async function emitDatasetVersionCommitted(events, job, step, correlationId) {
|
|
65
73
|
if (!step.versionCreated)
|
|
66
74
|
return;
|
|
67
75
|
await events?.emit({
|
|
@@ -80,21 +88,26 @@ async function emitDatasetVersionCommitted(events, job, step) {
|
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
|
-
]
|
|
91
|
+
],
|
|
92
|
+
correlationId
|
|
84
93
|
}, { source: SOURCE });
|
|
85
94
|
}
|
|
86
|
-
async function emitPipelineRunFinished(events,
|
|
87
|
-
await events?.emit({
|
|
95
|
+
async function emitPipelineRunFinished(events, run, correlationId) {
|
|
96
|
+
await events?.emit({
|
|
97
|
+
events: [{ type: "pipeline.run.finished", payload: buildPipelineRunFinishedPayload(run) }],
|
|
98
|
+
correlationId
|
|
99
|
+
}, { source: SOURCE });
|
|
88
100
|
}
|
|
89
|
-
function buildPipelineRunFinishedPayload(
|
|
101
|
+
function buildPipelineRunFinishedPayload(run) {
|
|
90
102
|
return {
|
|
91
|
-
pipelineId:
|
|
92
|
-
runId:
|
|
93
|
-
status:
|
|
94
|
-
...
|
|
95
|
-
datasetId:
|
|
96
|
-
versionId:
|
|
97
|
-
} : {}
|
|
103
|
+
pipelineId: run.pipelineId,
|
|
104
|
+
runId: run.id,
|
|
105
|
+
status: requireTerminalStatus(run.status, `Pipeline run '${run.id}'`),
|
|
106
|
+
...run.output ? {
|
|
107
|
+
datasetId: run.output.datasetId,
|
|
108
|
+
versionId: run.output.versionId
|
|
109
|
+
} : {},
|
|
110
|
+
...run.error ? { error: run.error } : {}
|
|
98
111
|
};
|
|
99
112
|
}
|
|
100
113
|
function requireFinishedAt(stepRunId, finishedAt) {
|
|
@@ -104,30 +117,19 @@ function requireFinishedAt(stepRunId, finishedAt) {
|
|
|
104
117
|
throw new Error(`[SixbPipelineWorker] Pipeline step run '${stepRunId}' has no finishedAt.`);
|
|
105
118
|
}
|
|
106
119
|
function requireTerminalStatus(status, context) {
|
|
107
|
-
if (status === "running") {
|
|
120
|
+
if (status === "queued" || status === "running") {
|
|
108
121
|
throw new Error(`[SixbPipelineWorker] ${context} is still running.`);
|
|
109
122
|
}
|
|
110
123
|
return status;
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
// src/run-pipeline-job.ts
|
|
114
|
-
import {
|
|
127
|
+
import { captureSixbFailure as captureSixbFailure2 } from "@sixb/core/internal/errors";
|
|
128
|
+
import { resolveLoggingService } from "@sixb/core/internal/logging";
|
|
129
|
+
import { PIPELINE_RUN_FAILURE_CODES as PIPELINE_RUN_FAILURE_CODES2 } from "@sixb/core/storage";
|
|
115
130
|
|
|
116
131
|
// src/errors.ts
|
|
117
|
-
|
|
118
|
-
name = "PipelineWorkerError";
|
|
119
|
-
}
|
|
120
|
-
function toPipelineRunFailure(error) {
|
|
121
|
-
if (error instanceof Error) {
|
|
122
|
-
return {
|
|
123
|
-
name: error.name,
|
|
124
|
-
message: error.message
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
message: String(error)
|
|
129
|
-
};
|
|
130
|
-
}
|
|
132
|
+
import { createSixbError, isSixbError, summarizeErrorMessage } from "@sixb/core/internal/errors";
|
|
131
133
|
function throwIfAborted(signal) {
|
|
132
134
|
if (signal.aborted) {
|
|
133
135
|
throw createAbortError();
|
|
@@ -136,21 +138,53 @@ function throwIfAborted(signal) {
|
|
|
136
138
|
function statusForFailure(signal, error) {
|
|
137
139
|
return signal.aborted || isAbortError(error) ? "cancelled" : "failed";
|
|
138
140
|
}
|
|
139
|
-
function requireFinishedAt2(
|
|
140
|
-
if (finishedAt) {
|
|
141
|
-
return finishedAt;
|
|
141
|
+
function requireFinishedAt2(input) {
|
|
142
|
+
if (input.finishedAt) {
|
|
143
|
+
return input.finishedAt;
|
|
142
144
|
}
|
|
143
|
-
throw
|
|
145
|
+
throw createSixbError("internal.unexpected", `[SixbPipelineWorker] Pipeline run '${input.runId}' finished without a finishedAt timestamp.`, { details: { pipelineId: input.pipelineId, runId: input.runId } });
|
|
144
146
|
}
|
|
145
147
|
function createStepBookkeepingError(options) {
|
|
146
|
-
return
|
|
148
|
+
return createSixbError("internal.unexpected", `[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.`, {
|
|
149
|
+
cause: options.cause,
|
|
150
|
+
details: {
|
|
151
|
+
pipelineId: options.pipelineId,
|
|
152
|
+
pipelineRunId: options.pipelineRunId,
|
|
153
|
+
stepId: options.stepId,
|
|
154
|
+
stepRunId: options.stepRunId,
|
|
155
|
+
datasetId: options.version.datasetId,
|
|
156
|
+
versionId: options.version.versionId
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function createPipelineStepFailure(options) {
|
|
161
|
+
return createSixbError("pipeline.step_failed", summarizeErrorMessage(options.cause, "Pipeline step execution failed."), {
|
|
162
|
+
cause: options.cause,
|
|
163
|
+
details: {
|
|
164
|
+
pipelineId: options.pipelineId,
|
|
165
|
+
pipelineRunId: options.pipelineRunId,
|
|
166
|
+
stepId: options.stepId,
|
|
167
|
+
...options.stepRunId ? { stepRunId: options.stepRunId } : {}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function unwrapPipelineStepFailure(error) {
|
|
172
|
+
return isSixbError(error) && error.code === "pipeline.step_failed" && error.cause !== undefined ? error.cause : error;
|
|
147
173
|
}
|
|
148
174
|
function createPipelineBookkeepingError(options) {
|
|
149
|
-
return
|
|
175
|
+
return createSixbError("internal.unexpected", `[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.`, {
|
|
176
|
+
cause: options.cause,
|
|
177
|
+
details: {
|
|
178
|
+
pipelineId: options.pipelineId,
|
|
179
|
+
runId: options.runId,
|
|
180
|
+
datasetId: options.version.datasetId,
|
|
181
|
+
versionId: options.version.versionId
|
|
182
|
+
}
|
|
183
|
+
});
|
|
150
184
|
}
|
|
151
185
|
function requirePipeline(pipeline, job) {
|
|
152
186
|
if (!pipeline) {
|
|
153
|
-
throw
|
|
187
|
+
throw createSixbError("internal.unexpected", `[SixbPipelineWorker] Unknown pipeline '${job.pipelineId}'.`, { details: { pipelineId: job.pipelineId, runId: job.id } });
|
|
154
188
|
}
|
|
155
189
|
return pipeline;
|
|
156
190
|
}
|
|
@@ -159,7 +193,14 @@ function requireRegisteredDataset(options) {
|
|
|
159
193
|
return options.dataset;
|
|
160
194
|
}
|
|
161
195
|
const namedRole = options.role === "input" ? `input '${options.name ?? "unknown"}'` : "output";
|
|
162
|
-
throw
|
|
196
|
+
throw createSixbError("internal.unexpected", `[SixbPipelineWorker] Pipeline '${options.pipelineId}' step '${options.stepId}' ${namedRole} references unknown dataset '${options.datasetId}'.`, {
|
|
197
|
+
details: {
|
|
198
|
+
pipelineId: options.pipelineId,
|
|
199
|
+
pipelineRunId: options.pipelineRunId,
|
|
200
|
+
stepId: options.stepId,
|
|
201
|
+
datasetId: options.datasetId
|
|
202
|
+
}
|
|
203
|
+
});
|
|
163
204
|
}
|
|
164
205
|
function createAbortError() {
|
|
165
206
|
const error = new Error("Worker runtime aborted.");
|
|
@@ -170,14 +211,23 @@ function isAbortError(error) {
|
|
|
170
211
|
return error instanceof Error && error.name === "AbortError";
|
|
171
212
|
}
|
|
172
213
|
|
|
214
|
+
// src/run-step.ts
|
|
215
|
+
import { captureSixbFailure } from "@sixb/core/internal/errors";
|
|
216
|
+
import { PIPELINE_RUN_FAILURE_CODES } from "@sixb/core/storage";
|
|
217
|
+
|
|
218
|
+
// src/execute-run-step.ts
|
|
219
|
+
import { createSixbError as createSixbError3 } from "@sixb/core/internal/errors";
|
|
220
|
+
|
|
173
221
|
// src/step-inputs.ts
|
|
222
|
+
import { createSixbError as createSixbError2 } from "@sixb/core/internal/errors";
|
|
174
223
|
async function resolveStepInputs(input) {
|
|
175
|
-
const { runtime, pipeline, step } = input;
|
|
224
|
+
const { runtime, pipeline, step, pipelineRunId } = input;
|
|
176
225
|
const resolved = [];
|
|
177
226
|
for (const [name, declaredDataset] of Object.entries(step.inputs)) {
|
|
178
227
|
const dataset = requireRegisteredDataset({
|
|
179
|
-
dataset: runtime.
|
|
228
|
+
dataset: runtime.datasets.getById(declaredDataset.id),
|
|
180
229
|
pipelineId: pipeline.id,
|
|
230
|
+
pipelineRunId,
|
|
181
231
|
stepId: step.id,
|
|
182
232
|
role: "input",
|
|
183
233
|
name,
|
|
@@ -185,7 +235,14 @@ async function resolveStepInputs(input) {
|
|
|
185
235
|
});
|
|
186
236
|
const version = await runtime.lakeStorage.getLatestVersion(dataset.id);
|
|
187
237
|
if (!version) {
|
|
188
|
-
throw
|
|
238
|
+
throw createSixbError2("internal.unexpected", `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' input '${name}' dataset '${dataset.id}' has no committed version.`, {
|
|
239
|
+
details: {
|
|
240
|
+
pipelineId: pipeline.id,
|
|
241
|
+
pipelineRunId,
|
|
242
|
+
stepId: step.id,
|
|
243
|
+
datasetId: dataset.id
|
|
244
|
+
}
|
|
245
|
+
});
|
|
189
246
|
}
|
|
190
247
|
resolved.push({
|
|
191
248
|
name,
|
|
@@ -221,7 +278,14 @@ function createStepInputs(lakeStorage, resolved) {
|
|
|
221
278
|
async function executeRunStep(input) {
|
|
222
279
|
const { runtime, pipeline, step, job, signal, outputDataset, resolvedInputs } = input;
|
|
223
280
|
if (step.executor.kind !== "run") {
|
|
224
|
-
throw
|
|
281
|
+
throw createSixbError3("internal.unexpected", `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses SQL execution, which is not supported by the run step executor.`, {
|
|
282
|
+
details: {
|
|
283
|
+
pipelineId: pipeline.id,
|
|
284
|
+
pipelineRunId: job.id,
|
|
285
|
+
stepId: step.id,
|
|
286
|
+
datasetId: outputDataset.id
|
|
287
|
+
}
|
|
288
|
+
});
|
|
225
289
|
}
|
|
226
290
|
const logger = input.logSession.withContext({ stepId: step.id });
|
|
227
291
|
let rowsWritten = 0;
|
|
@@ -278,6 +342,7 @@ async function* countRowsWritten(rows, signal, onWritten) {
|
|
|
278
342
|
}
|
|
279
343
|
|
|
280
344
|
// src/execute-sql-step.ts
|
|
345
|
+
import { createSixbError as createSixbError4 } from "@sixb/core/internal/errors";
|
|
281
346
|
function hasSqlExecutor(lakeStorage) {
|
|
282
347
|
const sql = "sql" in lakeStorage ? lakeStorage.sql : null;
|
|
283
348
|
return typeof sql === "object" && sql !== null && "execute" in sql && typeof sql.execute === "function";
|
|
@@ -286,22 +351,45 @@ function assertSqlModeSupported(input) {
|
|
|
286
351
|
const supported = input.mode === "append" ? input.capabilities.supportsAppend : input.capabilities.supportsSnapshot;
|
|
287
352
|
if (supported)
|
|
288
353
|
return;
|
|
289
|
-
throw
|
|
354
|
+
throw createSixbError4("internal.unexpected", `[SixbPipelineWorker] Pipeline '${input.pipelineId}' step '${input.stepId}' writes in ` + `'${input.mode}' mode, which the ${input.dialect} SQL executor does not support.`, {
|
|
355
|
+
details: {
|
|
356
|
+
pipelineId: input.pipelineId,
|
|
357
|
+
pipelineRunId: input.pipelineRunId,
|
|
358
|
+
stepId: input.stepId,
|
|
359
|
+
datasetId: input.datasetId
|
|
360
|
+
}
|
|
361
|
+
});
|
|
290
362
|
}
|
|
291
363
|
async function executeSqlStep(input) {
|
|
292
364
|
const { runtime, pipeline, step, job, signal, outputDataset, resolvedInputs } = input;
|
|
293
365
|
if (step.executor.kind !== "sql") {
|
|
294
|
-
throw
|
|
366
|
+
throw createSixbError4("internal.unexpected", `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses run execution, which is not supported by the SQL step executor.`, {
|
|
367
|
+
details: {
|
|
368
|
+
pipelineId: pipeline.id,
|
|
369
|
+
pipelineRunId: job.id,
|
|
370
|
+
stepId: step.id,
|
|
371
|
+
datasetId: outputDataset.id
|
|
372
|
+
}
|
|
373
|
+
});
|
|
295
374
|
}
|
|
296
375
|
if (!hasSqlExecutor(runtime.lakeStorage)) {
|
|
297
|
-
throw
|
|
376
|
+
throw createSixbError4("internal.unexpected", `[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' requires SQL transform support, but lake storage does not provide lakeStorage.sql.execute(...).`, {
|
|
377
|
+
details: {
|
|
378
|
+
pipelineId: pipeline.id,
|
|
379
|
+
pipelineRunId: job.id,
|
|
380
|
+
stepId: step.id,
|
|
381
|
+
datasetId: outputDataset.id
|
|
382
|
+
}
|
|
383
|
+
});
|
|
298
384
|
}
|
|
299
385
|
assertSqlModeSupported({
|
|
300
386
|
capabilities: runtime.lakeStorage.sql.capabilities,
|
|
301
387
|
dialect: runtime.lakeStorage.sql.dialect,
|
|
302
388
|
mode: step.mode,
|
|
303
389
|
pipelineId: pipeline.id,
|
|
304
|
-
|
|
390
|
+
pipelineRunId: job.id,
|
|
391
|
+
stepId: step.id,
|
|
392
|
+
datasetId: outputDataset.id
|
|
305
393
|
});
|
|
306
394
|
throwIfAborted(signal);
|
|
307
395
|
const commit = await runtime.lakeStorage.sql.execute({
|
|
@@ -340,16 +428,36 @@ async function runStep(input) {
|
|
|
340
428
|
totalSteps: pipeline.graph.nodes.length
|
|
341
429
|
};
|
|
342
430
|
throwIfAborted(signal);
|
|
343
|
-
|
|
431
|
+
let resolvedInputs;
|
|
432
|
+
let outputDataset;
|
|
433
|
+
try {
|
|
434
|
+
resolvedInputs = await resolveStepInputs({
|
|
435
|
+
runtime,
|
|
436
|
+
pipeline,
|
|
437
|
+
step,
|
|
438
|
+
pipelineRunId: job.id
|
|
439
|
+
});
|
|
440
|
+
outputDataset = requireRegisteredDataset({
|
|
441
|
+
dataset: runtime.datasets.getById(step.output.id),
|
|
442
|
+
pipelineId: pipeline.id,
|
|
443
|
+
pipelineRunId: job.id,
|
|
444
|
+
stepId: step.id,
|
|
445
|
+
role: "output",
|
|
446
|
+
datasetId: step.output.id
|
|
447
|
+
});
|
|
448
|
+
await runtime.lakeStorage.createDataset(outputDataset);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (statusForFailure(signal, error) === "failed") {
|
|
451
|
+
throw createPipelineStepFailure({
|
|
452
|
+
pipelineId: pipeline.id,
|
|
453
|
+
pipelineRunId: job.id,
|
|
454
|
+
stepId: step.id,
|
|
455
|
+
cause: error
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
344
460
|
const inputRefs = resolvedInputs.map((resolved) => resolved.ref);
|
|
345
|
-
const outputDataset = requireRegisteredDataset({
|
|
346
|
-
dataset: runtime.getDatasetById(step.output.id),
|
|
347
|
-
pipelineId: pipeline.id,
|
|
348
|
-
stepId: step.id,
|
|
349
|
-
role: "output",
|
|
350
|
-
datasetId: step.output.id
|
|
351
|
-
});
|
|
352
|
-
await runtime.lakeStorage.createDataset(outputDataset);
|
|
353
461
|
const stepRunId = `${job.id}:step:${stepIndex + 1}:${step.id}`;
|
|
354
462
|
const startedStepRun = await runtime.pipelineRunsStorage.startStep({
|
|
355
463
|
projectId: runtime.id,
|
|
@@ -392,8 +500,9 @@ async function runStep(input) {
|
|
|
392
500
|
} catch (error) {
|
|
393
501
|
throw createStepBookkeepingError({
|
|
394
502
|
pipelineId: pipeline.id,
|
|
503
|
+
pipelineRunId: job.id,
|
|
395
504
|
stepId: step.id,
|
|
396
|
-
|
|
505
|
+
stepRunId,
|
|
397
506
|
version: committedVersion,
|
|
398
507
|
cause: error
|
|
399
508
|
});
|
|
@@ -406,16 +515,34 @@ async function runStep(input) {
|
|
|
406
515
|
};
|
|
407
516
|
} catch (error) {
|
|
408
517
|
if (!committedVersion) {
|
|
518
|
+
const status = statusForFailure(signal, error);
|
|
519
|
+
const failureError = status === "failed" ? createPipelineStepFailure({
|
|
520
|
+
pipelineId: pipeline.id,
|
|
521
|
+
pipelineRunId: job.id,
|
|
522
|
+
stepId: step.id,
|
|
523
|
+
stepRunId,
|
|
524
|
+
cause: error
|
|
525
|
+
}) : error;
|
|
409
526
|
const failedStepRun = await runtime.pipelineRunsStorage.finishStep({
|
|
410
527
|
projectId: runtime.id,
|
|
411
528
|
id: stepRunId,
|
|
412
|
-
status
|
|
529
|
+
status,
|
|
413
530
|
rowsWritten,
|
|
414
|
-
error:
|
|
531
|
+
error: captureSixbFailure(failureError, {
|
|
532
|
+
allowedCodes: PIPELINE_RUN_FAILURE_CODES,
|
|
533
|
+
defaultCode: status === "cancelled" ? "runtime.cancelled" : "internal.unexpected",
|
|
534
|
+
details: {
|
|
535
|
+
pipelineId: pipeline.id,
|
|
536
|
+
pipelineRunId: job.id,
|
|
537
|
+
stepId: step.id,
|
|
538
|
+
stepRunId
|
|
539
|
+
}
|
|
540
|
+
})
|
|
415
541
|
}).catch(() => null);
|
|
416
542
|
if (failedStepRun) {
|
|
417
543
|
await input.onStepFinished?.(failedStepRun, lifecycleContext);
|
|
418
544
|
}
|
|
545
|
+
throw failureError;
|
|
419
546
|
}
|
|
420
547
|
throw error;
|
|
421
548
|
}
|
|
@@ -439,10 +566,13 @@ class PipelineRunAlreadyStartedError extends Error {
|
|
|
439
566
|
}
|
|
440
567
|
}
|
|
441
568
|
async function runPipelineJob(input) {
|
|
442
|
-
const { runtime,
|
|
569
|
+
const { runtime, run: durableRun } = input;
|
|
570
|
+
const job = { id: durableRun.id, pipelineId: durableRun.pipelineId };
|
|
443
571
|
const signal = input.signal ?? new AbortController().signal;
|
|
444
|
-
|
|
445
|
-
|
|
572
|
+
if (durableRun.projectId !== runtime.id) {
|
|
573
|
+
throw new Error(`[SixbPipelineWorker] Pipeline run '${durableRun.id}' belongs to project '${durableRun.projectId}', not '${runtime.id}'.`);
|
|
574
|
+
}
|
|
575
|
+
const logSession = resolveLoggingService(runtime.id, runtime.logging).startExecution({
|
|
446
576
|
kind: "pipeline",
|
|
447
577
|
id: job.id
|
|
448
578
|
});
|
|
@@ -455,20 +585,20 @@ async function runPipelineJob(input) {
|
|
|
455
585
|
try {
|
|
456
586
|
startedRun = await runtime.pipelineRunsStorage.start({
|
|
457
587
|
projectId: runtime.id,
|
|
458
|
-
id: job.id
|
|
459
|
-
pipelineId: pipeline.id
|
|
588
|
+
id: job.id
|
|
460
589
|
});
|
|
461
590
|
} catch (error) {
|
|
462
591
|
const existing = await runtime.pipelineRunsStorage.getById({
|
|
463
592
|
projectId: runtime.id,
|
|
464
593
|
id: job.id
|
|
465
594
|
});
|
|
466
|
-
if (existing?.pipelineId ===
|
|
595
|
+
if (existing?.pipelineId === durableRun.pipelineId && existing.executionId === durableRun.executionId) {
|
|
467
596
|
throw new PipelineRunAlreadyStartedError(existing);
|
|
468
597
|
}
|
|
469
598
|
throw error;
|
|
470
599
|
}
|
|
471
600
|
await input.onRunStarted?.(startedRun);
|
|
601
|
+
const pipeline = requirePipeline(runtime.pipelines.getById(job.pipelineId), job);
|
|
472
602
|
for (const [stepIndex, node] of pipeline.graph.nodes.entries()) {
|
|
473
603
|
const stepResult = await runStep({
|
|
474
604
|
runtime,
|
|
@@ -507,135 +637,159 @@ async function runPipelineJob(input) {
|
|
|
507
637
|
}
|
|
508
638
|
throw error;
|
|
509
639
|
}
|
|
640
|
+
const finishedRun = {
|
|
641
|
+
...run,
|
|
642
|
+
finishedAt: requireFinishedAt2({
|
|
643
|
+
pipelineId: pipeline.id,
|
|
644
|
+
runId: job.id,
|
|
645
|
+
finishedAt: run.finishedAt
|
|
646
|
+
})
|
|
647
|
+
};
|
|
510
648
|
finished = true;
|
|
649
|
+
await notifyRunFinished(input.onRunFinished, finishedRun);
|
|
511
650
|
return {
|
|
512
|
-
run:
|
|
513
|
-
...run,
|
|
514
|
-
finishedAt: requireFinishedAt2(job.id, run.finishedAt)
|
|
515
|
-
},
|
|
651
|
+
run: finishedRun,
|
|
516
652
|
steps,
|
|
517
653
|
version: finalVersion
|
|
518
654
|
};
|
|
519
655
|
} catch (error) {
|
|
656
|
+
const reportedError = unwrapPipelineStepFailure(error);
|
|
520
657
|
if (startedRun && !finished) {
|
|
521
658
|
const status = statusForFailure(signal, error);
|
|
522
659
|
try {
|
|
660
|
+
const failure = captureSixbFailure2(error, {
|
|
661
|
+
allowedCodes: PIPELINE_RUN_FAILURE_CODES2,
|
|
662
|
+
defaultCode: status === "cancelled" ? "runtime.cancelled" : "internal.unexpected",
|
|
663
|
+
details: { pipelineId: durableRun.pipelineId, runId: job.id }
|
|
664
|
+
});
|
|
523
665
|
const run = await runtime.pipelineRunsStorage.finish({
|
|
524
666
|
projectId: runtime.id,
|
|
525
667
|
id: job.id,
|
|
526
668
|
status,
|
|
527
|
-
error:
|
|
669
|
+
error: failure
|
|
528
670
|
});
|
|
529
|
-
if (status === "failed" && run.status === "failed")
|
|
530
|
-
input.onRunFailed?.(
|
|
671
|
+
if (status === "failed" && run.status === "failed") {
|
|
672
|
+
input.onRunFailed?.(reportedError, run, failure);
|
|
673
|
+
}
|
|
674
|
+
await notifyRunFinished(input.onRunFinished, run);
|
|
531
675
|
} catch {}
|
|
532
676
|
}
|
|
533
|
-
throw
|
|
677
|
+
throw reportedError;
|
|
534
678
|
} finally {
|
|
535
679
|
await logSession.flush();
|
|
536
680
|
}
|
|
537
681
|
}
|
|
682
|
+
async function notifyRunFinished(handler, run) {
|
|
683
|
+
try {
|
|
684
|
+
await handler?.(run);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error("[SixbPipelineWorker] Pipeline run lifecycle handler failed:", error);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
538
689
|
|
|
539
690
|
// src/worker.ts
|
|
540
691
|
class PipelineWorker extends QueueWorker {
|
|
541
692
|
context;
|
|
542
|
-
|
|
543
|
-
constructor(
|
|
544
|
-
if (
|
|
693
|
+
host;
|
|
694
|
+
constructor(host) {
|
|
695
|
+
if (host.definitions.pipelines.list().length === 0) {
|
|
545
696
|
throw new Error("[SixbPipelineWorker] No pipeline definitions are registered.");
|
|
546
697
|
}
|
|
547
|
-
const pipelineRunsStorage =
|
|
698
|
+
const pipelineRunsStorage = host.storage.pipelineRuns;
|
|
548
699
|
if (!pipelineRunsStorage) {
|
|
549
700
|
throw new Error("[SixbPipelineWorker] Pipeline workers require storage.pipelineRuns support.");
|
|
550
701
|
}
|
|
551
702
|
super({
|
|
552
|
-
projectId:
|
|
553
|
-
queue:
|
|
554
|
-
|
|
703
|
+
projectId: host.id,
|
|
704
|
+
queue: host.queues.pipelines,
|
|
705
|
+
failureCodes: PIPELINE_RUN_FAILURE_CODES3,
|
|
706
|
+
workerId: `pipeline-worker-${host.id}`
|
|
555
707
|
});
|
|
556
|
-
this.context = buildPipelineContext(
|
|
557
|
-
this.
|
|
708
|
+
this.context = buildPipelineContext(host, pipelineRunsStorage);
|
|
709
|
+
this.host = host;
|
|
558
710
|
}
|
|
559
711
|
async execute(claimed, signal) {
|
|
560
712
|
const { job } = claimed;
|
|
713
|
+
const pipelineRuns = this.host.storage.pipelineRuns;
|
|
714
|
+
if (!pipelineRuns) {
|
|
715
|
+
throw new Error("[SixbPipelineWorker] Pipeline workers require storage.pipelineRuns support.");
|
|
716
|
+
}
|
|
717
|
+
const run = await pipelineRuns.getById({ projectId: this.host.id, id: job.payload.runId });
|
|
718
|
+
if (!run) {
|
|
719
|
+
throw new Error(`[SixbPipelineWorker] Pipeline run '${job.payload.runId}' was not found.`);
|
|
720
|
+
}
|
|
721
|
+
const durableExecution = await this.host.storage.executions.getById({
|
|
722
|
+
projectId: this.host.id,
|
|
723
|
+
id: run.executionId
|
|
724
|
+
});
|
|
725
|
+
if (!durableExecution) {
|
|
726
|
+
throw new Error(`[SixbPipelineWorker] Pipeline run '${run.id}' references missing execution '${run.executionId}'.`);
|
|
727
|
+
}
|
|
561
728
|
const pipelineJob = {
|
|
562
|
-
id:
|
|
563
|
-
pipelineId:
|
|
729
|
+
id: run.id,
|
|
730
|
+
pipelineId: run.pipelineId
|
|
564
731
|
};
|
|
565
|
-
|
|
732
|
+
bindDurablePrimitiveExecution(this.host, {
|
|
733
|
+
execution: durableExecution,
|
|
734
|
+
primitive: { kind: "pipeline", id: run.pipelineId, runId: run.id }
|
|
735
|
+
});
|
|
566
736
|
try {
|
|
567
|
-
|
|
737
|
+
await runPipelineJob({
|
|
568
738
|
runtime: this.context,
|
|
569
|
-
|
|
739
|
+
run,
|
|
570
740
|
signal,
|
|
571
|
-
onRunStarted: (
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
741
|
+
onRunStarted: (run2) => emitPipelineRunStarted(this.host.events, run2, durableExecution.correlationId),
|
|
742
|
+
onRunFinished: (run2) => emitPipelineRunFinished(this.host.events, run2, durableExecution.correlationId),
|
|
743
|
+
onRunFailed: (error, run2, failure) => {
|
|
744
|
+
reportRunFailure(this.host, error, {
|
|
745
|
+
projectId: this.host.id,
|
|
576
746
|
attempt: job.attempt,
|
|
747
|
+
runKind: "pipeline",
|
|
577
748
|
run: {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
749
|
+
runId: run2.id,
|
|
750
|
+
pipelineId: run2.pipelineId
|
|
751
|
+
},
|
|
752
|
+
failure
|
|
582
753
|
});
|
|
583
754
|
},
|
|
584
|
-
onStepStarted: (step, context) => emitPipelineRunStepStarted(this.
|
|
585
|
-
onStepFinished: (step, context) => emitPipelineRunStepFinished(this.
|
|
586
|
-
onStepCommitted: (step) => emitDatasetVersionCommitted(this.
|
|
755
|
+
onStepStarted: (step, context) => emitPipelineRunStepStarted(this.host.events, step, context, durableExecution.correlationId),
|
|
756
|
+
onStepFinished: (step, context) => emitPipelineRunStepFinished(this.host.events, step, context, durableExecution.correlationId),
|
|
757
|
+
onStepCommitted: (step) => emitDatasetVersionCommitted(this.host.events, pipelineJob, step, durableExecution.correlationId)
|
|
587
758
|
});
|
|
588
759
|
} catch (error) {
|
|
589
760
|
if (error instanceof PipelineRunAlreadyStartedError)
|
|
590
761
|
return;
|
|
591
762
|
throw error;
|
|
592
763
|
}
|
|
593
|
-
await emitPipelineRunFinished(this.sixb.events, {
|
|
594
|
-
id: pipelineJob.id,
|
|
595
|
-
pipelineId: pipelineJob.pipelineId,
|
|
596
|
-
status: "succeeded",
|
|
597
|
-
datasetId: result.version?.datasetId,
|
|
598
|
-
versionId: result.version?.versionId
|
|
599
|
-
});
|
|
600
764
|
}
|
|
601
765
|
async onExecutionError(claimed, _error) {
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
id: job.payload.runId
|
|
605
|
-
pipelineId: job.payload.pipelineId,
|
|
606
|
-
status: "failed"
|
|
766
|
+
const run = await this.host.storage.pipelineRuns?.getById({
|
|
767
|
+
projectId: this.host.id,
|
|
768
|
+
id: claimed.job.payload.runId
|
|
607
769
|
});
|
|
608
|
-
return { kind: "fail" };
|
|
770
|
+
return run?.status === "failed" && run.error ? { kind: "fail", failure: run.error } : { kind: "fail" };
|
|
609
771
|
}
|
|
610
772
|
async onAbortError(claimed, _error) {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
id: job.payload.runId
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
773
|
+
const run = await this.host.storage.pipelineRuns?.getById({
|
|
774
|
+
projectId: this.host.id,
|
|
775
|
+
id: claimed.job.payload.runId
|
|
776
|
+
});
|
|
777
|
+
if (!run)
|
|
778
|
+
return { kind: "fail" };
|
|
779
|
+
if (!await hasCommittedStep(this.context, run.id)) {
|
|
617
780
|
return { kind: "retry" };
|
|
618
781
|
}
|
|
619
|
-
|
|
620
|
-
id: pipelineJob.id,
|
|
621
|
-
pipelineId: pipelineJob.pipelineId,
|
|
622
|
-
status: "cancelled"
|
|
623
|
-
});
|
|
624
|
-
return { kind: "fail" };
|
|
782
|
+
return run.status === "cancelled" && run.error ? { kind: "fail", failure: run.error } : { kind: "fail" };
|
|
625
783
|
}
|
|
626
784
|
}
|
|
627
|
-
function buildPipelineContext(
|
|
785
|
+
function buildPipelineContext(host, pipelineRunsStorage) {
|
|
628
786
|
return {
|
|
629
|
-
id:
|
|
787
|
+
id: host.id,
|
|
630
788
|
pipelineRunsStorage,
|
|
631
|
-
lakeStorage:
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
},
|
|
636
|
-
getDatasetById(datasetId) {
|
|
637
|
-
return sixb.getDatasetById(datasetId);
|
|
638
|
-
}
|
|
789
|
+
lakeStorage: host.lakeStorage,
|
|
790
|
+
logging: host.logging,
|
|
791
|
+
pipelines: host.definitions.pipelines,
|
|
792
|
+
datasets: host.definitions.datasets
|
|
639
793
|
};
|
|
640
794
|
}
|
|
641
795
|
async function hasCommittedStep(context, pipelineRunId) {
|
|
@@ -651,4 +805,4 @@ export {
|
|
|
651
805
|
PipelineWorker
|
|
652
806
|
};
|
|
653
807
|
|
|
654
|
-
//# debugId=
|
|
808
|
+
//# debugId=C4158195AF5A241E64756E2164756E21
|