@sixb/pipeline-worker 0.1.1 → 0.1.3

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/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.message } : {}
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, job) {
87
- await events?.emit({ events: [{ type: "pipeline.run.finished", payload: buildPipelineRunFinishedPayload(job) }] }, { source: SOURCE });
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(job) {
101
+ function buildPipelineRunFinishedPayload(run) {
90
102
  return {
91
- pipelineId: job.pipelineId,
92
- runId: job.id,
93
- status: job.status,
94
- ...job.datasetId && job.versionId ? {
95
- datasetId: job.datasetId,
96
- versionId: job.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 { resolveLogsRuntime } from "@sixb/core/internal/logging";
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
- class PipelineWorkerError extends Error {
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(runId, finishedAt) {
140
- if (finishedAt) {
141
- return finishedAt;
141
+ function requireFinishedAt2(input) {
142
+ if (input.finishedAt) {
143
+ return input.finishedAt;
142
144
  }
143
- throw new PipelineWorkerError(`[SixbPipelineWorker] Pipeline run '${runId}' finished without a finishedAt timestamp.`);
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${options.pipelineId}' step '${options.stepId}' committed dataset version '${options.version.versionId}', but failed to finalize step run '${options.runId}'. The dataset commit may already have succeeded and the step run record may need repair.`, { cause: options.cause });
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 new PipelineWorkerError(`[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.`, { cause: options.cause });
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 new PipelineWorkerError(`[SixbPipelineWorker] Unknown pipeline '${job.pipelineId}'.`);
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${options.pipelineId}' step '${options.stepId}' ${namedRole} references unknown dataset '${options.datasetId}'.`);
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.getDatasetById(declaredDataset.id),
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' input '${name}' dataset '${dataset.id}' has no committed version.`);
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses SQL execution, which is not supported by the run step executor.`);
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${input.pipelineId}' step '${input.stepId}' writes in ` + `'${input.mode}' mode, which the ${input.dialect} SQL executor does not support.`);
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' uses run execution, which is not supported by the SQL step executor.`);
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 new PipelineWorkerError(`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' requires SQL transform support, but lake storage does not provide lakeStorage.sql.execute(...).`);
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
- stepId: step.id
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
- const resolvedInputs = await resolveStepInputs({ runtime, pipeline, step });
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
- runId: stepRunId,
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: statusForFailure(signal, error),
529
+ status,
413
530
  rowsWritten,
414
- error: toPipelineRunFailure(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, job } = input;
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
- const pipeline = requirePipeline(runtime.getPipelineById(job.pipelineId), job);
445
- const logSession = resolveLogsRuntime(runtime.id, runtime.logs).startExecution({
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 === pipeline.id) {
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,160 @@ 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: toPipelineRunFailure(error)
669
+ error: failure
528
670
  });
529
- if (status === "failed" && run.status === "failed")
530
- input.onRunFailed?.(error, run);
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 error;
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
- sixb;
543
- constructor(sixb) {
544
- if (sixb.listPipelines().length === 0) {
693
+ host;
694
+ constructor(host, options = {}) {
695
+ if (host.definitions.pipelines.list().length === 0) {
545
696
  throw new Error("[SixbPipelineWorker] No pipeline definitions are registered.");
546
697
  }
547
- const pipelineRunsStorage = sixb.storage.pipelineRuns;
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: sixb.id,
553
- queue: sixb.queues.pipelines,
554
- workerId: `pipeline-worker-${sixb.id}`
703
+ projectId: host.id,
704
+ queue: host.queues.pipelines,
705
+ failureCodes: PIPELINE_RUN_FAILURE_CODES3,
706
+ workerId: `pipeline-worker-${host.id}`,
707
+ claimLimit: options.concurrency
555
708
  });
556
- this.context = buildPipelineContext(sixb, pipelineRunsStorage);
557
- this.sixb = sixb;
709
+ this.context = buildPipelineContext(host, pipelineRunsStorage);
710
+ this.host = host;
558
711
  }
559
712
  async execute(claimed, signal) {
560
713
  const { job } = claimed;
714
+ const pipelineRuns = this.host.storage.pipelineRuns;
715
+ if (!pipelineRuns) {
716
+ throw new Error("[SixbPipelineWorker] Pipeline workers require storage.pipelineRuns support.");
717
+ }
718
+ const run = await pipelineRuns.getById({ projectId: this.host.id, id: job.payload.runId });
719
+ if (!run) {
720
+ throw new Error(`[SixbPipelineWorker] Pipeline run '${job.payload.runId}' was not found.`);
721
+ }
722
+ const durableExecution = await this.host.storage.executions.getById({
723
+ projectId: this.host.id,
724
+ id: run.executionId
725
+ });
726
+ if (!durableExecution) {
727
+ throw new Error(`[SixbPipelineWorker] Pipeline run '${run.id}' references missing execution '${run.executionId}'.`);
728
+ }
561
729
  const pipelineJob = {
562
- id: job.payload.runId ?? `${job.id}:attempt:${job.attempt}`,
563
- pipelineId: job.payload.pipelineId
730
+ id: run.id,
731
+ pipelineId: run.pipelineId
564
732
  };
565
- let result;
733
+ bindDurablePrimitiveExecution(this.host, {
734
+ execution: durableExecution,
735
+ primitive: { kind: "pipeline", id: run.pipelineId, runId: run.id }
736
+ });
566
737
  try {
567
- result = await runPipelineJob({
738
+ await runPipelineJob({
568
739
  runtime: this.context,
569
- job: pipelineJob,
740
+ run,
570
741
  signal,
571
- onRunStarted: (run) => emitPipelineRunStarted(this.sixb.events, run),
572
- onRunFailed: (error, run) => {
573
- reportRunFailure(this.sixb, error, {
574
- projectId: this.sixb.id,
575
- occurredAt: run.finishedAt,
742
+ onRunStarted: (run2) => emitPipelineRunStarted(this.host.events, run2, durableExecution.correlationId),
743
+ onRunFinished: (run2) => emitPipelineRunFinished(this.host.events, run2, durableExecution.correlationId),
744
+ onRunFailed: (error, run2, failure) => {
745
+ reportRunFailure(this.host, error, {
746
+ projectId: this.host.id,
576
747
  attempt: job.attempt,
748
+ runKind: "pipeline",
577
749
  run: {
578
- kind: "pipeline",
579
- runId: run.id,
580
- pipelineId: run.pipelineId
581
- }
750
+ runId: run2.id,
751
+ pipelineId: run2.pipelineId
752
+ },
753
+ failure
582
754
  });
583
755
  },
584
- onStepStarted: (step, context) => emitPipelineRunStepStarted(this.sixb.events, step, context),
585
- onStepFinished: (step, context) => emitPipelineRunStepFinished(this.sixb.events, step, context),
586
- onStepCommitted: (step) => emitDatasetVersionCommitted(this.sixb.events, pipelineJob, step)
756
+ onStepStarted: (step, context) => emitPipelineRunStepStarted(this.host.events, step, context, durableExecution.correlationId),
757
+ onStepFinished: (step, context) => emitPipelineRunStepFinished(this.host.events, step, context, durableExecution.correlationId),
758
+ onStepCommitted: (step) => emitDatasetVersionCommitted(this.host.events, pipelineJob, step, durableExecution.correlationId)
587
759
  });
588
760
  } catch (error) {
589
761
  if (error instanceof PipelineRunAlreadyStartedError)
590
762
  return;
591
763
  throw error;
592
764
  }
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
765
  }
601
766
  async onExecutionError(claimed, _error) {
602
- const { job } = claimed;
603
- await emitPipelineRunFinished(this.sixb.events, {
604
- id: job.payload.runId ?? `${job.id}:attempt:${job.attempt}`,
605
- pipelineId: job.payload.pipelineId,
606
- status: "failed"
767
+ const run = await this.host.storage.pipelineRuns?.getById({
768
+ projectId: this.host.id,
769
+ id: claimed.job.payload.runId
607
770
  });
608
- return { kind: "fail" };
771
+ return run?.status === "failed" && run.error ? { kind: "fail", failure: run.error } : { kind: "fail" };
609
772
  }
610
773
  async onAbortError(claimed, _error) {
611
- const { job } = claimed;
612
- const pipelineJob = {
613
- id: job.payload.runId ?? `${job.id}:attempt:${job.attempt}`,
614
- pipelineId: job.payload.pipelineId
615
- };
616
- if (!await hasCommittedStep(this.context, pipelineJob.id)) {
774
+ const run = await this.host.storage.pipelineRuns?.getById({
775
+ projectId: this.host.id,
776
+ id: claimed.job.payload.runId
777
+ });
778
+ if (!run)
779
+ return { kind: "fail" };
780
+ if (!await hasCommittedStep(this.context, run.id)) {
617
781
  return { kind: "retry" };
618
782
  }
619
- await emitPipelineRunFinished(this.sixb.events, {
620
- id: pipelineJob.id,
621
- pipelineId: pipelineJob.pipelineId,
622
- status: "cancelled"
623
- });
624
- return { kind: "fail" };
783
+ return run.status === "cancelled" && run.error ? { kind: "fail", failure: run.error } : { kind: "fail" };
625
784
  }
626
785
  }
627
- function buildPipelineContext(sixb, pipelineRunsStorage) {
786
+ function buildPipelineContext(host, pipelineRunsStorage) {
628
787
  return {
629
- id: sixb.id,
788
+ id: host.id,
630
789
  pipelineRunsStorage,
631
- lakeStorage: sixb.lakeStorage,
632
- logs: sixb.logs,
633
- getPipelineById(pipelineId) {
634
- return sixb.getPipelineById(pipelineId);
635
- },
636
- getDatasetById(datasetId) {
637
- return sixb.getDatasetById(datasetId);
638
- }
790
+ lakeStorage: host.lakeStorage,
791
+ logging: host.logging,
792
+ pipelines: host.definitions.pipelines,
793
+ datasets: host.definitions.datasets
639
794
  };
640
795
  }
641
796
  async function hasCommittedStep(context, pipelineRunId) {
@@ -651,4 +806,4 @@ export {
651
806
  PipelineWorker
652
807
  };
653
808
 
654
- //# debugId=068611D3BE49C18D64756E2164756E21
809
+ //# debugId=DD983D45D23B0E1464756E2164756E21