@coji/durably 0.10.0 → 0.12.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 +2 -2
- package/dist/{index-BjlCb0gP.d.ts → index-hM7-oiyj.d.ts} +88 -63
- package/dist/index.d.ts +46 -96
- package/dist/index.js +516 -415
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/docs/llms.md +179 -61
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -53,6 +53,8 @@ function createEventEmitter() {
|
|
|
53
53
|
|
|
54
54
|
// src/job.ts
|
|
55
55
|
import { prettifyError } from "zod";
|
|
56
|
+
var noop = () => {
|
|
57
|
+
};
|
|
56
58
|
function validateJobInputOrThrow(schema, input, context) {
|
|
57
59
|
const result = schema.safeParse(input);
|
|
58
60
|
if (!result.success) {
|
|
@@ -75,7 +77,7 @@ function createJobRegistry() {
|
|
|
75
77
|
}
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
|
-
function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
80
|
+
function createJobHandle(jobDef, storage, eventEmitter, registry, labelsSchema) {
|
|
79
81
|
const existingJob = registry.get(jobDef.name);
|
|
80
82
|
if (existingJob) {
|
|
81
83
|
if (existingJob.jobDef === jobDef) {
|
|
@@ -91,6 +93,9 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
91
93
|
name: jobDef.name,
|
|
92
94
|
async trigger(input, options) {
|
|
93
95
|
const validatedInput = validateJobInputOrThrow(inputSchema, input);
|
|
96
|
+
if (labelsSchema && options?.labels) {
|
|
97
|
+
validateJobInputOrThrow(labelsSchema, options.labels, "labels");
|
|
98
|
+
}
|
|
94
99
|
const run = await storage.createRun({
|
|
95
100
|
jobName: jobDef.name,
|
|
96
101
|
input: validatedInput,
|
|
@@ -112,30 +117,57 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
112
117
|
return new Promise((resolve, reject) => {
|
|
113
118
|
let timeoutId;
|
|
114
119
|
let resolved = false;
|
|
120
|
+
const unsubscribes = [];
|
|
115
121
|
const cleanup = () => {
|
|
116
122
|
if (resolved) return;
|
|
117
123
|
resolved = true;
|
|
118
|
-
|
|
119
|
-
unsubscribeFail();
|
|
124
|
+
for (const unsub of unsubscribes) unsub();
|
|
120
125
|
if (timeoutId) {
|
|
121
126
|
clearTimeout(timeoutId);
|
|
122
127
|
}
|
|
123
128
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
unsubscribes.push(
|
|
130
|
+
eventEmitter.on("run:complete", (event) => {
|
|
131
|
+
if (event.runId === run.id && !resolved) {
|
|
132
|
+
cleanup();
|
|
133
|
+
resolve({
|
|
134
|
+
id: run.id,
|
|
135
|
+
output: event.output
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
unsubscribes.push(
|
|
141
|
+
eventEmitter.on("run:fail", (event) => {
|
|
142
|
+
if (event.runId === run.id && !resolved) {
|
|
143
|
+
cleanup();
|
|
144
|
+
reject(new Error(event.error));
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
if (options?.onProgress) {
|
|
149
|
+
const onProgress = options.onProgress;
|
|
150
|
+
unsubscribes.push(
|
|
151
|
+
eventEmitter.on("run:progress", (event) => {
|
|
152
|
+
if (event.runId === run.id && !resolved) {
|
|
153
|
+
void Promise.resolve(onProgress(event.progress)).catch(noop);
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (options?.onLog) {
|
|
159
|
+
const onLog = options.onLog;
|
|
160
|
+
unsubscribes.push(
|
|
161
|
+
eventEmitter.on("log:write", (event) => {
|
|
162
|
+
if (event.runId === run.id && !resolved) {
|
|
163
|
+
const { level, message, data, stepName } = event;
|
|
164
|
+
void Promise.resolve(
|
|
165
|
+
onLog({ level, message, data, stepName })
|
|
166
|
+
).catch(noop);
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
}
|
|
139
171
|
storage.getRun(run.id).then((currentRun) => {
|
|
140
172
|
if (resolved || !currentRun) return;
|
|
141
173
|
if (currentRun.status === "completed") {
|
|
@@ -148,6 +180,10 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
148
180
|
cleanup();
|
|
149
181
|
reject(new Error(currentRun.error || "Run failed"));
|
|
150
182
|
}
|
|
183
|
+
}).catch((error) => {
|
|
184
|
+
if (resolved) return;
|
|
185
|
+
cleanup();
|
|
186
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
151
187
|
});
|
|
152
188
|
if (options?.timeout !== void 0) {
|
|
153
189
|
timeoutId = setTimeout(() => {
|
|
@@ -178,6 +214,13 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
178
214
|
normalized[i].input,
|
|
179
215
|
`at index ${i}`
|
|
180
216
|
);
|
|
217
|
+
if (labelsSchema && normalized[i].options?.labels) {
|
|
218
|
+
validateJobInputOrThrow(
|
|
219
|
+
labelsSchema,
|
|
220
|
+
normalized[i].options?.labels,
|
|
221
|
+
`labels at index ${i}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
181
224
|
validated.push({
|
|
182
225
|
input: validatedInput,
|
|
183
226
|
options: normalized[i].options
|
|
@@ -222,6 +265,7 @@ function createJobHandle(jobDef, storage, eventEmitter, registry) {
|
|
|
222
265
|
name: jobDef.name,
|
|
223
266
|
inputSchema,
|
|
224
267
|
outputSchema,
|
|
268
|
+
labelsSchema,
|
|
225
269
|
fn: jobDef.run,
|
|
226
270
|
jobDef,
|
|
227
271
|
handle
|
|
@@ -460,11 +504,19 @@ function createKyselyStorage(db) {
|
|
|
460
504
|
query = query.where("durably_runs.status", "=", filter.status);
|
|
461
505
|
}
|
|
462
506
|
if (filter?.jobName) {
|
|
463
|
-
|
|
507
|
+
if (Array.isArray(filter.jobName)) {
|
|
508
|
+
if (filter.jobName.length > 0) {
|
|
509
|
+
query = query.where("durably_runs.job_name", "in", filter.jobName);
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
query = query.where("durably_runs.job_name", "=", filter.jobName);
|
|
513
|
+
}
|
|
464
514
|
}
|
|
465
515
|
if (filter?.labels) {
|
|
466
|
-
|
|
467
|
-
|
|
516
|
+
const labels = filter.labels;
|
|
517
|
+
validateLabels(labels);
|
|
518
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
519
|
+
if (value === void 0) continue;
|
|
468
520
|
query = query.where(
|
|
469
521
|
sql`json_extract(durably_runs.labels, ${`$."${key}"`})`,
|
|
470
522
|
"=",
|
|
@@ -526,6 +578,9 @@ function createKyselyStorage(db) {
|
|
|
526
578
|
await db.insertInto("durably_steps").values(step).execute();
|
|
527
579
|
return rowToStep(step);
|
|
528
580
|
},
|
|
581
|
+
async deleteSteps(runId) {
|
|
582
|
+
await db.deleteFrom("durably_steps").where("run_id", "=", runId).execute();
|
|
583
|
+
},
|
|
529
584
|
async getSteps(runId) {
|
|
530
585
|
const rows = await db.selectFrom("durably_steps").selectAll().where("run_id", "=", runId).orderBy("index", "asc").execute();
|
|
531
586
|
return rows.map(rowToStep);
|
|
@@ -825,6 +880,12 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
825
880
|
} catch (error) {
|
|
826
881
|
await handleRunFailure(run.id, run.jobName, error);
|
|
827
882
|
} finally {
|
|
883
|
+
if (config.cleanupSteps) {
|
|
884
|
+
try {
|
|
885
|
+
await storage.deleteSteps(run.id);
|
|
886
|
+
} catch {
|
|
887
|
+
}
|
|
888
|
+
}
|
|
828
889
|
dispose();
|
|
829
890
|
if (heartbeatInterval) {
|
|
830
891
|
clearInterval(heartbeatInterval);
|
|
@@ -911,10 +972,18 @@ function createWorker(config, storage, eventEmitter, jobRegistry) {
|
|
|
911
972
|
var DEFAULTS = {
|
|
912
973
|
pollingInterval: 1e3,
|
|
913
974
|
heartbeatInterval: 5e3,
|
|
914
|
-
staleThreshold: 3e4
|
|
975
|
+
staleThreshold: 3e4,
|
|
976
|
+
cleanupSteps: true
|
|
915
977
|
};
|
|
916
978
|
function createDurablyInstance(state, jobs) {
|
|
917
979
|
const { db, storage, eventEmitter, jobRegistry, worker } = state;
|
|
980
|
+
async function getRunOrThrow(runId) {
|
|
981
|
+
const run = await storage.getRun(runId);
|
|
982
|
+
if (!run) {
|
|
983
|
+
throw new Error(`Run not found: ${runId}`);
|
|
984
|
+
}
|
|
985
|
+
return run;
|
|
986
|
+
}
|
|
918
987
|
const durably = {
|
|
919
988
|
db,
|
|
920
989
|
storage,
|
|
@@ -933,12 +1002,16 @@ function createDurablyInstance(state, jobs) {
|
|
|
933
1002
|
jobDef,
|
|
934
1003
|
storage,
|
|
935
1004
|
eventEmitter,
|
|
936
|
-
jobRegistry
|
|
1005
|
+
jobRegistry,
|
|
1006
|
+
state.labelsSchema
|
|
937
1007
|
);
|
|
938
1008
|
newHandles[key] = handle;
|
|
939
1009
|
}
|
|
940
1010
|
const mergedJobs = { ...jobs, ...newHandles };
|
|
941
|
-
return createDurablyInstance(
|
|
1011
|
+
return createDurablyInstance(
|
|
1012
|
+
state,
|
|
1013
|
+
mergedJobs
|
|
1014
|
+
);
|
|
942
1015
|
},
|
|
943
1016
|
getRun: storage.getRun,
|
|
944
1017
|
getRuns: storage.getRuns,
|
|
@@ -955,93 +1028,34 @@ function createDurablyInstance(state, jobs) {
|
|
|
955
1028
|
subscribe(runId) {
|
|
956
1029
|
let closed = false;
|
|
957
1030
|
let cleanup = null;
|
|
1031
|
+
const closeEvents = /* @__PURE__ */ new Set(["run:complete", "run:delete"]);
|
|
1032
|
+
const subscribedEvents = [
|
|
1033
|
+
"run:start",
|
|
1034
|
+
"run:complete",
|
|
1035
|
+
"run:fail",
|
|
1036
|
+
"run:cancel",
|
|
1037
|
+
"run:delete",
|
|
1038
|
+
"run:progress",
|
|
1039
|
+
"step:start",
|
|
1040
|
+
"step:complete",
|
|
1041
|
+
"step:fail",
|
|
1042
|
+
"log:write"
|
|
1043
|
+
];
|
|
958
1044
|
return new ReadableStream({
|
|
959
1045
|
start: (controller) => {
|
|
960
|
-
const
|
|
961
|
-
|
|
1046
|
+
const unsubscribes = subscribedEvents.map(
|
|
1047
|
+
(type) => eventEmitter.on(type, (event) => {
|
|
1048
|
+
if (closed || event.runId !== runId) return;
|
|
962
1049
|
controller.enqueue(event);
|
|
963
|
-
|
|
964
|
-
});
|
|
965
|
-
const unsubscribeComplete = eventEmitter.on(
|
|
966
|
-
"run:complete",
|
|
967
|
-
(event) => {
|
|
968
|
-
if (!closed && event.runId === runId) {
|
|
969
|
-
controller.enqueue(event);
|
|
1050
|
+
if (closeEvents.has(type)) {
|
|
970
1051
|
closed = true;
|
|
971
1052
|
cleanup?.();
|
|
972
1053
|
controller.close();
|
|
973
1054
|
}
|
|
974
|
-
}
|
|
975
|
-
);
|
|
976
|
-
const unsubscribeFail = eventEmitter.on("run:fail", (event) => {
|
|
977
|
-
if (!closed && event.runId === runId) {
|
|
978
|
-
controller.enqueue(event);
|
|
979
|
-
}
|
|
980
|
-
});
|
|
981
|
-
const unsubscribeCancel = eventEmitter.on("run:cancel", (event) => {
|
|
982
|
-
if (!closed && event.runId === runId) {
|
|
983
|
-
controller.enqueue(event);
|
|
984
|
-
}
|
|
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
|
-
});
|
|
994
|
-
const unsubscribeRetry = eventEmitter.on("run:retry", (event) => {
|
|
995
|
-
if (!closed && event.runId === runId) {
|
|
996
|
-
controller.enqueue(event);
|
|
997
|
-
}
|
|
998
|
-
});
|
|
999
|
-
const unsubscribeProgress = eventEmitter.on(
|
|
1000
|
-
"run:progress",
|
|
1001
|
-
(event) => {
|
|
1002
|
-
if (!closed && event.runId === runId) {
|
|
1003
|
-
controller.enqueue(event);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
);
|
|
1007
|
-
const unsubscribeStepStart = eventEmitter.on(
|
|
1008
|
-
"step:start",
|
|
1009
|
-
(event) => {
|
|
1010
|
-
if (!closed && event.runId === runId) {
|
|
1011
|
-
controller.enqueue(event);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
);
|
|
1015
|
-
const unsubscribeStepComplete = eventEmitter.on(
|
|
1016
|
-
"step:complete",
|
|
1017
|
-
(event) => {
|
|
1018
|
-
if (!closed && event.runId === runId) {
|
|
1019
|
-
controller.enqueue(event);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1055
|
+
})
|
|
1022
1056
|
);
|
|
1023
|
-
const unsubscribeStepFail = eventEmitter.on("step:fail", (event) => {
|
|
1024
|
-
if (!closed && event.runId === runId) {
|
|
1025
|
-
controller.enqueue(event);
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1028
|
-
const unsubscribeLog = eventEmitter.on("log:write", (event) => {
|
|
1029
|
-
if (!closed && event.runId === runId) {
|
|
1030
|
-
controller.enqueue(event);
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
1057
|
cleanup = () => {
|
|
1034
|
-
|
|
1035
|
-
unsubscribeComplete();
|
|
1036
|
-
unsubscribeFail();
|
|
1037
|
-
unsubscribeCancel();
|
|
1038
|
-
unsubscribeDelete();
|
|
1039
|
-
unsubscribeRetry();
|
|
1040
|
-
unsubscribeProgress();
|
|
1041
|
-
unsubscribeStepStart();
|
|
1042
|
-
unsubscribeStepComplete();
|
|
1043
|
-
unsubscribeStepFail();
|
|
1044
|
-
unsubscribeLog();
|
|
1058
|
+
for (const unsub of unsubscribes) unsub();
|
|
1045
1059
|
};
|
|
1046
1060
|
},
|
|
1047
1061
|
cancel: () => {
|
|
@@ -1052,36 +1066,34 @@ function createDurablyInstance(state, jobs) {
|
|
|
1052
1066
|
}
|
|
1053
1067
|
});
|
|
1054
1068
|
},
|
|
1055
|
-
async
|
|
1056
|
-
const run = await
|
|
1057
|
-
if (!run) {
|
|
1058
|
-
throw new Error(`Run not found: ${runId}`);
|
|
1059
|
-
}
|
|
1060
|
-
if (run.status === "completed") {
|
|
1061
|
-
throw new Error(`Cannot retry completed run: ${runId}`);
|
|
1062
|
-
}
|
|
1069
|
+
async retrigger(runId) {
|
|
1070
|
+
const run = await getRunOrThrow(runId);
|
|
1063
1071
|
if (run.status === "pending") {
|
|
1064
|
-
throw new Error(`Cannot
|
|
1072
|
+
throw new Error(`Cannot retrigger pending run: ${runId}`);
|
|
1065
1073
|
}
|
|
1066
1074
|
if (run.status === "running") {
|
|
1067
|
-
throw new Error(`Cannot
|
|
1075
|
+
throw new Error(`Cannot retrigger running run: ${runId}`);
|
|
1068
1076
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1077
|
+
if (!jobRegistry.get(run.jobName)) {
|
|
1078
|
+
throw new Error(`Unknown job: ${run.jobName}`);
|
|
1079
|
+
}
|
|
1080
|
+
const nextRun = await storage.createRun({
|
|
1081
|
+
jobName: run.jobName,
|
|
1082
|
+
input: run.input,
|
|
1083
|
+
concurrencyKey: run.concurrencyKey ?? void 0,
|
|
1084
|
+
labels: run.labels
|
|
1072
1085
|
});
|
|
1073
1086
|
eventEmitter.emit({
|
|
1074
|
-
type: "run:
|
|
1075
|
-
runId,
|
|
1087
|
+
type: "run:trigger",
|
|
1088
|
+
runId: nextRun.id,
|
|
1076
1089
|
jobName: run.jobName,
|
|
1090
|
+
input: run.input,
|
|
1077
1091
|
labels: run.labels
|
|
1078
1092
|
});
|
|
1093
|
+
return nextRun;
|
|
1079
1094
|
},
|
|
1080
1095
|
async cancel(runId) {
|
|
1081
|
-
const run = await
|
|
1082
|
-
if (!run) {
|
|
1083
|
-
throw new Error(`Run not found: ${runId}`);
|
|
1084
|
-
}
|
|
1096
|
+
const run = await getRunOrThrow(runId);
|
|
1085
1097
|
if (run.status === "completed") {
|
|
1086
1098
|
throw new Error(`Cannot cancel completed run: ${runId}`);
|
|
1087
1099
|
}
|
|
@@ -1091,9 +1103,14 @@ function createDurablyInstance(state, jobs) {
|
|
|
1091
1103
|
if (run.status === "cancelled") {
|
|
1092
1104
|
throw new Error(`Cannot cancel already cancelled run: ${runId}`);
|
|
1093
1105
|
}
|
|
1106
|
+
const wasPending = run.status === "pending";
|
|
1094
1107
|
await storage.updateRun(runId, {
|
|
1095
|
-
status: "cancelled"
|
|
1108
|
+
status: "cancelled",
|
|
1109
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1096
1110
|
});
|
|
1111
|
+
if (wasPending && state.cleanupSteps) {
|
|
1112
|
+
await storage.deleteSteps(runId);
|
|
1113
|
+
}
|
|
1097
1114
|
eventEmitter.emit({
|
|
1098
1115
|
type: "run:cancel",
|
|
1099
1116
|
runId,
|
|
@@ -1102,10 +1119,7 @@ function createDurablyInstance(state, jobs) {
|
|
|
1102
1119
|
});
|
|
1103
1120
|
},
|
|
1104
1121
|
async deleteRun(runId) {
|
|
1105
|
-
const run = await
|
|
1106
|
-
if (!run) {
|
|
1107
|
-
throw new Error(`Run not found: ${runId}`);
|
|
1108
|
-
}
|
|
1122
|
+
const run = await getRunOrThrow(runId);
|
|
1109
1123
|
if (run.status === "pending") {
|
|
1110
1124
|
throw new Error(`Cannot delete pending run: ${runId}`);
|
|
1111
1125
|
}
|
|
@@ -1145,7 +1159,8 @@ function createDurably(options) {
|
|
|
1145
1159
|
const config = {
|
|
1146
1160
|
pollingInterval: options.pollingInterval ?? DEFAULTS.pollingInterval,
|
|
1147
1161
|
heartbeatInterval: options.heartbeatInterval ?? DEFAULTS.heartbeatInterval,
|
|
1148
|
-
staleThreshold: options.staleThreshold ?? DEFAULTS.staleThreshold
|
|
1162
|
+
staleThreshold: options.staleThreshold ?? DEFAULTS.staleThreshold,
|
|
1163
|
+
cleanupSteps: options.cleanupSteps ?? DEFAULTS.cleanupSteps
|
|
1149
1164
|
};
|
|
1150
1165
|
const db = new Kysely({ dialect: options.dialect });
|
|
1151
1166
|
const storage = createKyselyStorage(db);
|
|
@@ -1158,10 +1173,19 @@ function createDurably(options) {
|
|
|
1158
1173
|
eventEmitter,
|
|
1159
1174
|
jobRegistry,
|
|
1160
1175
|
worker,
|
|
1176
|
+
labelsSchema: options.labels,
|
|
1177
|
+
cleanupSteps: config.cleanupSteps,
|
|
1161
1178
|
migrating: null,
|
|
1162
1179
|
migrated: false
|
|
1163
1180
|
};
|
|
1164
|
-
|
|
1181
|
+
const instance = createDurablyInstance(
|
|
1182
|
+
state,
|
|
1183
|
+
{}
|
|
1184
|
+
);
|
|
1185
|
+
if (options.jobs) {
|
|
1186
|
+
return instance.register(options.jobs);
|
|
1187
|
+
}
|
|
1188
|
+
return instance;
|
|
1165
1189
|
}
|
|
1166
1190
|
|
|
1167
1191
|
// src/define-job.ts
|
|
@@ -1400,6 +1424,14 @@ function createThrottledSSEController(inner, throttleMs) {
|
|
|
1400
1424
|
}
|
|
1401
1425
|
|
|
1402
1426
|
// src/server.ts
|
|
1427
|
+
var VALID_STATUSES = [
|
|
1428
|
+
"pending",
|
|
1429
|
+
"running",
|
|
1430
|
+
"completed",
|
|
1431
|
+
"failed",
|
|
1432
|
+
"cancelled"
|
|
1433
|
+
];
|
|
1434
|
+
var VALID_STATUSES_SET = new Set(VALID_STATUSES);
|
|
1403
1435
|
function parseLabelsFromParams(searchParams) {
|
|
1404
1436
|
const labels = {};
|
|
1405
1437
|
for (const [key, value] of searchParams.entries()) {
|
|
@@ -1409,6 +1441,51 @@ function parseLabelsFromParams(searchParams) {
|
|
|
1409
1441
|
}
|
|
1410
1442
|
return Object.keys(labels).length > 0 ? labels : void 0;
|
|
1411
1443
|
}
|
|
1444
|
+
function parseRunFilter(url) {
|
|
1445
|
+
const jobNames = url.searchParams.getAll("jobName");
|
|
1446
|
+
const statusParam = url.searchParams.get("status");
|
|
1447
|
+
const limitParam = url.searchParams.get("limit");
|
|
1448
|
+
const offsetParam = url.searchParams.get("offset");
|
|
1449
|
+
const labels = parseLabelsFromParams(url.searchParams);
|
|
1450
|
+
if (statusParam && !VALID_STATUSES_SET.has(statusParam)) {
|
|
1451
|
+
return errorResponse(
|
|
1452
|
+
`Invalid status: ${statusParam}. Must be one of: ${VALID_STATUSES.join(", ")}`,
|
|
1453
|
+
400
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
let limit;
|
|
1457
|
+
if (limitParam) {
|
|
1458
|
+
limit = Number.parseInt(limitParam, 10);
|
|
1459
|
+
if (Number.isNaN(limit) || limit < 0) {
|
|
1460
|
+
return errorResponse("Invalid limit: must be a non-negative integer", 400);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
let offset;
|
|
1464
|
+
if (offsetParam) {
|
|
1465
|
+
offset = Number.parseInt(offsetParam, 10);
|
|
1466
|
+
if (Number.isNaN(offset) || offset < 0) {
|
|
1467
|
+
return errorResponse(
|
|
1468
|
+
"Invalid offset: must be a non-negative integer",
|
|
1469
|
+
400
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
jobName: jobNames.length > 0 ? jobNames : void 0,
|
|
1475
|
+
status: statusParam,
|
|
1476
|
+
labels,
|
|
1477
|
+
limit,
|
|
1478
|
+
offset
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
function parseRunsSubscribeFilter(url) {
|
|
1482
|
+
const jobNames = url.searchParams.getAll("jobName");
|
|
1483
|
+
const labels = parseLabelsFromParams(url.searchParams);
|
|
1484
|
+
return {
|
|
1485
|
+
jobName: jobNames.length > 0 ? jobNames : void 0,
|
|
1486
|
+
labels
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1412
1489
|
function matchesLabels(eventLabels, filterLabels) {
|
|
1413
1490
|
for (const [key, value] of Object.entries(filterLabels)) {
|
|
1414
1491
|
if (eventLabels[key] !== value) return false;
|
|
@@ -1417,310 +1494,334 @@ function matchesLabels(eventLabels, filterLabels) {
|
|
|
1417
1494
|
}
|
|
1418
1495
|
function createDurablyHandler(durably, options) {
|
|
1419
1496
|
const throttleMs = options?.sseThrottleMs ?? 100;
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1497
|
+
const auth = options?.auth;
|
|
1498
|
+
if (auth && !auth.authenticate) {
|
|
1499
|
+
throw new Error(
|
|
1500
|
+
"createDurablyHandler: auth.authenticate is required when auth is provided"
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
async function withErrorHandling(fn) {
|
|
1504
|
+
try {
|
|
1505
|
+
return await fn();
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
if (error instanceof Response) throw error;
|
|
1508
|
+
return errorResponse(getErrorMessage(error), 500);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async function requireRunAccess(url, ctx, operation) {
|
|
1512
|
+
const runId = getRequiredQueryParam(url, "runId");
|
|
1513
|
+
if (runId instanceof Response) return runId;
|
|
1514
|
+
const run = await durably.getRun(runId);
|
|
1515
|
+
if (!run) return errorResponse("Run not found", 404);
|
|
1516
|
+
if (auth?.onRunAccess && ctx !== void 0) {
|
|
1517
|
+
await auth.onRunAccess(ctx, run, {
|
|
1518
|
+
operation
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
return { run, runId };
|
|
1522
|
+
}
|
|
1523
|
+
async function handleTrigger(request, ctx) {
|
|
1524
|
+
return withErrorHandling(async () => {
|
|
1525
|
+
const body = await request.json();
|
|
1526
|
+
if (!body.jobName) {
|
|
1527
|
+
return errorResponse("jobName is required", 400);
|
|
1434
1528
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
if (path === "/cancel") return handler.cancel(request);
|
|
1529
|
+
const job = durably.getJob(body.jobName);
|
|
1530
|
+
if (!job) {
|
|
1531
|
+
return errorResponse(`Job not found: ${body.jobName}`, 404);
|
|
1439
1532
|
}
|
|
1440
|
-
if (
|
|
1441
|
-
|
|
1533
|
+
if (auth?.onTrigger && ctx !== void 0) {
|
|
1534
|
+
await auth.onTrigger(ctx, body);
|
|
1442
1535
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
try {
|
|
1447
|
-
const body = await request.json();
|
|
1448
|
-
if (!body.jobName) {
|
|
1449
|
-
return errorResponse("jobName is required", 400);
|
|
1450
|
-
}
|
|
1451
|
-
const job = durably.getJob(body.jobName);
|
|
1452
|
-
if (!job) {
|
|
1453
|
-
return errorResponse(`Job not found: ${body.jobName}`, 404);
|
|
1454
|
-
}
|
|
1455
|
-
const run = await job.trigger(body.input ?? {}, {
|
|
1536
|
+
const run = await job.trigger(
|
|
1537
|
+
body.input ?? {},
|
|
1538
|
+
{
|
|
1456
1539
|
idempotencyKey: body.idempotencyKey,
|
|
1457
1540
|
concurrencyKey: body.concurrencyKey,
|
|
1458
1541
|
labels: body.labels
|
|
1459
|
-
}
|
|
1460
|
-
const response = { runId: run.id };
|
|
1461
|
-
return jsonResponse(response);
|
|
1462
|
-
} catch (error) {
|
|
1463
|
-
return errorResponse(getErrorMessage(error), 500);
|
|
1464
|
-
}
|
|
1465
|
-
},
|
|
1466
|
-
subscribe(request) {
|
|
1467
|
-
const url = new URL(request.url);
|
|
1468
|
-
const runId = getRequiredQueryParam(url, "runId");
|
|
1469
|
-
if (runId instanceof Response) return runId;
|
|
1470
|
-
const stream = durably.subscribe(runId);
|
|
1471
|
-
const sseStream = createThrottledSSEStreamFromReader(
|
|
1472
|
-
stream.getReader(),
|
|
1473
|
-
throttleMs
|
|
1542
|
+
}
|
|
1474
1543
|
);
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1544
|
+
const response = { runId: run.id };
|
|
1545
|
+
return jsonResponse(response);
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
async function handleSubscribe(url, ctx) {
|
|
1549
|
+
const result = await requireRunAccess(url, ctx, "subscribe");
|
|
1550
|
+
if (result instanceof Response) return result;
|
|
1551
|
+
const stream = durably.subscribe(result.runId);
|
|
1552
|
+
const sseStream = createThrottledSSEStreamFromReader(
|
|
1553
|
+
stream.getReader(),
|
|
1554
|
+
throttleMs
|
|
1555
|
+
);
|
|
1556
|
+
return createSSEResponse(sseStream);
|
|
1557
|
+
}
|
|
1558
|
+
async function handleRuns(url, ctx) {
|
|
1559
|
+
return withErrorHandling(async () => {
|
|
1560
|
+
const filterOrError = parseRunFilter(url);
|
|
1561
|
+
if (filterOrError instanceof Response) return filterOrError;
|
|
1562
|
+
let filter = filterOrError;
|
|
1563
|
+
if (auth?.scopeRuns && ctx !== void 0) {
|
|
1564
|
+
filter = await auth.scopeRuns(ctx, filter);
|
|
1495
1565
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1566
|
+
const runs = await durably.getRuns(filter);
|
|
1567
|
+
return jsonResponse(runs.map(toClientRun));
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
async function handleRun(url, ctx) {
|
|
1571
|
+
return withErrorHandling(async () => {
|
|
1572
|
+
const result = await requireRunAccess(url, ctx, "read");
|
|
1573
|
+
if (result instanceof Response) return result;
|
|
1574
|
+
return jsonResponse(toClientRun(result.run));
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
async function handleSteps(url, ctx) {
|
|
1578
|
+
return withErrorHandling(async () => {
|
|
1579
|
+
const result = await requireRunAccess(url, ctx, "steps");
|
|
1580
|
+
if (result instanceof Response) return result;
|
|
1581
|
+
const steps = await durably.storage.getSteps(result.runId);
|
|
1582
|
+
return jsonResponse(steps);
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
async function handleRetrigger(url, ctx) {
|
|
1586
|
+
return withErrorHandling(async () => {
|
|
1587
|
+
const result = await requireRunAccess(url, ctx, "retrigger");
|
|
1588
|
+
if (result instanceof Response) return result;
|
|
1589
|
+
const run = await durably.retrigger(result.runId);
|
|
1590
|
+
return jsonResponse({ success: true, runId: run.id });
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
async function handleCancel(url, ctx) {
|
|
1594
|
+
return withErrorHandling(async () => {
|
|
1595
|
+
const result = await requireRunAccess(url, ctx, "cancel");
|
|
1596
|
+
if (result instanceof Response) return result;
|
|
1597
|
+
await durably.cancel(result.runId);
|
|
1598
|
+
return successResponse();
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
async function handleDelete(url, ctx) {
|
|
1602
|
+
return withErrorHandling(async () => {
|
|
1603
|
+
const result = await requireRunAccess(url, ctx, "delete");
|
|
1604
|
+
if (result instanceof Response) return result;
|
|
1605
|
+
await durably.deleteRun(result.runId);
|
|
1606
|
+
return successResponse();
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
async function handleRunsSubscribe(url, ctx) {
|
|
1610
|
+
let filter;
|
|
1611
|
+
if (ctx !== void 0 && auth?.scopeRunsSubscribe) {
|
|
1612
|
+
const parsed = parseRunsSubscribeFilter(
|
|
1613
|
+
url
|
|
1614
|
+
);
|
|
1615
|
+
filter = await auth.scopeRunsSubscribe(ctx, parsed);
|
|
1616
|
+
} else if (ctx !== void 0 && auth?.scopeRuns) {
|
|
1617
|
+
const parsed = parseRunsSubscribeFilter(
|
|
1618
|
+
url
|
|
1619
|
+
);
|
|
1620
|
+
const scoped = await auth.scopeRuns(
|
|
1621
|
+
ctx,
|
|
1622
|
+
{
|
|
1623
|
+
...parsed
|
|
1505
1624
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1625
|
+
);
|
|
1626
|
+
filter = { jobName: scoped.jobName, labels: scoped.labels };
|
|
1627
|
+
} else {
|
|
1628
|
+
filter = parseRunsSubscribeFilter(url);
|
|
1629
|
+
}
|
|
1630
|
+
return createRunsSSEStream(filter);
|
|
1631
|
+
}
|
|
1632
|
+
function createRunsSSEStream(filter) {
|
|
1633
|
+
const jobNameFilter = Array.isArray(filter.jobName) ? filter.jobName : filter.jobName ? [filter.jobName] : [];
|
|
1634
|
+
const labelsFilter = filter.labels;
|
|
1635
|
+
const matchesFilter = (jobName, labels) => {
|
|
1636
|
+
if (jobNameFilter.length > 0 && !jobNameFilter.includes(jobName))
|
|
1637
|
+
return false;
|
|
1638
|
+
if (labelsFilter && (!labels || !matchesLabels(labels, labelsFilter)))
|
|
1639
|
+
return false;
|
|
1640
|
+
return true;
|
|
1641
|
+
};
|
|
1642
|
+
const sseStream = createSSEStreamFromSubscriptions(
|
|
1643
|
+
(innerCtrl) => {
|
|
1644
|
+
const { controller: ctrl, dispose } = createThrottledSSEController(
|
|
1645
|
+
innerCtrl,
|
|
1646
|
+
throttleMs
|
|
1647
|
+
);
|
|
1648
|
+
const unsubscribes = [
|
|
1649
|
+
durably.on("run:trigger", (event) => {
|
|
1650
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1651
|
+
ctrl.enqueue({
|
|
1652
|
+
type: "run:trigger",
|
|
1653
|
+
runId: event.runId,
|
|
1654
|
+
jobName: event.jobName,
|
|
1655
|
+
labels: event.labels
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
}),
|
|
1659
|
+
durably.on("run:start", (event) => {
|
|
1660
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1661
|
+
ctrl.enqueue({
|
|
1662
|
+
type: "run:start",
|
|
1663
|
+
runId: event.runId,
|
|
1664
|
+
jobName: event.jobName,
|
|
1665
|
+
labels: event.labels
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
}),
|
|
1669
|
+
durably.on("run:complete", (event) => {
|
|
1670
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1671
|
+
ctrl.enqueue({
|
|
1672
|
+
type: "run:complete",
|
|
1673
|
+
runId: event.runId,
|
|
1674
|
+
jobName: event.jobName,
|
|
1675
|
+
labels: event.labels
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
}),
|
|
1679
|
+
durably.on("run:fail", (event) => {
|
|
1680
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1681
|
+
ctrl.enqueue({
|
|
1682
|
+
type: "run:fail",
|
|
1683
|
+
runId: event.runId,
|
|
1684
|
+
jobName: event.jobName,
|
|
1685
|
+
labels: event.labels
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
}),
|
|
1689
|
+
durably.on("run:cancel", (event) => {
|
|
1690
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1691
|
+
ctrl.enqueue({
|
|
1692
|
+
type: "run:cancel",
|
|
1693
|
+
runId: event.runId,
|
|
1694
|
+
jobName: event.jobName,
|
|
1695
|
+
labels: event.labels
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
}),
|
|
1699
|
+
durably.on("run:delete", (event) => {
|
|
1700
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1701
|
+
ctrl.enqueue({
|
|
1702
|
+
type: "run:delete",
|
|
1703
|
+
runId: event.runId,
|
|
1704
|
+
jobName: event.jobName,
|
|
1705
|
+
labels: event.labels
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
}),
|
|
1709
|
+
durably.on("run:progress", (event) => {
|
|
1710
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1711
|
+
ctrl.enqueue({
|
|
1712
|
+
type: "run:progress",
|
|
1713
|
+
runId: event.runId,
|
|
1714
|
+
jobName: event.jobName,
|
|
1715
|
+
progress: event.progress,
|
|
1716
|
+
labels: event.labels
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
}),
|
|
1720
|
+
durably.on("step:start", (event) => {
|
|
1721
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1722
|
+
ctrl.enqueue({
|
|
1723
|
+
type: "step:start",
|
|
1724
|
+
runId: event.runId,
|
|
1725
|
+
jobName: event.jobName,
|
|
1726
|
+
stepName: event.stepName,
|
|
1727
|
+
stepIndex: event.stepIndex,
|
|
1728
|
+
labels: event.labels
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
}),
|
|
1732
|
+
durably.on("step:complete", (event) => {
|
|
1733
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1734
|
+
ctrl.enqueue({
|
|
1735
|
+
type: "step:complete",
|
|
1736
|
+
runId: event.runId,
|
|
1737
|
+
jobName: event.jobName,
|
|
1738
|
+
stepName: event.stepName,
|
|
1739
|
+
stepIndex: event.stepIndex,
|
|
1740
|
+
labels: event.labels
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
}),
|
|
1744
|
+
durably.on("step:fail", (event) => {
|
|
1745
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1746
|
+
ctrl.enqueue({
|
|
1747
|
+
type: "step:fail",
|
|
1748
|
+
runId: event.runId,
|
|
1749
|
+
jobName: event.jobName,
|
|
1750
|
+
stepName: event.stepName,
|
|
1751
|
+
stepIndex: event.stepIndex,
|
|
1752
|
+
error: event.error,
|
|
1753
|
+
labels: event.labels
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
}),
|
|
1757
|
+
durably.on("step:cancel", (event) => {
|
|
1758
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1759
|
+
ctrl.enqueue({
|
|
1760
|
+
type: "step:cancel",
|
|
1761
|
+
runId: event.runId,
|
|
1762
|
+
jobName: event.jobName,
|
|
1763
|
+
stepName: event.stepName,
|
|
1764
|
+
stepIndex: event.stepIndex,
|
|
1765
|
+
labels: event.labels
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
}),
|
|
1769
|
+
durably.on("log:write", (event) => {
|
|
1770
|
+
if (matchesFilter(event.jobName, event.labels)) {
|
|
1771
|
+
ctrl.enqueue({
|
|
1772
|
+
type: "log:write",
|
|
1773
|
+
runId: event.runId,
|
|
1774
|
+
jobName: event.jobName,
|
|
1775
|
+
labels: event.labels,
|
|
1776
|
+
stepName: event.stepName,
|
|
1777
|
+
level: event.level,
|
|
1778
|
+
message: event.message,
|
|
1779
|
+
data: event.data
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
})
|
|
1783
|
+
];
|
|
1784
|
+
return [...unsubscribes, dispose];
|
|
1542
1785
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1786
|
+
);
|
|
1787
|
+
return createSSEResponse(sseStream);
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
async handle(request, basePath) {
|
|
1545
1791
|
try {
|
|
1792
|
+
let ctx;
|
|
1793
|
+
if (auth?.authenticate) {
|
|
1794
|
+
ctx = await auth.authenticate(request);
|
|
1795
|
+
}
|
|
1796
|
+
if (options?.onRequest) {
|
|
1797
|
+
await options.onRequest();
|
|
1798
|
+
}
|
|
1546
1799
|
const url = new URL(request.url);
|
|
1547
|
-
const
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1800
|
+
const path = url.pathname.replace(basePath, "");
|
|
1801
|
+
const method = request.method;
|
|
1802
|
+
if (method === "GET") {
|
|
1803
|
+
if (path === "/subscribe") return await handleSubscribe(url, ctx);
|
|
1804
|
+
if (path === "/runs") return await handleRuns(url, ctx);
|
|
1805
|
+
if (path === "/run") return await handleRun(url, ctx);
|
|
1806
|
+
if (path === "/steps") return await handleSteps(url, ctx);
|
|
1807
|
+
if (path === "/runs/subscribe")
|
|
1808
|
+
return await handleRunsSubscribe(url, ctx);
|
|
1809
|
+
}
|
|
1810
|
+
if (method === "POST") {
|
|
1811
|
+
if (path === "/trigger") return await handleTrigger(request, ctx);
|
|
1812
|
+
if (path === "/retrigger") return await handleRetrigger(url, ctx);
|
|
1813
|
+
if (path === "/cancel") return await handleCancel(url, ctx);
|
|
1814
|
+
}
|
|
1815
|
+
if (method === "DELETE") {
|
|
1816
|
+
if (path === "/run") return await handleDelete(url, ctx);
|
|
1817
|
+
}
|
|
1818
|
+
return new Response("Not Found", { status: 404 });
|
|
1551
1819
|
} catch (error) {
|
|
1820
|
+
if (error instanceof Response) return error;
|
|
1552
1821
|
return errorResponse(getErrorMessage(error), 500);
|
|
1553
1822
|
}
|
|
1554
|
-
},
|
|
1555
|
-
runsSubscribe(request) {
|
|
1556
|
-
const url = new URL(request.url);
|
|
1557
|
-
const jobNameFilter = url.searchParams.get("jobName");
|
|
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
|
-
};
|
|
1565
|
-
const sseStream = createSSEStreamFromSubscriptions(
|
|
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
|
-
}
|
|
1719
|
-
);
|
|
1720
|
-
return createSSEResponse(sseStream);
|
|
1721
1823
|
}
|
|
1722
1824
|
};
|
|
1723
|
-
return handler;
|
|
1724
1825
|
}
|
|
1725
1826
|
export {
|
|
1726
1827
|
CancelledError,
|