@upstash/workflow 1.4.0-rc → 1.4.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/astro.d.mts +2 -3
- package/astro.d.ts +2 -3
- package/astro.js +164 -575
- package/astro.mjs +1 -1
- package/{chunk-UH6O5IJY.mjs → chunk-DRHEHWMS.mjs} +326 -737
- package/cloudflare.d.mts +2 -3
- package/cloudflare.d.ts +2 -3
- package/cloudflare.js +164 -575
- package/cloudflare.mjs +1 -1
- package/express.d.mts +2 -3
- package/express.d.ts +2 -3
- package/express.js +164 -575
- package/express.mjs +1 -1
- package/h3.d.mts +2 -3
- package/h3.d.ts +2 -3
- package/h3.js +164 -575
- package/h3.mjs +1 -1
- package/hono.d.mts +2 -3
- package/hono.d.ts +2 -3
- package/hono.js +164 -575
- package/hono.mjs +1 -1
- package/index.d.mts +2 -3
- package/index.d.ts +2 -3
- package/index.js +164 -575
- package/index.mjs +1 -1
- package/nextjs.d.mts +2 -3
- package/nextjs.d.ts +2 -3
- package/nextjs.js +164 -575
- package/nextjs.mjs +1 -1
- package/package.json +1 -1
- package/react-router.d.mts +2 -3
- package/react-router.d.ts +2 -3
- package/react-router.js +164 -575
- package/react-router.mjs +1 -1
- package/{serve-many-CbIV7145.d.mts → serve-many-CG3BFvO3.d.mts} +1 -1
- package/{serve-many-BCtZg31b.d.ts → serve-many-iJF1IUXk.d.ts} +1 -1
- package/solidjs.d.mts +1 -2
- package/solidjs.d.ts +1 -2
- package/solidjs.js +164 -575
- package/solidjs.mjs +1 -1
- package/svelte.d.mts +2 -3
- package/svelte.d.ts +2 -3
- package/svelte.js +164 -575
- package/svelte.mjs +1 -1
- package/tanstack.d.mts +2 -3
- package/tanstack.d.ts +2 -3
- package/tanstack.js +164 -575
- package/tanstack.mjs +1 -1
- package/{types-ffLPVG5_.d.ts → types-CekOpKvz.d.mts} +8 -248
- package/{types-ffLPVG5_.d.mts → types-CekOpKvz.d.ts} +8 -248
|
@@ -118,14 +118,6 @@ var WORKFLOW_CREATED_AT_HEADER = "Upstash-Workflow-CreatedAt";
|
|
|
118
118
|
var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
|
|
119
119
|
var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
|
|
120
120
|
var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
|
|
121
|
-
var WORKFLOW_FEATURE_SET = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
122
|
-
var WORKFLOW_STEP_CONFIG_CALL_TYPE = "stepConfig";
|
|
123
|
-
var WORKFLOW_STEP_CONFIG_HEADER = "Upstash-Workflow-Step-Config";
|
|
124
|
-
var FLOW_CONTROL_KEY_HEADER = "Upstash-Flow-Control-Key";
|
|
125
|
-
var FLOW_CONTROL_VALUE_HEADER = "Upstash-Flow-Control-Value";
|
|
126
|
-
var RETRIES_HEADER = "Upstash-Retries";
|
|
127
|
-
var RETRY_DELAY_HEADER = "Upstash-Retry-Delay";
|
|
128
|
-
var WORKFLOW_STEP_CONFIG_FEATURE = "WF_StepConfig";
|
|
129
121
|
var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
|
|
130
122
|
var WORKFLOW_RETRIED_HEADER = "Upstash-Retried";
|
|
131
123
|
var WORKFLOW_LABEL_HEADER = "Upstash-Label";
|
|
@@ -232,6 +224,248 @@ var getNewUrlFromWorkflowId = (url, workflowId) => {
|
|
|
232
224
|
return url.replace(/[^/]+$/, workflowId);
|
|
233
225
|
};
|
|
234
226
|
|
|
227
|
+
// src/context/auto-executor.ts
|
|
228
|
+
import { QstashError as QstashError5 } from "@upstash/qstash";
|
|
229
|
+
|
|
230
|
+
// src/qstash/headers.ts
|
|
231
|
+
import { QstashError as QstashError4 } from "@upstash/qstash";
|
|
232
|
+
|
|
233
|
+
// src/client/utils.ts
|
|
234
|
+
import { QstashError as QstashError2 } from "@upstash/qstash";
|
|
235
|
+
var assertNonEmptyId = (id, label = "id") => {
|
|
236
|
+
if (!id) {
|
|
237
|
+
throw new QstashError2(`${label} cannot be empty`);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var toNonEmptyIdArray = (request, label = "id") => {
|
|
241
|
+
const ids = typeof request === "string" ? [request] : request;
|
|
242
|
+
for (const id of ids) assertNonEmptyId(id, label);
|
|
243
|
+
return ids;
|
|
244
|
+
};
|
|
245
|
+
var makeNotifyRequest = async (requester, eventId, eventData, workflowRunId) => {
|
|
246
|
+
assertNonEmptyId(eventId, "Event id");
|
|
247
|
+
if (workflowRunId !== void 0) assertNonEmptyId(workflowRunId, "Workflow run id");
|
|
248
|
+
const path = workflowRunId === void 0 ? ["v2", "notify", eventId] : ["v2", "notify", workflowRunId, eventId];
|
|
249
|
+
const result = await requester.request({
|
|
250
|
+
path,
|
|
251
|
+
method: "POST",
|
|
252
|
+
body: typeof eventData === "string" ? eventData : JSON.stringify(eventData)
|
|
253
|
+
});
|
|
254
|
+
return result;
|
|
255
|
+
};
|
|
256
|
+
var makeGetWaitersRequest = async (requester, eventId) => {
|
|
257
|
+
assertNonEmptyId(eventId, "Event id");
|
|
258
|
+
const result = await requester.request({
|
|
259
|
+
path: ["v2", "waiters", eventId],
|
|
260
|
+
method: "GET"
|
|
261
|
+
});
|
|
262
|
+
return result;
|
|
263
|
+
};
|
|
264
|
+
var makeCancelRequest = async (requester, workflowRunId) => {
|
|
265
|
+
await requester.request({
|
|
266
|
+
path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
|
|
267
|
+
method: "DELETE",
|
|
268
|
+
parseResponseAsJson: false
|
|
269
|
+
});
|
|
270
|
+
return true;
|
|
271
|
+
};
|
|
272
|
+
var getSteps = async (requester, workflowRunId, messageId, dispatchDebug) => {
|
|
273
|
+
try {
|
|
274
|
+
const steps = await requester.request({
|
|
275
|
+
path: ["v2", "workflows", "runs", workflowRunId],
|
|
276
|
+
parseResponseAsJson: true
|
|
277
|
+
});
|
|
278
|
+
if (steps.length === 1) {
|
|
279
|
+
return {
|
|
280
|
+
steps,
|
|
281
|
+
workflowRunEnded: false
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (!messageId) {
|
|
285
|
+
await dispatchDebug?.("onInfo", {
|
|
286
|
+
info: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
|
|
287
|
+
});
|
|
288
|
+
return { steps, workflowRunEnded: false };
|
|
289
|
+
} else {
|
|
290
|
+
const index = steps.findIndex((item) => item.messageId === messageId);
|
|
291
|
+
if (index === -1) {
|
|
292
|
+
return { steps: [], workflowRunEnded: false };
|
|
293
|
+
}
|
|
294
|
+
const filteredSteps = steps.slice(0, index + 1);
|
|
295
|
+
await dispatchDebug?.("onInfo", {
|
|
296
|
+
info: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
|
|
297
|
+
});
|
|
298
|
+
return { steps: filteredSteps, workflowRunEnded: false };
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (isInstanceOf(error, QstashError2) && error.status === 404) {
|
|
302
|
+
await dispatchDebug?.("onWarning", {
|
|
303
|
+
warning: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed."
|
|
304
|
+
});
|
|
305
|
+
return { steps: void 0, workflowRunEnded: true };
|
|
306
|
+
} else {
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
function normalizeCursor(response) {
|
|
312
|
+
const cursor = response.cursor;
|
|
313
|
+
return { ...response, cursor: cursor || void 0 };
|
|
314
|
+
}
|
|
315
|
+
var DEFAULT_BULK_COUNT = 100;
|
|
316
|
+
function buildBulkActionQueryParameters(request, options) {
|
|
317
|
+
const cursor = "cursor" in request ? request.cursor : void 0;
|
|
318
|
+
if ("all" in request) {
|
|
319
|
+
return { count: request.count ?? DEFAULT_BULK_COUNT, cursor };
|
|
320
|
+
}
|
|
321
|
+
if ("dlqIds" in request) {
|
|
322
|
+
const ids = request.dlqIds;
|
|
323
|
+
if (Array.isArray(ids) && ids.length === 0) {
|
|
324
|
+
throw new QstashError2(
|
|
325
|
+
"Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return { dlqIds: ids, cursor };
|
|
329
|
+
}
|
|
330
|
+
if ("workflowRunIds" in request && request.workflowRunIds) {
|
|
331
|
+
if (request.workflowRunIds.length === 0) {
|
|
332
|
+
throw new QstashError2(
|
|
333
|
+
"Empty workflowRunIds array provided. If you intend to target all workflow runs, use { all: true } explicitly."
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return { workflowRunIds: request.workflowRunIds };
|
|
337
|
+
}
|
|
338
|
+
const filter = request.filter;
|
|
339
|
+
if (!filter) {
|
|
340
|
+
throw new QstashError2(
|
|
341
|
+
"No filter provided. Use { filter: { ... } } with at least one filter field, or { all: true }."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
for (const [field, value] of Object.entries(filter)) {
|
|
345
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
346
|
+
throw new QstashError2(
|
|
347
|
+
`Empty array provided for filter field '${field}'. If you intend to target all records, use { all: true } explicitly.`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (options?.translateWorkflowUrl) {
|
|
352
|
+
const { workflowUrlStartingWith, workflowUrl, ...rest } = filter;
|
|
353
|
+
if (workflowUrlStartingWith && workflowUrl) {
|
|
354
|
+
throw new QstashError2(
|
|
355
|
+
"workflowUrl and workflowUrlStartingWith are mutually exclusive. Use workflowUrl for exact match or workflowUrlStartingWith for prefix match."
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const urlParams = {};
|
|
359
|
+
if (workflowUrlStartingWith) {
|
|
360
|
+
urlParams.workflowUrl = workflowUrlStartingWith;
|
|
361
|
+
} else if (workflowUrl) {
|
|
362
|
+
urlParams.workflowUrl = workflowUrl;
|
|
363
|
+
urlParams.workflowUrlExactMatch = true;
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
...rest,
|
|
367
|
+
...urlParams,
|
|
368
|
+
count: request.count ?? DEFAULT_BULK_COUNT,
|
|
369
|
+
cursor
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
...filter,
|
|
374
|
+
count: request.count ?? DEFAULT_BULK_COUNT,
|
|
375
|
+
cursor
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/utils.ts
|
|
380
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
381
|
+
var NANOID_LENGTH = 21;
|
|
382
|
+
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
383
|
+
function validateLabel(label) {
|
|
384
|
+
if (label === void 0) return;
|
|
385
|
+
const labels = Array.isArray(label) ? label : [label];
|
|
386
|
+
if (labels.length === 0) {
|
|
387
|
+
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
388
|
+
}
|
|
389
|
+
for (const value of labels) {
|
|
390
|
+
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
391
|
+
throw new WorkflowNonRetryableError(
|
|
392
|
+
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function serializeLabel(label) {
|
|
398
|
+
return Array.isArray(label) ? label.join(",") : label;
|
|
399
|
+
}
|
|
400
|
+
function validateFlowControl(flowControl) {
|
|
401
|
+
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
402
|
+
throw new WorkflowNonRetryableError(
|
|
403
|
+
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function getRandomInt() {
|
|
408
|
+
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
409
|
+
}
|
|
410
|
+
function nanoid(length = NANOID_LENGTH) {
|
|
411
|
+
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
412
|
+
}
|
|
413
|
+
function getWorkflowRunId(id) {
|
|
414
|
+
return `wfr_${id ?? nanoid()}`;
|
|
415
|
+
}
|
|
416
|
+
function decodeBase64(base64) {
|
|
417
|
+
const binString = atob(base64);
|
|
418
|
+
try {
|
|
419
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
420
|
+
return new TextDecoder().decode(intArray);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.warn(
|
|
423
|
+
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
424
|
+
);
|
|
425
|
+
return binString;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function getUserIdFromToken(qstashClient) {
|
|
429
|
+
try {
|
|
430
|
+
const token = qstashClient.token;
|
|
431
|
+
const decodedToken = decodeBase64(token);
|
|
432
|
+
const tokenPayload = JSON.parse(decodedToken);
|
|
433
|
+
const userId = tokenPayload.UserID;
|
|
434
|
+
if (!userId) {
|
|
435
|
+
throw new WorkflowError("QStash token payload does not contain userId");
|
|
436
|
+
}
|
|
437
|
+
return userId;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
throw new WorkflowError(
|
|
440
|
+
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function getQStashUrl(qstashClient) {
|
|
445
|
+
try {
|
|
446
|
+
const requester = qstashClient.http;
|
|
447
|
+
const baseUrl = requester.baseUrl;
|
|
448
|
+
if (!baseUrl) {
|
|
449
|
+
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
450
|
+
}
|
|
451
|
+
return baseUrl;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function getEventId() {
|
|
457
|
+
return `evt_${nanoid(15)}`;
|
|
458
|
+
}
|
|
459
|
+
function stringifyBody(body) {
|
|
460
|
+
if (body === void 0) {
|
|
461
|
+
return void 0;
|
|
462
|
+
}
|
|
463
|
+
if (typeof body === "string") {
|
|
464
|
+
return body;
|
|
465
|
+
}
|
|
466
|
+
return JSON.stringify(body);
|
|
467
|
+
}
|
|
468
|
+
|
|
235
469
|
// node_modules/neverthrow/dist/index.es.js
|
|
236
470
|
var defaultErrorConfig = {
|
|
237
471
|
withStackTrace: false
|
|
@@ -625,269 +859,27 @@ var Err = class {
|
|
|
625
859
|
asyncMap(_f) {
|
|
626
860
|
return errAsync(this.error);
|
|
627
861
|
}
|
|
628
|
-
unwrapOr(v) {
|
|
629
|
-
return v;
|
|
630
|
-
}
|
|
631
|
-
match(_ok, err2) {
|
|
632
|
-
return err2(this.error);
|
|
633
|
-
}
|
|
634
|
-
safeUnwrap() {
|
|
635
|
-
const error = this.error;
|
|
636
|
-
return (function* () {
|
|
637
|
-
yield err(error);
|
|
638
|
-
throw new Error("Do not use this generator out of `safeTry`");
|
|
639
|
-
})();
|
|
640
|
-
}
|
|
641
|
-
_unsafeUnwrap(config) {
|
|
642
|
-
throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
|
|
643
|
-
}
|
|
644
|
-
_unsafeUnwrapErr(_) {
|
|
645
|
-
return this.error;
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
var fromThrowable = Result.fromThrowable;
|
|
649
|
-
|
|
650
|
-
// src/context/auto-executor.ts
|
|
651
|
-
import { QstashError as QstashError5 } from "@upstash/qstash";
|
|
652
|
-
|
|
653
|
-
// src/qstash/headers.ts
|
|
654
|
-
import { QstashError as QstashError4 } from "@upstash/qstash";
|
|
655
|
-
|
|
656
|
-
// src/client/utils.ts
|
|
657
|
-
import { QstashError as QstashError2 } from "@upstash/qstash";
|
|
658
|
-
var assertNonEmptyId = (id, label = "id") => {
|
|
659
|
-
if (!id) {
|
|
660
|
-
throw new QstashError2(`${label} cannot be empty`);
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
var toNonEmptyIdArray = (request, label = "id") => {
|
|
664
|
-
const ids = typeof request === "string" ? [request] : request;
|
|
665
|
-
for (const id of ids) assertNonEmptyId(id, label);
|
|
666
|
-
return ids;
|
|
667
|
-
};
|
|
668
|
-
var makeNotifyRequest = async (requester, eventId, eventData, workflowRunId) => {
|
|
669
|
-
assertNonEmptyId(eventId, "Event id");
|
|
670
|
-
if (workflowRunId !== void 0) assertNonEmptyId(workflowRunId, "Workflow run id");
|
|
671
|
-
const path = workflowRunId === void 0 ? ["v2", "notify", eventId] : ["v2", "notify", workflowRunId, eventId];
|
|
672
|
-
const result = await requester.request({
|
|
673
|
-
path,
|
|
674
|
-
method: "POST",
|
|
675
|
-
body: typeof eventData === "string" ? eventData : JSON.stringify(eventData)
|
|
676
|
-
});
|
|
677
|
-
return result;
|
|
678
|
-
};
|
|
679
|
-
var makeGetWaitersRequest = async (requester, eventId) => {
|
|
680
|
-
assertNonEmptyId(eventId, "Event id");
|
|
681
|
-
const result = await requester.request({
|
|
682
|
-
path: ["v2", "waiters", eventId],
|
|
683
|
-
method: "GET"
|
|
684
|
-
});
|
|
685
|
-
return result;
|
|
686
|
-
};
|
|
687
|
-
var makeCancelRequest = async (requester, workflowRunId) => {
|
|
688
|
-
await requester.request({
|
|
689
|
-
path: ["v2", "workflows", "runs", `${workflowRunId}?cancel=true`],
|
|
690
|
-
method: "DELETE",
|
|
691
|
-
parseResponseAsJson: false
|
|
692
|
-
});
|
|
693
|
-
return true;
|
|
694
|
-
};
|
|
695
|
-
var getSteps = async (requester, workflowRunId, messageId, dispatchDebug) => {
|
|
696
|
-
try {
|
|
697
|
-
const steps = await requester.request({
|
|
698
|
-
path: ["v2", "workflows", "runs", workflowRunId],
|
|
699
|
-
parseResponseAsJson: true
|
|
700
|
-
});
|
|
701
|
-
if (steps.length === 1) {
|
|
702
|
-
return {
|
|
703
|
-
steps,
|
|
704
|
-
workflowRunEnded: false
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
if (!messageId) {
|
|
708
|
-
await dispatchDebug?.("onInfo", {
|
|
709
|
-
info: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
|
|
710
|
-
});
|
|
711
|
-
return { steps, workflowRunEnded: false };
|
|
712
|
-
} else {
|
|
713
|
-
const index = steps.findIndex((item) => item.messageId === messageId);
|
|
714
|
-
if (index === -1) {
|
|
715
|
-
return { steps: [], workflowRunEnded: false };
|
|
716
|
-
}
|
|
717
|
-
const filteredSteps = steps.slice(0, index + 1);
|
|
718
|
-
await dispatchDebug?.("onInfo", {
|
|
719
|
-
info: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
|
|
720
|
-
});
|
|
721
|
-
return { steps: filteredSteps, workflowRunEnded: false };
|
|
722
|
-
}
|
|
723
|
-
} catch (error) {
|
|
724
|
-
if (isInstanceOf(error, QstashError2) && error.status === 404) {
|
|
725
|
-
await dispatchDebug?.("onWarning", {
|
|
726
|
-
warning: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed."
|
|
727
|
-
});
|
|
728
|
-
return { steps: void 0, workflowRunEnded: true };
|
|
729
|
-
} else {
|
|
730
|
-
throw error;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
function normalizeCursor(response) {
|
|
735
|
-
const cursor = response.cursor;
|
|
736
|
-
return { ...response, cursor: cursor || void 0 };
|
|
737
|
-
}
|
|
738
|
-
var DEFAULT_BULK_COUNT = 100;
|
|
739
|
-
function buildBulkActionQueryParameters(request, options) {
|
|
740
|
-
const cursor = "cursor" in request ? request.cursor : void 0;
|
|
741
|
-
if ("all" in request) {
|
|
742
|
-
return { count: request.count ?? DEFAULT_BULK_COUNT, cursor };
|
|
743
|
-
}
|
|
744
|
-
if ("dlqIds" in request) {
|
|
745
|
-
const ids = request.dlqIds;
|
|
746
|
-
if (Array.isArray(ids) && ids.length === 0) {
|
|
747
|
-
throw new QstashError2(
|
|
748
|
-
"Empty dlqIds array provided. If you intend to target all DLQ messages, use { all: true } explicitly."
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
return { dlqIds: ids, cursor };
|
|
752
|
-
}
|
|
753
|
-
if ("workflowRunIds" in request && request.workflowRunIds) {
|
|
754
|
-
if (request.workflowRunIds.length === 0) {
|
|
755
|
-
throw new QstashError2(
|
|
756
|
-
"Empty workflowRunIds array provided. If you intend to target all workflow runs, use { all: true } explicitly."
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
return { workflowRunIds: request.workflowRunIds };
|
|
760
|
-
}
|
|
761
|
-
const filter = request.filter;
|
|
762
|
-
if (!filter) {
|
|
763
|
-
throw new QstashError2(
|
|
764
|
-
"No filter provided. Use { filter: { ... } } with at least one filter field, or { all: true }."
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
for (const [field, value] of Object.entries(filter)) {
|
|
768
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
769
|
-
throw new QstashError2(
|
|
770
|
-
`Empty array provided for filter field '${field}'. If you intend to target all records, use { all: true } explicitly.`
|
|
771
|
-
);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (options?.translateWorkflowUrl) {
|
|
775
|
-
const { workflowUrlStartingWith, workflowUrl, ...rest } = filter;
|
|
776
|
-
if (workflowUrlStartingWith && workflowUrl) {
|
|
777
|
-
throw new QstashError2(
|
|
778
|
-
"workflowUrl and workflowUrlStartingWith are mutually exclusive. Use workflowUrl for exact match or workflowUrlStartingWith for prefix match."
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
const urlParams = {};
|
|
782
|
-
if (workflowUrlStartingWith) {
|
|
783
|
-
urlParams.workflowUrl = workflowUrlStartingWith;
|
|
784
|
-
} else if (workflowUrl) {
|
|
785
|
-
urlParams.workflowUrl = workflowUrl;
|
|
786
|
-
urlParams.workflowUrlExactMatch = true;
|
|
787
|
-
}
|
|
788
|
-
return {
|
|
789
|
-
...rest,
|
|
790
|
-
...urlParams,
|
|
791
|
-
count: request.count ?? DEFAULT_BULK_COUNT,
|
|
792
|
-
cursor
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
return {
|
|
796
|
-
...filter,
|
|
797
|
-
count: request.count ?? DEFAULT_BULK_COUNT,
|
|
798
|
-
cursor
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// src/utils.ts
|
|
803
|
-
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
804
|
-
var NANOID_LENGTH = 21;
|
|
805
|
-
var RESOURCE_NAME_PATTERN = /^[a-zA-Z0-9\-_.]+$/;
|
|
806
|
-
function validateLabel(label) {
|
|
807
|
-
if (label === void 0) return;
|
|
808
|
-
const labels = Array.isArray(label) ? label : [label];
|
|
809
|
-
if (labels.length === 0) {
|
|
810
|
-
throw new WorkflowNonRetryableError("Invalid label: label array must not be empty.");
|
|
811
|
-
}
|
|
812
|
-
for (const value of labels) {
|
|
813
|
-
if (!RESOURCE_NAME_PATTERN.test(value)) {
|
|
814
|
-
throw new WorkflowNonRetryableError(
|
|
815
|
-
`Invalid label "${value}": must be alphanumeric, hyphen, underscore, or period.`
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
function serializeLabel(label) {
|
|
821
|
-
return Array.isArray(label) ? label.join(",") : label;
|
|
822
|
-
}
|
|
823
|
-
function validateFlowControl(flowControl) {
|
|
824
|
-
if (flowControl?.key !== void 0 && !RESOURCE_NAME_PATTERN.test(flowControl.key)) {
|
|
825
|
-
throw new WorkflowNonRetryableError(
|
|
826
|
-
`Invalid flow control key "${flowControl.key}": must be alphanumeric, hyphen, underscore, or period.`
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
function getRandomInt() {
|
|
831
|
-
return Math.floor(Math.random() * NANOID_CHARS.length);
|
|
832
|
-
}
|
|
833
|
-
function nanoid(length = NANOID_LENGTH) {
|
|
834
|
-
return Array.from({ length }).map(() => NANOID_CHARS[getRandomInt()]).join("");
|
|
835
|
-
}
|
|
836
|
-
function getWorkflowRunId(id) {
|
|
837
|
-
return `wfr_${id ?? nanoid()}`;
|
|
838
|
-
}
|
|
839
|
-
function decodeBase64(base64) {
|
|
840
|
-
const binString = atob(base64);
|
|
841
|
-
try {
|
|
842
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
843
|
-
return new TextDecoder().decode(intArray);
|
|
844
|
-
} catch (error) {
|
|
845
|
-
console.warn(
|
|
846
|
-
`Upstash Qstash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
847
|
-
);
|
|
848
|
-
return binString;
|
|
862
|
+
unwrapOr(v) {
|
|
863
|
+
return v;
|
|
849
864
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
try {
|
|
853
|
-
const token = qstashClient.token;
|
|
854
|
-
const decodedToken = decodeBase64(token);
|
|
855
|
-
const tokenPayload = JSON.parse(decodedToken);
|
|
856
|
-
const userId = tokenPayload.UserID;
|
|
857
|
-
if (!userId) {
|
|
858
|
-
throw new WorkflowError("QStash token payload does not contain userId");
|
|
859
|
-
}
|
|
860
|
-
return userId;
|
|
861
|
-
} catch (error) {
|
|
862
|
-
throw new WorkflowError(
|
|
863
|
-
`Failed to decode QStash token while running create webhook step: ${error.message}`
|
|
864
|
-
);
|
|
865
|
+
match(_ok, err2) {
|
|
866
|
+
return err2(this.error);
|
|
865
867
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
throw new WorkflowError("QStash client does not have a baseUrl");
|
|
873
|
-
}
|
|
874
|
-
return baseUrl;
|
|
875
|
-
} catch (error) {
|
|
876
|
-
throw new WorkflowError(`Failed to get QStash URL from client: ${error.message}`);
|
|
868
|
+
safeUnwrap() {
|
|
869
|
+
const error = this.error;
|
|
870
|
+
return (function* () {
|
|
871
|
+
yield err(error);
|
|
872
|
+
throw new Error("Do not use this generator out of `safeTry`");
|
|
873
|
+
})();
|
|
877
874
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
return `evt_${nanoid(15)}`;
|
|
881
|
-
}
|
|
882
|
-
function stringifyBody(body) {
|
|
883
|
-
if (body === void 0) {
|
|
884
|
-
return void 0;
|
|
875
|
+
_unsafeUnwrap(config) {
|
|
876
|
+
throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
|
|
885
877
|
}
|
|
886
|
-
|
|
887
|
-
return
|
|
878
|
+
_unsafeUnwrapErr(_) {
|
|
879
|
+
return this.error;
|
|
888
880
|
}
|
|
889
|
-
|
|
890
|
-
|
|
881
|
+
};
|
|
882
|
+
var fromThrowable = Result.fromThrowable;
|
|
891
883
|
|
|
892
884
|
// src/workflow-requests.ts
|
|
893
885
|
import { QstashError as QstashError3 } from "@upstash/qstash";
|
|
@@ -914,6 +906,10 @@ var triggerFirstInvocation = async (params) => {
|
|
|
914
906
|
workflowConfig: {
|
|
915
907
|
workflowRunId: workflowContext.workflowRunId,
|
|
916
908
|
workflowUrl: workflowContext.url,
|
|
909
|
+
// the run doesn't exist yet, so the trigger time is sent when the creation time
|
|
910
|
+
// is not known. serve and client.trigger set it on the context already, so a
|
|
911
|
+
// retry of this request carries the same value and is deduplicated.
|
|
912
|
+
workflowRunCreatedAt: workflowContext.workflowRunCreatedAt || Date.now(),
|
|
917
913
|
failureUrl,
|
|
918
914
|
retries,
|
|
919
915
|
retryDelay,
|
|
@@ -980,10 +976,6 @@ var triggerFirstInvocation = async (params) => {
|
|
|
980
976
|
return err(error_);
|
|
981
977
|
}
|
|
982
978
|
};
|
|
983
|
-
var flushPendingStep = async (workflowContext) => {
|
|
984
|
-
const { executor } = workflowContext;
|
|
985
|
-
return await executor.submitPendingStep();
|
|
986
|
-
};
|
|
987
979
|
var triggerRouteFunction = async ({
|
|
988
980
|
onCleanup,
|
|
989
981
|
onStep,
|
|
@@ -1021,7 +1013,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
|
|
|
1021
1013
|
path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
|
|
1022
1014
|
method: "DELETE",
|
|
1023
1015
|
parseResponseAsJson: false,
|
|
1024
|
-
body: JSON.stringify(result)
|
|
1016
|
+
body: JSON.stringify(result),
|
|
1017
|
+
// with the creation time, QStash deletes the run only if it is the same run.
|
|
1018
|
+
// a late delete of a previous run can't delete a new run with the same id.
|
|
1019
|
+
headers: workflowContext.workflowRunCreatedAt ? { [WORKFLOW_CREATED_AT_HEADER]: workflowContext.workflowRunCreatedAt.toString() } : void 0
|
|
1025
1020
|
});
|
|
1026
1021
|
await dispatchDebug?.("onInfo", {
|
|
1027
1022
|
info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
|
|
@@ -1098,6 +1093,7 @@ ${atob(callbackMessage.body ?? "")}`
|
|
|
1098
1093
|
const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
|
|
1099
1094
|
const contentType = request.headers.get("Upstash-Workflow-ContentType");
|
|
1100
1095
|
const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
|
|
1096
|
+
const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
1101
1097
|
if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
|
|
1102
1098
|
throw new Error(
|
|
1103
1099
|
`Missing info in callback message source header: ${JSON.stringify({
|
|
@@ -1115,6 +1111,7 @@ ${atob(callbackMessage.body ?? "")}`
|
|
|
1115
1111
|
workflowConfig: {
|
|
1116
1112
|
workflowRunId,
|
|
1117
1113
|
workflowUrl,
|
|
1114
|
+
workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
|
|
1118
1115
|
telemetry
|
|
1119
1116
|
},
|
|
1120
1117
|
invokeCount: Number(invokeCount)
|
|
@@ -1191,29 +1188,8 @@ If you want to disable QStash Verification, you should clear env variables QSTAS
|
|
|
1191
1188
|
var BaseLazyStep = class _BaseLazyStep {
|
|
1192
1189
|
stepName;
|
|
1193
1190
|
context;
|
|
1194
|
-
|
|
1195
|
-
* step-level settings (flow control, retries etc.) which override the
|
|
1196
|
-
* settings the workflow run was triggered with, for this step only.
|
|
1197
|
-
*/
|
|
1198
|
-
stepSettings;
|
|
1199
|
-
/**
|
|
1200
|
-
* whether this step's result can be submitted after the route function
|
|
1201
|
-
* has moved on, instead of right away.
|
|
1202
|
-
*
|
|
1203
|
-
* Enabled for steps whose `getResultStep` produces the final `out` —
|
|
1204
|
-
* which is not the same as "needs no server round trip":
|
|
1205
|
-
* `LazyCreateWebhookStep` builds its result in `getBody` and returns
|
|
1206
|
-
* `out: undefined` from `getResultStep`, so deferring it would hand
|
|
1207
|
-
* `undefined` to the route function.
|
|
1208
|
-
*
|
|
1209
|
-
* Deferring lets the route function continue and reveal the next step,
|
|
1210
|
-
* so that step's settings can ride on this step's submission instead
|
|
1211
|
-
* of needing a step config request of their own.
|
|
1212
|
-
*/
|
|
1213
|
-
supportsDeferredSubmission = false;
|
|
1214
|
-
constructor(context, stepName, stepSettings) {
|
|
1191
|
+
constructor(context, stepName) {
|
|
1215
1192
|
this.context = context;
|
|
1216
|
-
this.stepSettings = stepSettings;
|
|
1217
1193
|
if (!stepName) {
|
|
1218
1194
|
throw new WorkflowError(
|
|
1219
1195
|
"A workflow step name cannot be undefined or an empty string. Please provide a name for your workflow step."
|
|
@@ -1277,18 +1253,13 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1277
1253
|
step.out = JSON.stringify(step.out);
|
|
1278
1254
|
return JSON.stringify(step);
|
|
1279
1255
|
}
|
|
1280
|
-
getHeaders({
|
|
1281
|
-
context,
|
|
1282
|
-
telemetry,
|
|
1283
|
-
invokeCount,
|
|
1284
|
-
step,
|
|
1285
|
-
stepSettings
|
|
1286
|
-
}) {
|
|
1256
|
+
getHeaders({ context, telemetry, invokeCount, step }) {
|
|
1287
1257
|
return getHeaders({
|
|
1288
1258
|
initHeaderValue: "false",
|
|
1289
1259
|
workflowConfig: {
|
|
1290
1260
|
workflowRunId: context.workflowRunId,
|
|
1291
1261
|
workflowUrl: context.url,
|
|
1262
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1292
1263
|
useJSONContent: false,
|
|
1293
1264
|
telemetry
|
|
1294
1265
|
},
|
|
@@ -1296,8 +1267,7 @@ var BaseLazyStep = class _BaseLazyStep {
|
|
|
1296
1267
|
stepInfo: {
|
|
1297
1268
|
step,
|
|
1298
1269
|
lazyStep: this
|
|
1299
|
-
}
|
|
1300
|
-
stepSettings
|
|
1270
|
+
}
|
|
1301
1271
|
});
|
|
1302
1272
|
}
|
|
1303
1273
|
async submitStep({ context, body, headers }) {
|
|
@@ -1315,9 +1285,8 @@ var LazyFunctionStep = class extends BaseLazyStep {
|
|
|
1315
1285
|
stepFunction;
|
|
1316
1286
|
stepType = "Run";
|
|
1317
1287
|
allowUndefinedOut = true;
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
super(context, stepName, stepSettings);
|
|
1288
|
+
constructor(context, stepName, stepFunction) {
|
|
1289
|
+
super(context, stepName);
|
|
1321
1290
|
this.stepFunction = stepFunction;
|
|
1322
1291
|
}
|
|
1323
1292
|
getPlanStep(concurrent, targetStep) {
|
|
@@ -1347,7 +1316,6 @@ var LazySleepStep = class extends BaseLazyStep {
|
|
|
1347
1316
|
sleep;
|
|
1348
1317
|
stepType = "SleepFor";
|
|
1349
1318
|
allowUndefinedOut = true;
|
|
1350
|
-
supportsDeferredSubmission = true;
|
|
1351
1319
|
constructor(context, stepName, sleep) {
|
|
1352
1320
|
super(context, stepName);
|
|
1353
1321
|
this.sleep = sleep;
|
|
@@ -1387,7 +1355,6 @@ var LazySleepUntilStep = class extends BaseLazyStep {
|
|
|
1387
1355
|
sleepUntil;
|
|
1388
1356
|
stepType = "SleepUntil";
|
|
1389
1357
|
allowUndefinedOut = true;
|
|
1390
|
-
supportsDeferredSubmission = true;
|
|
1391
1358
|
constructor(context, stepName, sleepUntil) {
|
|
1392
1359
|
super(context, stepName);
|
|
1393
1360
|
this.sleepUntil = sleepUntil;
|
|
@@ -1620,6 +1587,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
|
|
|
1620
1587
|
) : {},
|
|
1621
1588
|
// note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
|
|
1622
1589
|
"Upstash-Workflow-Runid": [context.workflowRunId],
|
|
1590
|
+
// same for WORKFLOW_CREATED_AT_HEADER, because Createdat -> CreatedAt:
|
|
1591
|
+
...context.workflowRunCreatedAt ? { "Upstash-Workflow-Createdat": [context.workflowRunCreatedAt.toString()] } : {},
|
|
1623
1592
|
[WORKFLOW_INIT_HEADER]: ["false"],
|
|
1624
1593
|
[WORKFLOW_URL_HEADER]: [context.url],
|
|
1625
1594
|
"Upstash-Workflow-CallType": ["step"]
|
|
@@ -1722,6 +1691,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
|
|
|
1722
1691
|
workflowConfig: {
|
|
1723
1692
|
workflowRunId: context.workflowRunId,
|
|
1724
1693
|
workflowUrl: context.url,
|
|
1694
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1725
1695
|
telemetry,
|
|
1726
1696
|
useJSONContent: false
|
|
1727
1697
|
},
|
|
@@ -1731,6 +1701,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
|
|
|
1731
1701
|
invokerHeaders[key] = value;
|
|
1732
1702
|
});
|
|
1733
1703
|
invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
|
|
1704
|
+
if (context.workflowRunCreatedAt) {
|
|
1705
|
+
invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
|
|
1706
|
+
}
|
|
1734
1707
|
const request = {
|
|
1735
1708
|
body: stringifyBody(this.params.body),
|
|
1736
1709
|
headers: Object.fromEntries(
|
|
@@ -1759,6 +1732,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
|
|
|
1759
1732
|
workflowConfig: {
|
|
1760
1733
|
workflowRunId: getWorkflowRunId(workflowRunId),
|
|
1761
1734
|
workflowUrl: newUrl,
|
|
1735
|
+
// the invoked run doesn't exist yet, so the creation time of the invoker run is sent.
|
|
1736
|
+
// it separates the invocations made by different runs of the invoker.
|
|
1737
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
1762
1738
|
retries,
|
|
1763
1739
|
retryDelay,
|
|
1764
1740
|
telemetry,
|
|
@@ -1880,7 +1856,6 @@ var WorkflowHeaders = class {
|
|
|
1880
1856
|
invokeCount;
|
|
1881
1857
|
initHeaderValue;
|
|
1882
1858
|
stepInfo;
|
|
1883
|
-
stepSettings;
|
|
1884
1859
|
headers;
|
|
1885
1860
|
/**
|
|
1886
1861
|
* @param params workflow header parameters
|
|
@@ -1890,20 +1865,17 @@ var WorkflowHeaders = class {
|
|
|
1890
1865
|
workflowConfig,
|
|
1891
1866
|
invokeCount,
|
|
1892
1867
|
initHeaderValue,
|
|
1893
|
-
stepInfo
|
|
1894
|
-
stepSettings
|
|
1868
|
+
stepInfo
|
|
1895
1869
|
}) {
|
|
1896
1870
|
this.userHeaders = userHeaders;
|
|
1897
1871
|
this.workflowConfig = workflowConfig;
|
|
1898
1872
|
this.invokeCount = invokeCount;
|
|
1899
1873
|
this.initHeaderValue = initHeaderValue;
|
|
1900
1874
|
this.stepInfo = stepInfo;
|
|
1901
|
-
this.stepSettings = stepSettings;
|
|
1902
1875
|
this.headers = {
|
|
1903
1876
|
rawHeaders: {},
|
|
1904
1877
|
workflowHeaders: {},
|
|
1905
|
-
failureHeaders: {}
|
|
1906
|
-
stepSettingsHeaders: {}
|
|
1878
|
+
failureHeaders: {}
|
|
1907
1879
|
};
|
|
1908
1880
|
}
|
|
1909
1881
|
getHeaders() {
|
|
@@ -1914,7 +1886,6 @@ var WorkflowHeaders = class {
|
|
|
1914
1886
|
this.addUserHeaders();
|
|
1915
1887
|
this.addInvokeCount();
|
|
1916
1888
|
this.addFailureUrl();
|
|
1917
|
-
this.addStepSettings();
|
|
1918
1889
|
const contentType = this.addContentType();
|
|
1919
1890
|
return this.prefixHeaders(contentType);
|
|
1920
1891
|
}
|
|
@@ -1924,10 +1895,13 @@ var WorkflowHeaders = class {
|
|
|
1924
1895
|
[WORKFLOW_INIT_HEADER]: this.initHeaderValue,
|
|
1925
1896
|
[WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
|
|
1926
1897
|
[WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
|
|
1927
|
-
[WORKFLOW_FEATURE_HEADER]:
|
|
1898
|
+
[WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig",
|
|
1928
1899
|
[WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
|
|
1929
1900
|
...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
|
|
1930
1901
|
};
|
|
1902
|
+
if (this.workflowConfig.workflowRunCreatedAt) {
|
|
1903
|
+
this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
|
|
1904
|
+
}
|
|
1931
1905
|
if (this.stepInfo?.lazyStep.stepType !== "Call") {
|
|
1932
1906
|
this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
|
|
1933
1907
|
}
|
|
@@ -2002,7 +1976,7 @@ var WorkflowHeaders = class {
|
|
|
2002
1976
|
this.headers.failureHeaders["Workflow-Init"] = "false";
|
|
2003
1977
|
this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
|
|
2004
1978
|
this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
|
|
2005
|
-
this.headers.failureHeaders["Feature-Set"] =
|
|
1979
|
+
this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
|
|
2006
1980
|
if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
|
|
2007
1981
|
this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
|
|
2008
1982
|
}
|
|
@@ -2010,42 +1984,6 @@ var WorkflowHeaders = class {
|
|
|
2010
1984
|
this.headers.failureHeaders["Retry-Delay"] = this.workflowConfig.retryDelay.toString();
|
|
2011
1985
|
}
|
|
2012
1986
|
}
|
|
2013
|
-
/**
|
|
2014
|
-
* Applies the step-level settings of a step to the message.
|
|
2015
|
-
*
|
|
2016
|
-
* These headers configure the message itself, so that QStash uses them
|
|
2017
|
-
* instead of the settings the run was triggered with. The delivery of
|
|
2018
|
-
* the message is what executes the step, which is how a step's
|
|
2019
|
-
* settings reach it.
|
|
2020
|
-
*
|
|
2021
|
-
* When any setting is present the feature set is extended with
|
|
2022
|
-
* `WF_StepConfig`, which is what tells QStash to keep them. QStash
|
|
2023
|
-
* reports that back on the delivery as `Upstash-Workflow-Step-Config`.
|
|
2024
|
-
*/
|
|
2025
|
-
addStepSettings() {
|
|
2026
|
-
if (!this.stepSettings) {
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
const headers = {};
|
|
2030
|
-
if (this.stepSettings.flowControl) {
|
|
2031
|
-
const { flowControlKey, flowControlValue } = prepareFlowControl(
|
|
2032
|
-
this.stepSettings.flowControl
|
|
2033
|
-
);
|
|
2034
|
-
headers[FLOW_CONTROL_KEY_HEADER] = flowControlKey;
|
|
2035
|
-
headers[FLOW_CONTROL_VALUE_HEADER] = flowControlValue;
|
|
2036
|
-
}
|
|
2037
|
-
if (this.stepSettings.retries !== void 0) {
|
|
2038
|
-
headers[RETRIES_HEADER] = this.stepSettings.retries.toString();
|
|
2039
|
-
}
|
|
2040
|
-
if (this.stepSettings.retryDelay) {
|
|
2041
|
-
headers[RETRY_DELAY_HEADER] = this.stepSettings.retryDelay;
|
|
2042
|
-
}
|
|
2043
|
-
if (Object.keys(headers).length === 0) {
|
|
2044
|
-
return;
|
|
2045
|
-
}
|
|
2046
|
-
headers[WORKFLOW_FEATURE_HEADER] = `${WORKFLOW_FEATURE_SET},${WORKFLOW_STEP_CONFIG_FEATURE}`;
|
|
2047
|
-
this.headers.stepSettingsHeaders = headers;
|
|
2048
|
-
}
|
|
2049
1987
|
addContentType() {
|
|
2050
1988
|
if (this.workflowConfig.useJSONContent) {
|
|
2051
1989
|
this.headers.rawHeaders["content-type"] = "application/json";
|
|
@@ -2059,17 +1997,14 @@ var WorkflowHeaders = class {
|
|
|
2059
1997
|
return contentType;
|
|
2060
1998
|
}
|
|
2061
1999
|
prefixHeaders(contentType) {
|
|
2062
|
-
const { rawHeaders, workflowHeaders, failureHeaders
|
|
2000
|
+
const { rawHeaders, workflowHeaders, failureHeaders } = this.headers;
|
|
2063
2001
|
const isCall = this.stepInfo?.lazyStep.stepType === "Call";
|
|
2064
2002
|
return {
|
|
2065
2003
|
headers: {
|
|
2066
2004
|
...rawHeaders,
|
|
2067
2005
|
...addPrefixToHeaders(workflowHeaders, isCall ? "Upstash-Callback-" : "Upstash-"),
|
|
2068
2006
|
...addPrefixToHeaders(failureHeaders, "Upstash-Failure-Callback-"),
|
|
2069
|
-
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
2070
|
-
// last, so they override the run's settings above. Never
|
|
2071
|
-
// prefixed: they configure this message, not its callback.
|
|
2072
|
-
...stepSettingsHeaders
|
|
2007
|
+
...isCall ? addPrefixToHeaders(failureHeaders, "Upstash-Callback-Failure-Callback-") : {}
|
|
2073
2008
|
},
|
|
2074
2009
|
contentType
|
|
2075
2010
|
};
|
|
@@ -2120,16 +2055,16 @@ var submitParallelSteps = async ({
|
|
|
2120
2055
|
info: `Submitting ${planSteps.length} parallel steps.`
|
|
2121
2056
|
});
|
|
2122
2057
|
const result = await context.qstashClient.batch(
|
|
2123
|
-
planSteps.map((planStep
|
|
2058
|
+
planSteps.map((planStep) => {
|
|
2124
2059
|
const { headers } = getHeaders({
|
|
2125
2060
|
initHeaderValue: "false",
|
|
2126
2061
|
workflowConfig: {
|
|
2127
2062
|
workflowRunId: context.workflowRunId,
|
|
2128
2063
|
workflowUrl: context.url,
|
|
2064
|
+
workflowRunCreatedAt: context.workflowRunCreatedAt,
|
|
2129
2065
|
telemetry
|
|
2130
2066
|
},
|
|
2131
|
-
invokeCount
|
|
2132
|
-
stepSettings: steps[index].stepSettings
|
|
2067
|
+
invokeCount
|
|
2133
2068
|
});
|
|
2134
2069
|
return {
|
|
2135
2070
|
headers,
|
|
@@ -2148,38 +2083,31 @@ var submitParallelSteps = async ({
|
|
|
2148
2083
|
}
|
|
2149
2084
|
throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
|
|
2150
2085
|
};
|
|
2151
|
-
var
|
|
2086
|
+
var submitSingleStep = async ({
|
|
2087
|
+
context,
|
|
2152
2088
|
lazyStep,
|
|
2153
2089
|
stepId,
|
|
2090
|
+
invokeCount,
|
|
2154
2091
|
concurrency,
|
|
2092
|
+
telemetry,
|
|
2093
|
+
dispatchDebug,
|
|
2155
2094
|
dispatchLifecycle
|
|
2156
2095
|
}) => {
|
|
2157
2096
|
await dispatchLifecycle("beforeExecution", {
|
|
2158
2097
|
stepName: lazyStep.stepName
|
|
2159
2098
|
});
|
|
2099
|
+
let resultStep;
|
|
2160
2100
|
try {
|
|
2161
|
-
|
|
2101
|
+
resultStep = await lazyStep.getResultStep(concurrency, stepId);
|
|
2162
2102
|
} catch (error) {
|
|
2163
2103
|
attachStepNameToError(error, lazyStep.stepName);
|
|
2164
2104
|
throw error;
|
|
2165
2105
|
}
|
|
2166
|
-
};
|
|
2167
|
-
var submitStepResult = async ({
|
|
2168
|
-
context,
|
|
2169
|
-
lazyStep,
|
|
2170
|
-
resultStep,
|
|
2171
|
-
invokeCount,
|
|
2172
|
-
concurrency,
|
|
2173
|
-
telemetry,
|
|
2174
|
-
dispatchDebug,
|
|
2175
|
-
nextStepSettings
|
|
2176
|
-
}) => {
|
|
2177
2106
|
const { headers } = lazyStep.getHeaders({
|
|
2178
2107
|
context,
|
|
2179
2108
|
step: resultStep,
|
|
2180
2109
|
invokeCount,
|
|
2181
|
-
telemetry
|
|
2182
|
-
stepSettings: nextStepSettings
|
|
2110
|
+
telemetry
|
|
2183
2111
|
});
|
|
2184
2112
|
const body = lazyStep.getBody({
|
|
2185
2113
|
context,
|
|
@@ -2204,138 +2132,6 @@ var submitStepResult = async ({
|
|
|
2204
2132
|
}
|
|
2205
2133
|
return resultStep;
|
|
2206
2134
|
};
|
|
2207
|
-
var publishStepConfigRequest = async ({
|
|
2208
|
-
context,
|
|
2209
|
-
lazyStep,
|
|
2210
|
-
targetStep,
|
|
2211
|
-
invokeCount,
|
|
2212
|
-
telemetry,
|
|
2213
|
-
dispatchDebug
|
|
2214
|
-
}) => {
|
|
2215
|
-
const { headers } = getHeaders({
|
|
2216
|
-
initHeaderValue: "false",
|
|
2217
|
-
workflowConfig: {
|
|
2218
|
-
workflowRunId: context.workflowRunId,
|
|
2219
|
-
workflowUrl: context.url,
|
|
2220
|
-
telemetry
|
|
2221
|
-
},
|
|
2222
|
-
invokeCount,
|
|
2223
|
-
stepSettings: lazyStep.stepSettings
|
|
2224
|
-
});
|
|
2225
|
-
await dispatchDebug("onInfo", {
|
|
2226
|
-
info: `Step "${lazyStep.stepName}" (${targetStep}) has step-level settings which the current delivery does not carry. Requesting a delivery which does.`
|
|
2227
|
-
});
|
|
2228
|
-
const result = await context.qstashClient.publishJSON({
|
|
2229
|
-
headers: {
|
|
2230
|
-
...headers,
|
|
2231
|
-
"Upstash-Workflow-CallType": WORKFLOW_STEP_CONFIG_CALL_TYPE
|
|
2232
|
-
},
|
|
2233
|
-
method: "POST",
|
|
2234
|
-
body: { targetStep, invokeCount },
|
|
2235
|
-
url: context.url,
|
|
2236
|
-
contentBasedDeduplication: true
|
|
2237
|
-
});
|
|
2238
|
-
if (result?.deduplicated) {
|
|
2239
|
-
await dispatchDebug("onWarning", {
|
|
2240
|
-
warning: `A step config request for step "${lazyStep.stepName}" (${targetStep}) was already published and this one was deduplicated. Expected when the delivery which published it is being retried; otherwise the run stops here.`
|
|
2241
|
-
});
|
|
2242
|
-
} else if (result?.messageId) {
|
|
2243
|
-
await dispatchDebug("onInfo", {
|
|
2244
|
-
info: `Requested a delivery under the settings of step "${lazyStep.stepName}" with messageId: ${result.messageId}.`
|
|
2245
|
-
});
|
|
2246
|
-
}
|
|
2247
|
-
};
|
|
2248
|
-
|
|
2249
|
-
// src/qstash/step-config.ts
|
|
2250
|
-
var DEFAULT_PARALLELISM = 0;
|
|
2251
|
-
var DEFAULT_RATE = 0;
|
|
2252
|
-
var DEFAULT_PERIOD_SECONDS = 1;
|
|
2253
|
-
var SECONDS_PER_UNIT = {
|
|
2254
|
-
s: 1,
|
|
2255
|
-
m: 60,
|
|
2256
|
-
h: 60 * 60,
|
|
2257
|
-
d: 24 * 60 * 60
|
|
2258
|
-
};
|
|
2259
|
-
var durationToSeconds = (duration) => {
|
|
2260
|
-
if (typeof duration === "number") {
|
|
2261
|
-
return Number.isFinite(duration) ? duration : void 0;
|
|
2262
|
-
}
|
|
2263
|
-
const match = /^(\d+)([smhd])$/.exec(duration.trim());
|
|
2264
|
-
if (match) {
|
|
2265
|
-
return Number(match[1]) * SECONDS_PER_UNIT[match[2]];
|
|
2266
|
-
}
|
|
2267
|
-
const seconds = Number(duration.trim());
|
|
2268
|
-
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2269
|
-
};
|
|
2270
|
-
var normalizeFlowControl = (flowControl) => {
|
|
2271
|
-
const period = flowControl.period === void 0 ? void 0 : durationToSeconds(flowControl.period);
|
|
2272
|
-
return {
|
|
2273
|
-
key: flowControl.key,
|
|
2274
|
-
parallelism: flowControl.parallelism ?? DEFAULT_PARALLELISM,
|
|
2275
|
-
rate: flowControl.rate ?? flowControl.ratePerSecond ?? DEFAULT_RATE,
|
|
2276
|
-
period: period ?? DEFAULT_PERIOD_SECONDS
|
|
2277
|
-
};
|
|
2278
|
-
};
|
|
2279
|
-
var parseFlowControlHeaders = (key, value) => {
|
|
2280
|
-
if (!key) {
|
|
2281
|
-
return void 0;
|
|
2282
|
-
}
|
|
2283
|
-
const flowControl = {
|
|
2284
|
-
key,
|
|
2285
|
-
parallelism: DEFAULT_PARALLELISM,
|
|
2286
|
-
rate: DEFAULT_RATE,
|
|
2287
|
-
period: DEFAULT_PERIOD_SECONDS
|
|
2288
|
-
};
|
|
2289
|
-
for (const entry of (value ?? "").split(",")) {
|
|
2290
|
-
const [name, rawValue] = entry.split("=").map((part) => part.trim());
|
|
2291
|
-
if (!name || rawValue === void 0) {
|
|
2292
|
-
continue;
|
|
2293
|
-
}
|
|
2294
|
-
switch (name) {
|
|
2295
|
-
case "parallelism": {
|
|
2296
|
-
flowControl.parallelism = Number(rawValue);
|
|
2297
|
-
break;
|
|
2298
|
-
}
|
|
2299
|
-
case "rate": {
|
|
2300
|
-
flowControl.rate = Number(rawValue);
|
|
2301
|
-
break;
|
|
2302
|
-
}
|
|
2303
|
-
case "period": {
|
|
2304
|
-
flowControl.period = durationToSeconds(rawValue) ?? DEFAULT_PERIOD_SECONDS;
|
|
2305
|
-
break;
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
return flowControl;
|
|
2310
|
-
};
|
|
2311
|
-
var getEffectiveConfig = (headers) => {
|
|
2312
|
-
const retriesHeader = headers.get(RETRIES_HEADER);
|
|
2313
|
-
return {
|
|
2314
|
-
flowControl: parseFlowControlHeaders(
|
|
2315
|
-
headers.get(FLOW_CONTROL_KEY_HEADER),
|
|
2316
|
-
headers.get(FLOW_CONTROL_VALUE_HEADER)
|
|
2317
|
-
),
|
|
2318
|
-
retries: retriesHeader === null ? void 0 : Number(retriesHeader),
|
|
2319
|
-
retryDelay: headers.get(RETRY_DELAY_HEADER) ?? void 0,
|
|
2320
|
-
hasStepConfig: headers.get(WORKFLOW_STEP_CONFIG_HEADER) === "true"
|
|
2321
|
-
};
|
|
2322
|
-
};
|
|
2323
|
-
var describeStepSettingsMismatch = (stepSettings, effectiveConfig) => {
|
|
2324
|
-
if (stepSettings.flowControl) {
|
|
2325
|
-
const wanted = normalizeFlowControl(stepSettings.flowControl);
|
|
2326
|
-
const applied = effectiveConfig.flowControl;
|
|
2327
|
-
if (!applied || applied.key !== wanted.key || applied.parallelism !== wanted.parallelism || applied.rate !== wanted.rate || applied.period !== wanted.period) {
|
|
2328
|
-
return `flow control: expected ${JSON.stringify(wanted)}, delivery has ${JSON.stringify(applied)}`;
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
if (stepSettings.retries !== void 0 && effectiveConfig.retries !== void 0 && stepSettings.retries !== effectiveConfig.retries) {
|
|
2332
|
-
return `retries: expected ${stepSettings.retries}, delivery has ${effectiveConfig.retries}`;
|
|
2333
|
-
}
|
|
2334
|
-
if (stepSettings.retryDelay !== void 0 && stepSettings.retryDelay !== effectiveConfig.retryDelay) {
|
|
2335
|
-
return `retry delay: expected '${stepSettings.retryDelay}', delivery has '${effectiveConfig.retryDelay ?? ""}'`;
|
|
2336
|
-
}
|
|
2337
|
-
return void 0;
|
|
2338
|
-
};
|
|
2339
2135
|
|
|
2340
2136
|
// src/context/auto-executor.ts
|
|
2341
2137
|
var AutoExecutor = class _AutoExecutor {
|
|
@@ -2352,25 +2148,6 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2352
2148
|
stepCount = 0;
|
|
2353
2149
|
planStepCount = 0;
|
|
2354
2150
|
executingStep = false;
|
|
2355
|
-
/**
|
|
2356
|
-
* an executed step whose result hasn't been submitted to QStash yet.
|
|
2357
|
-
*
|
|
2358
|
-
* Set when a step which can produce its result in-process executes. The
|
|
2359
|
-
* submission waits so that the route function can continue and reveal
|
|
2360
|
-
* what comes next: if that is a single step with step-level settings,
|
|
2361
|
-
* the settings ride on this submission and no step config request is
|
|
2362
|
-
* needed, since the delivery of the submission is what executes it.
|
|
2363
|
-
*
|
|
2364
|
-
* Flushed when the next step is reached (see `addStep`) or when the
|
|
2365
|
-
* route function ends (see `flushPendingStep` in `serve`).
|
|
2366
|
-
*/
|
|
2367
|
-
pendingStep;
|
|
2368
|
-
/**
|
|
2369
|
-
* configuration QStash applied to the delivery being handled. A step
|
|
2370
|
-
* with step-level settings is settled by comparing its settings against
|
|
2371
|
-
* this.
|
|
2372
|
-
*/
|
|
2373
|
-
effectiveConfig;
|
|
2374
2151
|
/**
|
|
2375
2152
|
* @param context workflow context
|
|
2376
2153
|
* @param steps list of steps
|
|
@@ -2378,17 +2155,14 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2378
2155
|
* @param dispatchLifecycle lifecycle event dispatcher
|
|
2379
2156
|
* @param telemetry optional telemetry information
|
|
2380
2157
|
* @param invokeCount optional invoke count
|
|
2381
|
-
* @param effectiveConfig configuration QStash applied to the delivery
|
|
2382
|
-
* being handled
|
|
2383
2158
|
*/
|
|
2384
|
-
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount
|
|
2159
|
+
constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount) {
|
|
2385
2160
|
this.context = context;
|
|
2386
2161
|
this.steps = steps;
|
|
2387
2162
|
this.dispatchDebug = dispatchDebug;
|
|
2388
2163
|
this.dispatchLifecycle = dispatchLifecycle;
|
|
2389
2164
|
this.telemetry = telemetry;
|
|
2390
2165
|
this.invokeCount = invokeCount ?? 0;
|
|
2391
|
-
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
2392
2166
|
this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
|
|
2393
2167
|
}
|
|
2394
2168
|
/**
|
|
@@ -2423,15 +2197,6 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2423
2197
|
lazyStepList.push(stepInfo);
|
|
2424
2198
|
const index = this.indexInCurrentList++;
|
|
2425
2199
|
const requestComplete = this.deferExecution().then(async () => {
|
|
2426
|
-
const submitted = await this.submitPendingStep(
|
|
2427
|
-
lazyStepList.length === 1 ? lazyStepList[0].stepSettings : void 0
|
|
2428
|
-
);
|
|
2429
|
-
if (submitted.isErr()) {
|
|
2430
|
-
throw submitted.error;
|
|
2431
|
-
}
|
|
2432
|
-
if (submitted.value.result === "submitted-step") {
|
|
2433
|
-
throw submitted.value.abort;
|
|
2434
|
-
}
|
|
2435
2200
|
if (!this.promises.has(lazyStepList)) {
|
|
2436
2201
|
const promise2 = this.getExecutionPromise(lazyStepList);
|
|
2437
2202
|
this.promises.set(lazyStepList, promise2);
|
|
@@ -2485,100 +2250,18 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2485
2250
|
}
|
|
2486
2251
|
return parsedOut;
|
|
2487
2252
|
}
|
|
2488
|
-
const
|
|
2489
|
-
if (mismatch) {
|
|
2490
|
-
if (!this.effectiveConfig.hasStepConfig) {
|
|
2491
|
-
await publishStepConfigRequest({
|
|
2492
|
-
context: this.context,
|
|
2493
|
-
lazyStep,
|
|
2494
|
-
targetStep: this.stepCount,
|
|
2495
|
-
invokeCount: this.invokeCount,
|
|
2496
|
-
telemetry: this.telemetry,
|
|
2497
|
-
dispatchDebug: this.dispatchDebug
|
|
2498
|
-
});
|
|
2499
|
-
throw new WorkflowAbort(lazyStep.stepName);
|
|
2500
|
-
} else {
|
|
2501
|
-
await this.dispatchDebug("onWarning", {
|
|
2502
|
-
warning: `The step-level settings of step '${lazyStep.stepName}' were published but are not recognized on the delivery which carries them (${mismatch}). Executing the step with the settings of the delivery instead. This is a bug in @upstash/workflow, please report it.`
|
|
2503
|
-
});
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
const resultStep = await executeStep({
|
|
2507
|
-
lazyStep,
|
|
2508
|
-
stepId: this.stepCount,
|
|
2509
|
-
concurrency: NO_CONCURRENCY,
|
|
2510
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2511
|
-
});
|
|
2512
|
-
if (lazyStep.supportsDeferredSubmission) {
|
|
2513
|
-
this.pendingStep = { status: "held", lazyStep, resultStep };
|
|
2514
|
-
return lazyStep.parseOut({
|
|
2515
|
-
...resultStep,
|
|
2516
|
-
out: resultStep.out === void 0 ? void 0 : JSON.stringify(resultStep.out)
|
|
2517
|
-
});
|
|
2518
|
-
}
|
|
2519
|
-
await submitStepResult({
|
|
2253
|
+
const resultStep = await submitSingleStep({
|
|
2520
2254
|
context: this.context,
|
|
2521
2255
|
lazyStep,
|
|
2522
|
-
|
|
2256
|
+
stepId: this.stepCount,
|
|
2523
2257
|
invokeCount: this.invokeCount,
|
|
2524
|
-
concurrency:
|
|
2258
|
+
concurrency: 1,
|
|
2525
2259
|
telemetry: this.telemetry,
|
|
2526
|
-
dispatchDebug: this.dispatchDebug
|
|
2260
|
+
dispatchDebug: this.dispatchDebug,
|
|
2261
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2527
2262
|
});
|
|
2528
2263
|
throw new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2529
2264
|
}
|
|
2530
|
-
/**
|
|
2531
|
-
* Submits the result of the step being held, if there is one, and
|
|
2532
|
-
* returns the abort the caller has to throw to end the invocation.
|
|
2533
|
-
*
|
|
2534
|
-
* Returns undefined when no step is being held, which is the caller's
|
|
2535
|
-
* signal that there is nothing to end. Throws instead of returning
|
|
2536
|
-
* when the submission itself fails, so that error reaches the caller
|
|
2537
|
-
* rather than an abort claiming the step was submitted.
|
|
2538
|
-
*
|
|
2539
|
-
* Callers which arrive while a submission is already under way (the
|
|
2540
|
-
* steps of a parallel group, added together) wait for that one and get
|
|
2541
|
-
* the same abort, rather than submitting a second time.
|
|
2542
|
-
*
|
|
2543
|
-
* The next step's settings are attached whenever it has any, without
|
|
2544
|
-
* checking them against the current delivery: the executor only knows
|
|
2545
|
-
* the configuration of the delivery in hand, which inside a
|
|
2546
|
-
* delivery is the *previous* step's, so such a check would be wrong
|
|
2547
|
-
* exactly when two steps with settings follow each other.
|
|
2548
|
-
*
|
|
2549
|
-
* @param nextStepSettings step-level settings of the next step
|
|
2550
|
-
* @returns `submitted-step` with the abort which ends this invocation,
|
|
2551
|
-
* or `no-pending-step` when nothing was held and there is nothing to
|
|
2552
|
-
* end
|
|
2553
|
-
*/
|
|
2554
|
-
async submitPendingStep(nextStepSettings) {
|
|
2555
|
-
if (!this.pendingStep) {
|
|
2556
|
-
return ok({ result: "no-pending-step" });
|
|
2557
|
-
}
|
|
2558
|
-
try {
|
|
2559
|
-
if (this.pendingStep.status === "submitting") {
|
|
2560
|
-
return ok({ result: "submitted-step", abort: await this.pendingStep.submitted });
|
|
2561
|
-
}
|
|
2562
|
-
const { lazyStep, resultStep } = this.pendingStep;
|
|
2563
|
-
const submitted = (async () => {
|
|
2564
|
-
await submitStepResult({
|
|
2565
|
-
context: this.context,
|
|
2566
|
-
lazyStep,
|
|
2567
|
-
resultStep,
|
|
2568
|
-
invokeCount: this.invokeCount,
|
|
2569
|
-
concurrency: NO_CONCURRENCY,
|
|
2570
|
-
telemetry: this.telemetry,
|
|
2571
|
-
dispatchDebug: this.dispatchDebug,
|
|
2572
|
-
nextStepSettings
|
|
2573
|
-
});
|
|
2574
|
-
return new WorkflowAbort(lazyStep.stepName, resultStep);
|
|
2575
|
-
})();
|
|
2576
|
-
this.pendingStep = { status: "submitting", submitted };
|
|
2577
|
-
return ok({ result: "submitted-step", abort: await submitted });
|
|
2578
|
-
} catch (error) {
|
|
2579
|
-
return err(error);
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
2265
|
/**
|
|
2583
2266
|
* Runs steps in parallel.
|
|
2584
2267
|
*
|
|
@@ -2627,20 +2310,15 @@ var AutoExecutor = class _AutoExecutor {
|
|
|
2627
2310
|
validateStep(parallelSteps[stepIndex], planStep);
|
|
2628
2311
|
try {
|
|
2629
2312
|
const parallelStep = parallelSteps[stepIndex];
|
|
2630
|
-
const resultStep = await
|
|
2631
|
-
lazyStep: parallelStep,
|
|
2632
|
-
stepId: planStep.targetStep,
|
|
2633
|
-
concurrency: parallelSteps.length,
|
|
2634
|
-
dispatchLifecycle: this.dispatchLifecycle
|
|
2635
|
-
});
|
|
2636
|
-
await submitStepResult({
|
|
2313
|
+
const resultStep = await submitSingleStep({
|
|
2637
2314
|
context: this.context,
|
|
2638
2315
|
lazyStep: parallelStep,
|
|
2639
|
-
|
|
2316
|
+
stepId: planStep.targetStep,
|
|
2640
2317
|
invokeCount: this.invokeCount,
|
|
2641
2318
|
concurrency: parallelSteps.length,
|
|
2642
2319
|
telemetry: this.telemetry,
|
|
2643
|
-
dispatchDebug: this.dispatchDebug
|
|
2320
|
+
dispatchDebug: this.dispatchDebug,
|
|
2321
|
+
dispatchLifecycle: this.dispatchLifecycle
|
|
2644
2322
|
});
|
|
2645
2323
|
throw new WorkflowAbort(parallelStep.stepName, resultStep);
|
|
2646
2324
|
} catch (error) {
|
|
@@ -3181,46 +2859,6 @@ var WorkflowContext = class {
|
|
|
3181
2859
|
* `1` on the first retry, `2` on the second, and so on.
|
|
3182
2860
|
*/
|
|
3183
2861
|
retried;
|
|
3184
|
-
/**
|
|
3185
|
-
* Configuration QStash applied to the request being handled.
|
|
3186
|
-
*
|
|
3187
|
-
* This is the configuration of *this delivery*, not of the run: it is
|
|
3188
|
-
* the configuration the run was triggered with, except inside the
|
|
3189
|
-
* delivery which executes a step that has step-level settings, where
|
|
3190
|
-
* it is that step's settings. The fields below expose it; the executor
|
|
3191
|
-
* gets it directly rather than reaching through the context.
|
|
3192
|
-
*/
|
|
3193
|
-
effectiveConfig;
|
|
3194
|
-
/**
|
|
3195
|
-
* Flow control QStash applied to the request being handled, if any.
|
|
3196
|
-
*
|
|
3197
|
-
* This is the flow control of the request in hand, which is the one the
|
|
3198
|
-
* run was triggered with except inside a step carrying its own.
|
|
3199
|
-
*/
|
|
3200
|
-
get flowControl() {
|
|
3201
|
-
return this.effectiveConfig.flowControl;
|
|
3202
|
-
}
|
|
3203
|
-
/**
|
|
3204
|
-
* Retry limit QStash applied to the request being handled, if it
|
|
3205
|
-
* reported one.
|
|
3206
|
-
*
|
|
3207
|
-
* Not to be confused with {@link retried}, which is how many retries
|
|
3208
|
-
* have already happened.
|
|
3209
|
-
*
|
|
3210
|
-
* @see {@link flowControl} for what "the request being handled" means
|
|
3211
|
-
*/
|
|
3212
|
-
get retries() {
|
|
3213
|
-
return this.effectiveConfig.retries;
|
|
3214
|
-
}
|
|
3215
|
-
/**
|
|
3216
|
-
* Retry delay expression QStash applied to the request being handled,
|
|
3217
|
-
* if any.
|
|
3218
|
-
*
|
|
3219
|
-
* @see {@link flowControl} for what "the request being handled" means
|
|
3220
|
-
*/
|
|
3221
|
-
get retryDelay() {
|
|
3222
|
-
return this.effectiveConfig.retryDelay;
|
|
3223
|
-
}
|
|
3224
2862
|
constructor({
|
|
3225
2863
|
qstashClient,
|
|
3226
2864
|
workflowRunId,
|
|
@@ -3234,8 +2872,7 @@ var WorkflowContext = class {
|
|
|
3234
2872
|
invokeCount,
|
|
3235
2873
|
label,
|
|
3236
2874
|
retried,
|
|
3237
|
-
middlewareManager
|
|
3238
|
-
effectiveConfig
|
|
2875
|
+
middlewareManager
|
|
3239
2876
|
}) {
|
|
3240
2877
|
this.qstashClient = qstashClient;
|
|
3241
2878
|
this.workflowRunId = workflowRunId;
|
|
@@ -3247,7 +2884,6 @@ var WorkflowContext = class {
|
|
|
3247
2884
|
this.env = env ?? {};
|
|
3248
2885
|
this.labels = label === void 0 ? [] : Array.isArray(label) ? label : label ? label.split(",") : [];
|
|
3249
2886
|
this.retried = retried ?? 0;
|
|
3250
|
-
this.effectiveConfig = effectiveConfig ?? { hasStepConfig: false };
|
|
3251
2887
|
const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
|
|
3252
2888
|
middlewareManagerInstance.assignContext(this);
|
|
3253
2889
|
this.executor = new AutoExecutor(
|
|
@@ -3256,8 +2892,7 @@ var WorkflowContext = class {
|
|
|
3256
2892
|
middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
|
|
3257
2893
|
middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
|
|
3258
2894
|
telemetry,
|
|
3259
|
-
invokeCount
|
|
3260
|
-
this.effectiveConfig
|
|
2895
|
+
invokeCount
|
|
3261
2896
|
);
|
|
3262
2897
|
}
|
|
3263
2898
|
/**
|
|
@@ -3283,35 +2918,13 @@ var WorkflowContext = class {
|
|
|
3283
2918
|
* ])
|
|
3284
2919
|
* ```
|
|
3285
2920
|
*
|
|
3286
|
-
* Step-level settings can be passed as a third argument, overriding
|
|
3287
|
-
* the settings the workflow run was triggered with for this step only:
|
|
3288
|
-
*
|
|
3289
|
-
* ```typescript
|
|
3290
|
-
* const result = await context.run(
|
|
3291
|
-
* "step 1",
|
|
3292
|
-
* () => {
|
|
3293
|
-
* return "result"
|
|
3294
|
-
* },
|
|
3295
|
-
* {
|
|
3296
|
-
* flowControl: { key: "custom-key", parallelism: 3 },
|
|
3297
|
-
* retries: 5,
|
|
3298
|
-
* }
|
|
3299
|
-
* )
|
|
3300
|
-
* ```
|
|
3301
|
-
*
|
|
3302
2921
|
* @param stepName name of the step
|
|
3303
2922
|
* @param stepFunction step function to be executed
|
|
3304
|
-
* @param stepSettings step-level settings for this step
|
|
3305
2923
|
* @returns result of the step function
|
|
3306
2924
|
*/
|
|
3307
|
-
async run(stepName, stepFunction
|
|
3308
|
-
if (stepSettings) {
|
|
3309
|
-
validateFlowControl(stepSettings.flowControl);
|
|
3310
|
-
}
|
|
2925
|
+
async run(stepName, stepFunction) {
|
|
3311
2926
|
const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
|
|
3312
|
-
return await this.addStep(
|
|
3313
|
-
new LazyFunctionStep(this, stepName, wrappedStepFunction, stepSettings)
|
|
3314
|
-
);
|
|
2927
|
+
return await this.addStep(new LazyFunctionStep(this, stepName, wrappedStepFunction));
|
|
3315
2928
|
}
|
|
3316
2929
|
/**
|
|
3317
2930
|
* Stops the execution for the duration provided.
|
|
@@ -3611,7 +3224,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3611
3224
|
*
|
|
3612
3225
|
* @param routeFunction
|
|
3613
3226
|
*/
|
|
3614
|
-
static async tryAuthentication(routeFunction, context
|
|
3227
|
+
static async tryAuthentication(routeFunction, context) {
|
|
3615
3228
|
const disabledContext = new _DisabledWorkflowContext({
|
|
3616
3229
|
qstashClient: new Client2({
|
|
3617
3230
|
baseUrl: "disabled-client",
|
|
@@ -3626,10 +3239,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
|
|
|
3626
3239
|
initialPayload: context.requestPayload,
|
|
3627
3240
|
env: context.env,
|
|
3628
3241
|
label: context.labels,
|
|
3629
|
-
retried: context.retried
|
|
3630
|
-
// the route function runs for real here until it reaches a step, so
|
|
3631
|
-
// it observes the same configuration as it will on the real context
|
|
3632
|
-
effectiveConfig
|
|
3242
|
+
retried: context.retried
|
|
3633
3243
|
});
|
|
3634
3244
|
try {
|
|
3635
3245
|
await routeFunction(disabledContext);
|
|
@@ -3846,7 +3456,6 @@ var handleFailure = async ({
|
|
|
3846
3456
|
}
|
|
3847
3457
|
const userHeaders = recreateUserHeaders(request.headers);
|
|
3848
3458
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
3849
|
-
const effectiveConfig = getEffectiveConfig(request.headers);
|
|
3850
3459
|
const workflowContext = new WorkflowContext({
|
|
3851
3460
|
qstashClient,
|
|
3852
3461
|
workflowRunId,
|
|
@@ -3860,13 +3469,11 @@ var handleFailure = async ({
|
|
|
3860
3469
|
label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
|
|
3861
3470
|
retried,
|
|
3862
3471
|
workflowRunCreatedAt: workflowCreatedAt,
|
|
3863
|
-
middlewareManager: void 0
|
|
3864
|
-
effectiveConfig
|
|
3472
|
+
middlewareManager: void 0
|
|
3865
3473
|
});
|
|
3866
3474
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
3867
3475
|
routeFunction,
|
|
3868
|
-
workflowContext
|
|
3869
|
-
effectiveConfig
|
|
3476
|
+
workflowContext
|
|
3870
3477
|
);
|
|
3871
3478
|
if (authCheck.isErr()) {
|
|
3872
3479
|
await dispatchDebug?.("onError", {
|
|
@@ -4281,28 +3888,26 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
4281
3888
|
const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
|
|
4282
3889
|
const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
|
|
4283
3890
|
const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
|
|
4284
|
-
const
|
|
4285
|
-
const
|
|
3891
|
+
const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
|
|
3892
|
+
const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
|
|
4286
3893
|
const workflowContext = new WorkflowContext({
|
|
4287
3894
|
qstashClient: regionalClient,
|
|
4288
3895
|
workflowRunId,
|
|
4289
3896
|
initialPayload: isThirdPartyCallResult(request) ? JSON.parse(rawInitialPayload) : initialPayloadParser(rawInitialPayload),
|
|
4290
3897
|
headers: recreateUserHeaders(request.headers),
|
|
4291
3898
|
steps,
|
|
4292
|
-
effectiveConfig,
|
|
4293
3899
|
url: workflowUrl,
|
|
4294
3900
|
env,
|
|
4295
3901
|
telemetry,
|
|
4296
3902
|
invokeCount,
|
|
4297
3903
|
label,
|
|
4298
3904
|
retried,
|
|
4299
|
-
workflowRunCreatedAt
|
|
3905
|
+
workflowRunCreatedAt,
|
|
4300
3906
|
middlewareManager
|
|
4301
3907
|
});
|
|
4302
3908
|
const authCheck = await DisabledWorkflowContext.tryAuthentication(
|
|
4303
3909
|
routeFunction,
|
|
4304
|
-
workflowContext
|
|
4305
|
-
effectiveConfig
|
|
3910
|
+
workflowContext
|
|
4306
3911
|
);
|
|
4307
3912
|
if (authCheck.isErr()) {
|
|
4308
3913
|
throw authCheck.error;
|
|
@@ -4339,23 +3944,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
|
|
|
4339
3944
|
if (steps.length === 1) {
|
|
4340
3945
|
await middlewareManager.dispatchLifecycle("runStarted", {});
|
|
4341
3946
|
}
|
|
4342
|
-
|
|
4343
|
-
try {
|
|
4344
|
-
outcome = { ran: true, result: await routeFunction(workflowContext) };
|
|
4345
|
-
} catch (error) {
|
|
4346
|
-
outcome = { ran: false, error };
|
|
4347
|
-
}
|
|
4348
|
-
const submitted = await flushPendingStep(workflowContext);
|
|
4349
|
-
if (submitted.isErr()) {
|
|
4350
|
-
throw submitted.error;
|
|
4351
|
-
}
|
|
4352
|
-
if (submitted.value.result === "submitted-step") {
|
|
4353
|
-
throw submitted.value.abort;
|
|
4354
|
-
}
|
|
4355
|
-
if (!outcome.ran) {
|
|
4356
|
-
throw outcome.error;
|
|
4357
|
-
}
|
|
4358
|
-
return outcome.result;
|
|
3947
|
+
return await routeFunction(workflowContext);
|
|
4359
3948
|
},
|
|
4360
3949
|
onCleanup: async (result2) => {
|
|
4361
3950
|
await middlewareManager.dispatchLifecycle("runCompleted", {
|