@coji/durably 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{index-4aPZWn8r.d.ts → index-BjlCb0gP.d.ts} +69 -19
- package/dist/index.d.ts +15 -3
- package/dist/index.js +477 -173
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/docs/llms.md +65 -12
- package/package.json +16 -16
package/dist/index.js
CHANGED
|
@@ -93,15 +93,17 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
93
93
|
const validatedInput = validateJobInputOrThrow(inputSchema, input);
|
|
94
94
|
const run = await storage.createRun({
|
|
95
95
|
jobName: jobDef.name,
|
|
96
|
-
|
|
96
|
+
input: validatedInput,
|
|
97
97
|
idempotencyKey: options?.idempotencyKey,
|
|
98
|
-
concurrencyKey: options?.concurrencyKey
|
|
98
|
+
concurrencyKey: options?.concurrencyKey,
|
|
99
|
+
labels: options?.labels
|
|
99
100
|
});
|
|
100
101
|
eventEmitter.emit({
|
|
101
102
|
type: "run:trigger",
|
|
102
103
|
runId: run.id,
|
|
103
104
|
jobName: jobDef.name,
|
|
104
|
-
|
|
105
|
+
input: validatedInput,
|
|
106
|
+
labels: run.labels
|
|
105
107
|
});
|
|
106
108
|
return run;
|
|
107
109
|
},
|
|
@@ -177,16 +179,17 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
177
179
|
`at index ${i}`
|
|
178
180
|
);
|
|
179
181
|
validated.push({
|
|
180
|
-
|
|
182
|
+
input: validatedInput,
|
|
181
183
|
options: normalized[i].options
|
|
182
184
|
});
|
|
183
185
|
}
|
|
184
186
|
const runs = await storage.batchCreateRuns(
|
|
185
187
|
validated.map((v) => ({
|
|
186
188
|
jobName: jobDef.name,
|
|
187
|
-
|
|
189
|
+
input: v.input,
|
|
188
190
|
idempotencyKey: v.options?.idempotencyKey,
|
|
189
|
-
concurrencyKey: v.options?.concurrencyKey
|
|
191
|
+
concurrencyKey: v.options?.concurrencyKey,
|
|
192
|
+
labels: v.options?.labels
|
|
190
193
|
}))
|
|
191
194
|
);
|
|
192
195
|
for (let i = 0; i < runs.length; i++) {
|
|
@@ -194,7 +197,8 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
194
197
|
type: "run:trigger",
|
|
195
198
|
runId: runs[i].id,
|
|
196
199
|
jobName: jobDef.name,
|
|
197
|
-
|
|
200
|
+
input: validated[i].input,
|
|
201
|
+
labels: runs[i].labels
|
|
198
202
|
});
|
|
199
203
|
}
|
|
200
204
|
return runs;
|
|
@@ -230,11 +234,11 @@ var migrations = [
|
|
|
230
234
|
{
|
|
231
235
|
version: 1,
|
|
232
236
|
up: async (db) => {
|
|
233
|
-
await db.schema.createTable("durably_runs").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("job_name", "text", (col) => col.notNull()).addColumn("
|
|
237
|
+
await db.schema.createTable("durably_runs").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("job_name", "text", (col) => col.notNull()).addColumn("input", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull()).addColumn("idempotency_key", "text").addColumn("concurrency_key", "text").addColumn("labels", "text", (col) => col.notNull().defaultTo("{}")).addColumn(
|
|
234
238
|
"current_step_index",
|
|
235
239
|
"integer",
|
|
236
240
|
(col) => col.notNull().defaultTo(0)
|
|
237
|
-
).addColumn("progress", "text").addColumn("output", "text").addColumn("error", "text").addColumn("heartbeat_at", "text", (col) => col.notNull()).addColumn("created_at", "text", (col) => col.notNull()).addColumn("updated_at", "text", (col) => col.notNull()).execute();
|
|
241
|
+
).addColumn("progress", "text").addColumn("output", "text").addColumn("error", "text").addColumn("heartbeat_at", "text", (col) => col.notNull()).addColumn("started_at", "text").addColumn("completed_at", "text").addColumn("created_at", "text", (col) => col.notNull()).addColumn("updated_at", "text", (col) => col.notNull()).execute();
|
|
238
242
|
await db.schema.createIndex("idx_durably_runs_job_idempotency").ifNotExists().on("durably_runs").columns(["job_name", "idempotency_key"]).unique().execute();
|
|
239
243
|
await db.schema.createIndex("idx_durably_runs_status_concurrency").ifNotExists().on("durably_runs").columns(["status", "concurrency_key"]).execute();
|
|
240
244
|
await db.schema.createIndex("idx_durably_runs_status_created").ifNotExists().on("durably_runs").columns(["status", "created_at"]).execute();
|
|
@@ -258,22 +262,47 @@ async function runMigrations(db) {
|
|
|
258
262
|
const currentVersion = await getCurrentVersion(db);
|
|
259
263
|
for (const migration of migrations) {
|
|
260
264
|
if (migration.version > currentVersion) {
|
|
261
|
-
await
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
await db.transaction().execute(async (trx) => {
|
|
266
|
+
await migration.up(trx);
|
|
267
|
+
await trx.insertInto("durably_schema_versions").values({
|
|
268
|
+
version: migration.version,
|
|
269
|
+
applied_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
270
|
+
}).execute();
|
|
271
|
+
});
|
|
266
272
|
}
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
// src/storage.ts
|
|
271
|
-
import {
|
|
277
|
+
import { sql } from "kysely";
|
|
278
|
+
import { monotonicFactory } from "ulidx";
|
|
279
|
+
var ulid = monotonicFactory();
|
|
280
|
+
function toClientRun(run) {
|
|
281
|
+
const {
|
|
282
|
+
idempotencyKey,
|
|
283
|
+
concurrencyKey,
|
|
284
|
+
heartbeatAt,
|
|
285
|
+
updatedAt,
|
|
286
|
+
...clientRun
|
|
287
|
+
} = run;
|
|
288
|
+
return clientRun;
|
|
289
|
+
}
|
|
290
|
+
var LABEL_KEY_PATTERN = /^[a-zA-Z0-9\-_./]+$/;
|
|
291
|
+
function validateLabels(labels) {
|
|
292
|
+
if (!labels) return;
|
|
293
|
+
for (const key of Object.keys(labels)) {
|
|
294
|
+
if (!LABEL_KEY_PATTERN.test(key)) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Invalid label key "${key}": must contain only alphanumeric characters, dashes, underscores, dots, and slashes`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
272
301
|
function rowToRun(row) {
|
|
273
302
|
return {
|
|
274
303
|
id: row.id,
|
|
275
304
|
jobName: row.job_name,
|
|
276
|
-
|
|
305
|
+
input: JSON.parse(row.input),
|
|
277
306
|
status: row.status,
|
|
278
307
|
idempotencyKey: row.idempotency_key,
|
|
279
308
|
concurrencyKey: row.concurrency_key,
|
|
@@ -282,7 +311,10 @@ function rowToRun(row) {
|
|
|
282
311
|
progress: row.progress ? JSON.parse(row.progress) : null,
|
|
283
312
|
output: row.output ? JSON.parse(row.output) : null,
|
|
284
313
|
error: row.error,
|
|
314
|
+
labels: JSON.parse(row.labels),
|
|
285
315
|
heartbeatAt: row.heartbeat_at,
|
|
316
|
+
startedAt: row.started_at,
|
|
317
|
+
completedAt: row.completed_at,
|
|
286
318
|
createdAt: row.created_at,
|
|
287
319
|
updatedAt: row.updated_at
|
|
288
320
|
};
|
|
@@ -321,11 +353,12 @@ function createKyselyStorage(db) {
|
|
|
321
353
|
return rowToRun(existing);
|
|
322
354
|
}
|
|
323
355
|
}
|
|
356
|
+
validateLabels(input.labels);
|
|
324
357
|
const id = ulid();
|
|
325
358
|
const run = {
|
|
326
359
|
id,
|
|
327
360
|
job_name: input.jobName,
|
|
328
|
-
|
|
361
|
+
input: JSON.stringify(input.input),
|
|
329
362
|
status: "pending",
|
|
330
363
|
idempotency_key: input.idempotencyKey ?? null,
|
|
331
364
|
concurrency_key: input.concurrencyKey ?? null,
|
|
@@ -333,7 +366,10 @@ function createKyselyStorage(db) {
|
|
|
333
366
|
progress: null,
|
|
334
367
|
output: null,
|
|
335
368
|
error: null,
|
|
369
|
+
labels: JSON.stringify(input.labels ?? {}),
|
|
336
370
|
heartbeat_at: now,
|
|
371
|
+
started_at: null,
|
|
372
|
+
completed_at: null,
|
|
337
373
|
created_at: now,
|
|
338
374
|
updated_at: now
|
|
339
375
|
};
|
|
@@ -347,6 +383,9 @@ function createKyselyStorage(db) {
|
|
|
347
383
|
return await db.transaction().execute(async (trx) => {
|
|
348
384
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
349
385
|
const runs = [];
|
|
386
|
+
for (const input of inputs) {
|
|
387
|
+
validateLabels(input.labels);
|
|
388
|
+
}
|
|
350
389
|
for (const input of inputs) {
|
|
351
390
|
if (input.idempotencyKey) {
|
|
352
391
|
const existing = await trx.selectFrom("durably_runs").selectAll().where("job_name", "=", input.jobName).where("idempotency_key", "=", input.idempotencyKey).executeTakeFirst();
|
|
@@ -359,7 +398,7 @@ function createKyselyStorage(db) {
|
|
|
359
398
|
runs.push({
|
|
360
399
|
id,
|
|
361
400
|
job_name: input.jobName,
|
|
362
|
-
|
|
401
|
+
input: JSON.stringify(input.input),
|
|
363
402
|
status: "pending",
|
|
364
403
|
idempotency_key: input.idempotencyKey ?? null,
|
|
365
404
|
concurrency_key: input.concurrencyKey ?? null,
|
|
@@ -367,7 +406,10 @@ function createKyselyStorage(db) {
|
|
|
367
406
|
progress: null,
|
|
368
407
|
output: null,
|
|
369
408
|
error: null,
|
|
409
|
+
labels: JSON.stringify(input.labels ?? {}),
|
|
370
410
|
heartbeat_at: now,
|
|
411
|
+
started_at: null,
|
|
412
|
+
completed_at: null,
|
|
371
413
|
created_at: now,
|
|
372
414
|
updated_at: now
|
|
373
415
|
});
|
|
@@ -394,6 +436,9 @@ function createKyselyStorage(db) {
|
|
|
394
436
|
if (data.error !== void 0) updates.error = data.error;
|
|
395
437
|
if (data.heartbeatAt !== void 0)
|
|
396
438
|
updates.heartbeat_at = data.heartbeatAt;
|
|
439
|
+
if (data.startedAt !== void 0) updates.started_at = data.startedAt;
|
|
440
|
+
if (data.completedAt !== void 0)
|
|
441
|
+
updates.completed_at = data.completedAt;
|
|
397
442
|
await db.updateTable("durably_runs").set(updates).where("id", "=", runId).execute();
|
|
398
443
|
},
|
|
399
444
|
async deleteRun(runId) {
|
|
@@ -417,6 +462,16 @@ function createKyselyStorage(db) {
|
|
|
417
462
|
if (filter?.jobName) {
|
|
418
463
|
query = query.where("durably_runs.job_name", "=", filter.jobName);
|
|
419
464
|
}
|
|
465
|
+
if (filter?.labels) {
|
|
466
|
+
validateLabels(filter.labels);
|
|
467
|
+
for (const [key, value] of Object.entries(filter.labels)) {
|
|
468
|
+
query = query.where(
|
|
469
|
+
sql`json_extract(durably_runs.labels, ${`$."${key}"`})`,
|
|
470
|
+
"=",
|
|
471
|
+
value
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
420
475
|
query = query.orderBy("durably_runs.created_at", "desc");
|
|
421
476
|
if (filter?.limit !== void 0) {
|
|
422
477
|
query = query.limit(filter.limit);
|
|
@@ -430,24 +485,29 @@ function createKyselyStorage(db) {
|
|
|
430
485
|
const rows = await query.execute();
|
|
431
486
|
return rows.map(rowToRun);
|
|
432
487
|
},
|
|
433
|
-
async
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
).where("durably_runs.status", "=", "pending").groupBy("durably_runs.id").orderBy("durably_runs.created_at", "asc").orderBy("durably_runs.id", "asc").limit(1);
|
|
488
|
+
async claimNextPendingRun(excludeConcurrencyKeys) {
|
|
489
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
490
|
+
let subquery = db.selectFrom("durably_runs").select("id").where("status", "=", "pending").orderBy("created_at", "asc").orderBy("id", "asc").limit(1);
|
|
437
491
|
if (excludeConcurrencyKeys.length > 0) {
|
|
438
|
-
|
|
492
|
+
subquery = subquery.where(
|
|
439
493
|
(eb) => eb.or([
|
|
440
|
-
eb("
|
|
441
|
-
eb(
|
|
442
|
-
"durably_runs.concurrency_key",
|
|
443
|
-
"not in",
|
|
444
|
-
excludeConcurrencyKeys
|
|
445
|
-
)
|
|
494
|
+
eb("concurrency_key", "is", null),
|
|
495
|
+
eb("concurrency_key", "not in", excludeConcurrencyKeys)
|
|
446
496
|
])
|
|
447
497
|
);
|
|
448
498
|
}
|
|
449
|
-
const row = await
|
|
450
|
-
|
|
499
|
+
const row = await db.updateTable("durably_runs").set({
|
|
500
|
+
status: "running",
|
|
501
|
+
heartbeat_at: now,
|
|
502
|
+
started_at: sql`COALESCE(started_at, ${now})`,
|
|
503
|
+
updated_at: now
|
|
504
|
+
}).where(
|
|
505
|
+
"id",
|
|
506
|
+
"=",
|
|
507
|
+
(eb) => eb.selectFrom(subquery.as("sub")).select("id")
|
|
508
|
+
).returningAll().executeTakeFirst();
|
|
509
|
+
if (!row) return null;
|
|
510
|
+
return rowToRun({ ...row, step_count: 0 });
|
|
451
511
|
},
|
|
452
512
|
async createStep(input) {
|
|
453
513
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -514,13 +574,26 @@ function getErrorMessage(error) {
|
|
|
514
574
|
function createStepContext(run, jobName, storage, eventEmitter) {
|
|
515
575
|
let stepIndex = run.currentStepIndex;
|
|
516
576
|
let currentStepName = null;
|
|
517
|
-
|
|
577
|
+
const controller = new AbortController();
|
|
578
|
+
const unsubscribe = eventEmitter.on("run:cancel", (event) => {
|
|
579
|
+
if (event.runId === run.id) {
|
|
580
|
+
controller.abort();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
const step = {
|
|
518
584
|
get runId() {
|
|
519
585
|
return run.id;
|
|
520
586
|
},
|
|
521
587
|
async run(name, fn) {
|
|
588
|
+
if (controller.signal.aborted) {
|
|
589
|
+
throw new CancelledError(run.id);
|
|
590
|
+
}
|
|
522
591
|
const currentRun = await storage.getRun(run.id);
|
|
523
592
|
if (currentRun?.status === "cancelled") {
|
|
593
|
+
controller.abort();
|
|
594
|
+
throw new CancelledError(run.id);
|
|
595
|
+
}
|
|
596
|
+
if (controller.signal.aborted) {
|
|
524
597
|
throw new CancelledError(run.id);
|
|
525
598
|
}
|
|
526
599
|
const existingStep = await storage.getCompletedStep(run.id, name);
|
|
@@ -536,10 +609,11 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
536
609
|
runId: run.id,
|
|
537
610
|
jobName,
|
|
538
611
|
stepName: name,
|
|
539
|
-
stepIndex
|
|
612
|
+
stepIndex,
|
|
613
|
+
labels: run.labels
|
|
540
614
|
});
|
|
541
615
|
try {
|
|
542
|
-
const result = await fn();
|
|
616
|
+
const result = await fn(controller.signal);
|
|
543
617
|
await storage.createStep({
|
|
544
618
|
runId: run.id,
|
|
545
619
|
name,
|
|
@@ -557,27 +631,32 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
557
631
|
stepName: name,
|
|
558
632
|
stepIndex: stepIndex - 1,
|
|
559
633
|
output: result,
|
|
560
|
-
duration: Date.now() - startTime
|
|
634
|
+
duration: Date.now() - startTime,
|
|
635
|
+
labels: run.labels
|
|
561
636
|
});
|
|
562
637
|
return result;
|
|
563
638
|
} catch (error) {
|
|
639
|
+
const isCancelled = controller.signal.aborted;
|
|
564
640
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
565
641
|
await storage.createStep({
|
|
566
642
|
runId: run.id,
|
|
567
643
|
name,
|
|
568
644
|
index: stepIndex,
|
|
569
|
-
status: "failed",
|
|
645
|
+
status: isCancelled ? "cancelled" : "failed",
|
|
570
646
|
error: errorMessage,
|
|
571
647
|
startedAt
|
|
572
648
|
});
|
|
573
649
|
eventEmitter.emit({
|
|
574
|
-
type: "step:fail",
|
|
650
|
+
...isCancelled ? { type: "step:cancel" } : { type: "step:fail", error: errorMessage },
|
|
575
651
|
runId: run.id,
|
|
576
652
|
jobName,
|
|
577
653
|
stepName: name,
|
|
578
654
|
stepIndex,
|
|
579
|
-
|
|
655
|
+
labels: run.labels
|
|
580
656
|
});
|
|
657
|
+
if (isCancelled) {
|
|
658
|
+
throw new CancelledError(run.id);
|
|
659
|
+
}
|
|
581
660
|
throw error;
|
|
582
661
|
} finally {
|
|
583
662
|
currentStepName = null;
|
|
@@ -590,7 +669,8 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
590
669
|
type: "run:progress",
|
|
591
670
|
runId: run.id,
|
|
592
671
|
jobName,
|
|
593
|
-
progress: progressData
|
|
672
|
+
progress: progressData,
|
|
673
|
+
labels: run.labels
|
|
594
674
|
});
|
|
595
675
|
},
|
|
596
676
|
log: {
|
|
@@ -598,6 +678,8 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
598
678
|
eventEmitter.emit({
|
|
599
679
|
type: "log:write",
|
|
600
680
|
runId: run.id,
|
|
681
|
+
jobName,
|
|
682
|
+
labels: run.labels,
|
|
601
683
|
stepName: currentStepName,
|
|
602
684
|
level: "info",
|
|
603
685
|
message,
|
|
@@ -608,6 +690,8 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
608
690
|
eventEmitter.emit({
|
|
609
691
|
type: "log:write",
|
|
610
692
|
runId: run.id,
|
|
693
|
+
jobName,
|
|
694
|
+
labels: run.labels,
|
|
611
695
|
stepName: currentStepName,
|
|
612
696
|
level: "warn",
|
|
613
697
|
message,
|
|
@@ -618,6 +702,8 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
618
702
|
eventEmitter.emit({
|
|
619
703
|
type: "log:write",
|
|
620
704
|
runId: run.id,
|
|
705
|
+
jobName,
|
|
706
|
+
labels: run.labels,
|
|
621
707
|
stepName: currentStepName,
|
|
622
708
|
level: "error",
|
|
623
709
|
message,
|
|
@@ -626,6 +712,7 @@ function createStepContext(run, jobName, storage, eventEmitter) {
|
|
|
626
712
|
}
|
|
627
713
|
}
|
|
628
714
|
};
|
|
715
|
+
return { step, dispose: unsubscribe };
|
|
629
716
|
}
|
|
630
717
|
|
|
631
718
|
// src/worker.ts
|
|
@@ -658,19 +745,21 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
658
745
|
}
|
|
659
746
|
async function handleRunSuccess(runId, jobName, output, startTime) {
|
|
660
747
|
const currentRun = await storage.getRun(runId);
|
|
661
|
-
if (currentRun
|
|
748
|
+
if (!currentRun || currentRun.status === "cancelled") {
|
|
662
749
|
return;
|
|
663
750
|
}
|
|
664
751
|
await storage.updateRun(runId, {
|
|
665
752
|
status: "completed",
|
|
666
|
-
output
|
|
753
|
+
output,
|
|
754
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
667
755
|
});
|
|
668
756
|
eventEmitter.emit({
|
|
669
757
|
type: "run:complete",
|
|
670
758
|
runId,
|
|
671
759
|
jobName,
|
|
672
760
|
output,
|
|
673
|
-
duration: Date.now() - startTime
|
|
761
|
+
duration: Date.now() - startTime,
|
|
762
|
+
labels: currentRun.labels
|
|
674
763
|
});
|
|
675
764
|
}
|
|
676
765
|
async function handleRunFailure(runId, jobName, error) {
|
|
@@ -678,7 +767,7 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
678
767
|
return;
|
|
679
768
|
}
|
|
680
769
|
const currentRun = await storage.getRun(runId);
|
|
681
|
-
if (currentRun
|
|
770
|
+
if (!currentRun || currentRun.status === "cancelled") {
|
|
682
771
|
return;
|
|
683
772
|
}
|
|
684
773
|
const errorMessage = getErrorMessage(error);
|
|
@@ -686,14 +775,16 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
686
775
|
const failedStep = steps.find((s) => s.status === "failed");
|
|
687
776
|
await storage.updateRun(runId, {
|
|
688
777
|
status: "failed",
|
|
689
|
-
error: errorMessage
|
|
778
|
+
error: errorMessage,
|
|
779
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
690
780
|
});
|
|
691
781
|
eventEmitter.emit({
|
|
692
782
|
type: "run:fail",
|
|
693
783
|
runId,
|
|
694
784
|
jobName,
|
|
695
785
|
error: errorMessage,
|
|
696
|
-
failedStepName: failedStep?.name ?? "unknown"
|
|
786
|
+
failedStepName: failedStep?.name ?? "unknown",
|
|
787
|
+
labels: currentRun.labels
|
|
697
788
|
});
|
|
698
789
|
}
|
|
699
790
|
async function executeRun(run, job) {
|
|
@@ -712,12 +803,18 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
712
803
|
type: "run:start",
|
|
713
804
|
runId: run.id,
|
|
714
805
|
jobName: run.jobName,
|
|
715
|
-
|
|
806
|
+
input: run.input,
|
|
807
|
+
labels: run.labels
|
|
716
808
|
});
|
|
717
809
|
const startTime = Date.now();
|
|
810
|
+
const { step, dispose } = createStepContext(
|
|
811
|
+
run,
|
|
812
|
+
run.jobName,
|
|
813
|
+
storage,
|
|
814
|
+
eventEmitter
|
|
815
|
+
);
|
|
718
816
|
try {
|
|
719
|
-
const
|
|
720
|
-
const output = await job.fn(step, run.payload);
|
|
817
|
+
const output = await job.fn(step, run.input);
|
|
721
818
|
if (job.outputSchema) {
|
|
722
819
|
const parseResult = job.outputSchema.safeParse(output);
|
|
723
820
|
if (!parseResult.success) {
|
|
@@ -728,6 +825,7 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
728
825
|
} catch (error) {
|
|
729
826
|
await handleRunFailure(run.id, run.jobName, error);
|
|
730
827
|
} finally {
|
|
828
|
+
dispose();
|
|
731
829
|
if (heartbeatInterval) {
|
|
732
830
|
clearInterval(heartbeatInterval);
|
|
733
831
|
heartbeatInterval = null;
|
|
@@ -740,7 +838,7 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
740
838
|
const excludeConcurrencyKeys = runningRuns.filter(
|
|
741
839
|
(r) => r.concurrencyKey !== null
|
|
742
840
|
).map((r) => r.concurrencyKey);
|
|
743
|
-
const run = await storage.
|
|
841
|
+
const run = await storage.claimNextPendingRun(excludeConcurrencyKeys);
|
|
744
842
|
if (!run) {
|
|
745
843
|
return false;
|
|
746
844
|
}
|
|
@@ -752,10 +850,6 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
752
850
|
});
|
|
753
851
|
return true;
|
|
754
852
|
}
|
|
755
|
-
await storage.updateRun(run.id, {
|
|
756
|
-
status: "running",
|
|
757
|
-
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
758
|
-
});
|
|
759
853
|
await executeRun(run, job);
|
|
760
854
|
return true;
|
|
761
855
|
}
|
|
@@ -889,6 +983,14 @@ function createDurablyInstance(state, jobs) {
|
|
|
889
983
|
controller.enqueue(event);
|
|
890
984
|
}
|
|
891
985
|
});
|
|
986
|
+
const unsubscribeDelete = eventEmitter.on("run:delete", (event) => {
|
|
987
|
+
if (!closed && event.runId === runId) {
|
|
988
|
+
controller.enqueue(event);
|
|
989
|
+
closed = true;
|
|
990
|
+
cleanup?.();
|
|
991
|
+
controller.close();
|
|
992
|
+
}
|
|
993
|
+
});
|
|
892
994
|
const unsubscribeRetry = eventEmitter.on("run:retry", (event) => {
|
|
893
995
|
if (!closed && event.runId === runId) {
|
|
894
996
|
controller.enqueue(event);
|
|
@@ -933,6 +1035,7 @@ function createDurablyInstance(state, jobs) {
|
|
|
933
1035
|
unsubscribeComplete();
|
|
934
1036
|
unsubscribeFail();
|
|
935
1037
|
unsubscribeCancel();
|
|
1038
|
+
unsubscribeDelete();
|
|
936
1039
|
unsubscribeRetry();
|
|
937
1040
|
unsubscribeProgress();
|
|
938
1041
|
unsubscribeStepStart();
|
|
@@ -970,7 +1073,8 @@ function createDurablyInstance(state, jobs) {
|
|
|
970
1073
|
eventEmitter.emit({
|
|
971
1074
|
type: "run:retry",
|
|
972
1075
|
runId,
|
|
973
|
-
jobName: run.jobName
|
|
1076
|
+
jobName: run.jobName,
|
|
1077
|
+
labels: run.labels
|
|
974
1078
|
});
|
|
975
1079
|
},
|
|
976
1080
|
async cancel(runId) {
|
|
@@ -993,7 +1097,8 @@ function createDurablyInstance(state, jobs) {
|
|
|
993
1097
|
eventEmitter.emit({
|
|
994
1098
|
type: "run:cancel",
|
|
995
1099
|
runId,
|
|
996
|
-
jobName: run.jobName
|
|
1100
|
+
jobName: run.jobName,
|
|
1101
|
+
labels: run.labels
|
|
997
1102
|
});
|
|
998
1103
|
},
|
|
999
1104
|
async deleteRun(runId) {
|
|
@@ -1008,6 +1113,12 @@ function createDurablyInstance(state, jobs) {
|
|
|
1008
1113
|
throw new Error(`Cannot delete running run: ${runId}`);
|
|
1009
1114
|
}
|
|
1010
1115
|
await storage.deleteRun(runId);
|
|
1116
|
+
eventEmitter.emit({
|
|
1117
|
+
type: "run:delete",
|
|
1118
|
+
runId,
|
|
1119
|
+
jobName: run.jobName,
|
|
1120
|
+
labels: run.labels
|
|
1121
|
+
});
|
|
1011
1122
|
},
|
|
1012
1123
|
async migrate() {
|
|
1013
1124
|
if (state.migrated) {
|
|
@@ -1132,6 +1243,48 @@ function createSSEStreamFromReader(reader) {
|
|
|
1132
1243
|
}
|
|
1133
1244
|
});
|
|
1134
1245
|
}
|
|
1246
|
+
function createThrottledSSEStreamFromReader(reader, throttleMs) {
|
|
1247
|
+
if (throttleMs <= 0) {
|
|
1248
|
+
return createSSEStreamFromReader(reader);
|
|
1249
|
+
}
|
|
1250
|
+
const encoder = createSSEEncoder();
|
|
1251
|
+
let closed = false;
|
|
1252
|
+
let throttle = null;
|
|
1253
|
+
return new ReadableStream({
|
|
1254
|
+
async start(controller) {
|
|
1255
|
+
const innerCtrl = {
|
|
1256
|
+
enqueue: (data) => controller.enqueue(encodeSSE(encoder, data)),
|
|
1257
|
+
close: () => {
|
|
1258
|
+
closed = true;
|
|
1259
|
+
controller.close();
|
|
1260
|
+
},
|
|
1261
|
+
get closed() {
|
|
1262
|
+
return closed;
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
throttle = createThrottledSSEController(innerCtrl, throttleMs);
|
|
1266
|
+
try {
|
|
1267
|
+
while (true) {
|
|
1268
|
+
const { done, value } = await reader.read();
|
|
1269
|
+
if (done) {
|
|
1270
|
+
throttle.controller.close();
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
throttle.controller.enqueue(value);
|
|
1274
|
+
}
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
throttle.dispose();
|
|
1277
|
+
reader.releaseLock();
|
|
1278
|
+
controller.error(error);
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
cancel() {
|
|
1282
|
+
closed = true;
|
|
1283
|
+
throttle?.dispose();
|
|
1284
|
+
reader.releaseLock();
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1135
1288
|
function createSSEStreamFromSubscriptions(setup) {
|
|
1136
1289
|
const encoder = createSSEEncoder();
|
|
1137
1290
|
let closed = false;
|
|
@@ -1162,9 +1315,108 @@ function createSSEStreamFromSubscriptions(setup) {
|
|
|
1162
1315
|
}
|
|
1163
1316
|
});
|
|
1164
1317
|
}
|
|
1318
|
+
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1319
|
+
"run:complete",
|
|
1320
|
+
"run:fail",
|
|
1321
|
+
"run:cancel",
|
|
1322
|
+
"run:delete"
|
|
1323
|
+
]);
|
|
1324
|
+
function createThrottledSSEController(inner, throttleMs) {
|
|
1325
|
+
if (throttleMs <= 0) {
|
|
1326
|
+
return { controller: inner, dispose: () => {
|
|
1327
|
+
} };
|
|
1328
|
+
}
|
|
1329
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1330
|
+
const lastSent = /* @__PURE__ */ new Map();
|
|
1331
|
+
const controller = {
|
|
1332
|
+
enqueue(data) {
|
|
1333
|
+
if (inner.closed) return;
|
|
1334
|
+
const event = typeof data === "object" && data !== null ? data : null;
|
|
1335
|
+
if (event?.runId && TERMINAL_EVENT_TYPES.has(event.type ?? "")) {
|
|
1336
|
+
lastSent.delete(event.runId);
|
|
1337
|
+
const entry = pending.get(event.runId);
|
|
1338
|
+
if (entry) {
|
|
1339
|
+
clearTimeout(entry.timer);
|
|
1340
|
+
if (!inner.closed) inner.enqueue(entry.data);
|
|
1341
|
+
pending.delete(event.runId);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (event?.type !== "run:progress" || !event?.runId) {
|
|
1345
|
+
inner.enqueue(data);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const runId = event.runId;
|
|
1349
|
+
const now = Date.now();
|
|
1350
|
+
const last = lastSent.get(runId) ?? 0;
|
|
1351
|
+
if (now - last >= throttleMs) {
|
|
1352
|
+
lastSent.set(runId, now);
|
|
1353
|
+
const entry = pending.get(runId);
|
|
1354
|
+
if (entry) {
|
|
1355
|
+
clearTimeout(entry.timer);
|
|
1356
|
+
pending.delete(runId);
|
|
1357
|
+
}
|
|
1358
|
+
inner.enqueue(data);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
const existing = pending.get(runId);
|
|
1362
|
+
if (existing) {
|
|
1363
|
+
clearTimeout(existing.timer);
|
|
1364
|
+
}
|
|
1365
|
+
const delay = Math.max(0, throttleMs - (now - last));
|
|
1366
|
+
const timer = setTimeout(() => {
|
|
1367
|
+
const current = pending.get(runId);
|
|
1368
|
+
if (!current || current.timer !== timer) return;
|
|
1369
|
+
pending.delete(runId);
|
|
1370
|
+
if (!inner.closed) {
|
|
1371
|
+
lastSent.set(runId, Date.now());
|
|
1372
|
+
inner.enqueue(current.data);
|
|
1373
|
+
}
|
|
1374
|
+
}, delay);
|
|
1375
|
+
pending.set(runId, { data, timer });
|
|
1376
|
+
},
|
|
1377
|
+
close() {
|
|
1378
|
+
for (const [, entry] of pending) {
|
|
1379
|
+
clearTimeout(entry.timer);
|
|
1380
|
+
if (!inner.closed) {
|
|
1381
|
+
inner.enqueue(entry.data);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
pending.clear();
|
|
1385
|
+
lastSent.clear();
|
|
1386
|
+
inner.close();
|
|
1387
|
+
},
|
|
1388
|
+
get closed() {
|
|
1389
|
+
return inner.closed;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
const dispose = () => {
|
|
1393
|
+
for (const [, entry] of pending) {
|
|
1394
|
+
clearTimeout(entry.timer);
|
|
1395
|
+
}
|
|
1396
|
+
pending.clear();
|
|
1397
|
+
lastSent.clear();
|
|
1398
|
+
};
|
|
1399
|
+
return { controller, dispose };
|
|
1400
|
+
}
|
|
1165
1401
|
|
|
1166
1402
|
// src/server.ts
|
|
1403
|
+
function parseLabelsFromParams(searchParams) {
|
|
1404
|
+
const labels = {};
|
|
1405
|
+
for (const [key, value] of searchParams.entries()) {
|
|
1406
|
+
if (key.startsWith("label.")) {
|
|
1407
|
+
labels[key.slice(6)] = value;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return Object.keys(labels).length > 0 ? labels : void 0;
|
|
1411
|
+
}
|
|
1412
|
+
function matchesLabels(eventLabels, filterLabels) {
|
|
1413
|
+
for (const [key, value] of Object.entries(filterLabels)) {
|
|
1414
|
+
if (eventLabels[key] !== value) return false;
|
|
1415
|
+
}
|
|
1416
|
+
return true;
|
|
1417
|
+
}
|
|
1167
1418
|
function createDurablyHandler(durably, options) {
|
|
1419
|
+
const throttleMs = options?.sseThrottleMs ?? 100;
|
|
1168
1420
|
const handler = {
|
|
1169
1421
|
async handle(request, basePath) {
|
|
1170
1422
|
if (options?.onRequest) {
|
|
@@ -1202,7 +1454,8 @@ function createDurablyHandler(durably, options) {
|
|
|
1202
1454
|
}
|
|
1203
1455
|
const run = await job.trigger(body.input ?? {}, {
|
|
1204
1456
|
idempotencyKey: body.idempotencyKey,
|
|
1205
|
-
concurrencyKey: body.concurrencyKey
|
|
1457
|
+
concurrencyKey: body.concurrencyKey,
|
|
1458
|
+
labels: body.labels
|
|
1206
1459
|
});
|
|
1207
1460
|
const response = { runId: run.id };
|
|
1208
1461
|
return jsonResponse(response);
|
|
@@ -1215,8 +1468,9 @@ function createDurablyHandler(durably, options) {
|
|
|
1215
1468
|
const runId = getRequiredQueryParam(url, "runId");
|
|
1216
1469
|
if (runId instanceof Response) return runId;
|
|
1217
1470
|
const stream = durably.subscribe(runId);
|
|
1218
|
-
const sseStream =
|
|
1219
|
-
stream.getReader()
|
|
1471
|
+
const sseStream = createThrottledSSEStreamFromReader(
|
|
1472
|
+
stream.getReader(),
|
|
1473
|
+
throttleMs
|
|
1220
1474
|
);
|
|
1221
1475
|
return createSSEResponse(sseStream);
|
|
1222
1476
|
},
|
|
@@ -1227,13 +1481,15 @@ function createDurablyHandler(durably, options) {
|
|
|
1227
1481
|
const status = url.searchParams.get("status");
|
|
1228
1482
|
const limit = url.searchParams.get("limit");
|
|
1229
1483
|
const offset = url.searchParams.get("offset");
|
|
1484
|
+
const labels = parseLabelsFromParams(url.searchParams);
|
|
1230
1485
|
const runs = await durably.getRuns({
|
|
1231
1486
|
jobName,
|
|
1232
1487
|
status,
|
|
1488
|
+
labels,
|
|
1233
1489
|
limit: limit ? Number.parseInt(limit, 10) : void 0,
|
|
1234
1490
|
offset: offset ? Number.parseInt(offset, 10) : void 0
|
|
1235
1491
|
});
|
|
1236
|
-
return jsonResponse(runs);
|
|
1492
|
+
return jsonResponse(runs.map(toClientRun));
|
|
1237
1493
|
} catch (error) {
|
|
1238
1494
|
return errorResponse(getErrorMessage(error), 500);
|
|
1239
1495
|
}
|
|
@@ -1247,7 +1503,7 @@ function createDurablyHandler(durably, options) {
|
|
|
1247
1503
|
if (!run) {
|
|
1248
1504
|
return errorResponse("Run not found", 404);
|
|
1249
1505
|
}
|
|
1250
|
-
return jsonResponse(run);
|
|
1506
|
+
return jsonResponse(toClientRun(run));
|
|
1251
1507
|
} catch (error) {
|
|
1252
1508
|
return errorResponse(getErrorMessage(error), 500);
|
|
1253
1509
|
}
|
|
@@ -1299,120 +1555,167 @@ function createDurablyHandler(durably, options) {
|
|
|
1299
1555
|
runsSubscribe(request) {
|
|
1300
1556
|
const url = new URL(request.url);
|
|
1301
1557
|
const jobNameFilter = url.searchParams.get("jobName");
|
|
1302
|
-
const
|
|
1558
|
+
const labelsFilter = parseLabelsFromParams(url.searchParams);
|
|
1559
|
+
const matchesFilter = (jobName, labels) => {
|
|
1560
|
+
if (jobNameFilter && jobName !== jobNameFilter) return false;
|
|
1561
|
+
if (labelsFilter && (!labels || !matchesLabels(labels, labelsFilter)))
|
|
1562
|
+
return false;
|
|
1563
|
+
return true;
|
|
1564
|
+
};
|
|
1303
1565
|
const sseStream = createSSEStreamFromSubscriptions(
|
|
1304
|
-
(
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1566
|
+
(innerCtrl) => {
|
|
1567
|
+
const { controller: ctrl, dispose } = createThrottledSSEController(
|
|
1568
|
+
innerCtrl,
|
|
1569
|
+
throttleMs
|
|
1570
|
+
);
|
|
1571
|
+
const unsubscribes = [
|
|
1572
|
+
durably.on("run:trigger", (event) => {
|
|
1573
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1574
|
+
ctrl.enqueue({
|
|
1575
|
+
type: "run:trigger",
|
|
1576
|
+
runId: event.runId,
|
|
1577
|
+
jobName: event.jobName,
|
|
1578
|
+
labels: event.labels
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}),
|
|
1582
|
+
durably.on("run:start", (event) => {
|
|
1583
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1584
|
+
ctrl.enqueue({
|
|
1585
|
+
type: "run:start",
|
|
1586
|
+
runId: event.runId,
|
|
1587
|
+
jobName: event.jobName,
|
|
1588
|
+
labels: event.labels
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
}),
|
|
1592
|
+
durably.on("run:complete", (event) => {
|
|
1593
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1594
|
+
ctrl.enqueue({
|
|
1595
|
+
type: "run:complete",
|
|
1596
|
+
runId: event.runId,
|
|
1597
|
+
jobName: event.jobName,
|
|
1598
|
+
labels: event.labels
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
}),
|
|
1602
|
+
durably.on("run:fail", (event) => {
|
|
1603
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1604
|
+
ctrl.enqueue({
|
|
1605
|
+
type: "run:fail",
|
|
1606
|
+
runId: event.runId,
|
|
1607
|
+
jobName: event.jobName,
|
|
1608
|
+
labels: event.labels
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
}),
|
|
1612
|
+
durably.on("run:cancel", (event) => {
|
|
1613
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1614
|
+
ctrl.enqueue({
|
|
1615
|
+
type: "run:cancel",
|
|
1616
|
+
runId: event.runId,
|
|
1617
|
+
jobName: event.jobName,
|
|
1618
|
+
labels: event.labels
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
}),
|
|
1622
|
+
durably.on("run:delete", (event) => {
|
|
1623
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1624
|
+
ctrl.enqueue({
|
|
1625
|
+
type: "run:delete",
|
|
1626
|
+
runId: event.runId,
|
|
1627
|
+
jobName: event.jobName,
|
|
1628
|
+
labels: event.labels
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
}),
|
|
1632
|
+
durably.on("run:retry", (event) => {
|
|
1633
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1634
|
+
ctrl.enqueue({
|
|
1635
|
+
type: "run:retry",
|
|
1636
|
+
runId: event.runId,
|
|
1637
|
+
jobName: event.jobName,
|
|
1638
|
+
labels: event.labels
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
}),
|
|
1642
|
+
durably.on("run:progress", (event) => {
|
|
1643
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1644
|
+
ctrl.enqueue({
|
|
1645
|
+
type: "run:progress",
|
|
1646
|
+
runId: event.runId,
|
|
1647
|
+
jobName: event.jobName,
|
|
1648
|
+
progress: event.progress,
|
|
1649
|
+
labels: event.labels
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
}),
|
|
1653
|
+
durably.on("step:start", (event) => {
|
|
1654
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1655
|
+
ctrl.enqueue({
|
|
1656
|
+
type: "step:start",
|
|
1657
|
+
runId: event.runId,
|
|
1658
|
+
jobName: event.jobName,
|
|
1659
|
+
stepName: event.stepName,
|
|
1660
|
+
stepIndex: event.stepIndex,
|
|
1661
|
+
labels: event.labels
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
}),
|
|
1665
|
+
durably.on("step:complete", (event) => {
|
|
1666
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1667
|
+
ctrl.enqueue({
|
|
1668
|
+
type: "step:complete",
|
|
1669
|
+
runId: event.runId,
|
|
1670
|
+
jobName: event.jobName,
|
|
1671
|
+
stepName: event.stepName,
|
|
1672
|
+
stepIndex: event.stepIndex,
|
|
1673
|
+
labels: event.labels
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
}),
|
|
1677
|
+
durably.on("step:fail", (event) => {
|
|
1678
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1679
|
+
ctrl.enqueue({
|
|
1680
|
+
type: "step:fail",
|
|
1681
|
+
runId: event.runId,
|
|
1682
|
+
jobName: event.jobName,
|
|
1683
|
+
stepName: event.stepName,
|
|
1684
|
+
stepIndex: event.stepIndex,
|
|
1685
|
+
error: event.error,
|
|
1686
|
+
labels: event.labels
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}),
|
|
1690
|
+
durably.on("step:cancel", (event) => {
|
|
1691
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1692
|
+
ctrl.enqueue({
|
|
1693
|
+
type: "step:cancel",
|
|
1694
|
+
runId: event.runId,
|
|
1695
|
+
jobName: event.jobName,
|
|
1696
|
+
stepName: event.stepName,
|
|
1697
|
+
stepIndex: event.stepIndex,
|
|
1698
|
+
labels: event.labels
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
}),
|
|
1702
|
+
durably.on("log:write", (event) => {
|
|
1703
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1704
|
+
ctrl.enqueue({
|
|
1705
|
+
type: "log:write",
|
|
1706
|
+
runId: event.runId,
|
|
1707
|
+
jobName: event.jobName,
|
|
1708
|
+
labels: event.labels,
|
|
1709
|
+
stepName: event.stepName,
|
|
1710
|
+
level: event.level,
|
|
1711
|
+
message: event.message,
|
|
1712
|
+
data: event.data
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
})
|
|
1716
|
+
];
|
|
1717
|
+
return [...unsubscribes, dispose];
|
|
1718
|
+
}
|
|
1416
1719
|
);
|
|
1417
1720
|
return createSSEResponse(sseStream);
|
|
1418
1721
|
}
|
|
@@ -1424,6 +1727,7 @@ export {
|
|
|
1424
1727
|
createDurably,
|
|
1425
1728
|
createDurablyHandler,
|
|
1426
1729
|
defineJob,
|
|
1730
|
+
toClientRun,
|
|
1427
1731
|
withLogPersistence
|
|
1428
1732
|
};
|
|
1429
1733
|
//# sourceMappingURL=index.js.map
|