@upstash/workflow 1.3.3 → 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.js CHANGED
@@ -26,6 +26,9 @@ __export(astro_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(astro_exports);
28
28
 
29
+ // src/serve/index.ts
30
+ var import_qstash12 = require("@upstash/qstash");
31
+
29
32
  // src/client/utils.ts
30
33
  var import_qstash2 = require("@upstash/qstash");
31
34
 
@@ -781,6 +784,10 @@ var triggerFirstInvocation = async (params) => {
781
784
  workflowConfig: {
782
785
  workflowRunId: workflowContext.workflowRunId,
783
786
  workflowUrl: workflowContext.url,
787
+ // the run doesn't exist yet, so the trigger time is sent when the creation time
788
+ // is not known. serve and client.trigger set it on the context already, so a
789
+ // retry of this request carries the same value and is deduplicated.
790
+ workflowRunCreatedAt: workflowContext.workflowRunCreatedAt || Date.now(),
784
791
  failureUrl,
785
792
  retries,
786
793
  retryDelay,
@@ -884,7 +891,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
884
891
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
885
892
  method: "DELETE",
886
893
  parseResponseAsJson: false,
887
- body: JSON.stringify(result)
894
+ body: JSON.stringify(result),
895
+ // with the creation time, QStash deletes the run only if it is the same run.
896
+ // a late delete of a previous run can't delete a new run with the same id.
897
+ headers: workflowContext.workflowRunCreatedAt ? { [WORKFLOW_CREATED_AT_HEADER]: workflowContext.workflowRunCreatedAt.toString() } : void 0
888
898
  });
889
899
  await dispatchDebug?.("onInfo", {
890
900
  info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
@@ -961,6 +971,7 @@ ${atob(callbackMessage.body ?? "")}`
961
971
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
962
972
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
963
973
  const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
974
+ const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
964
975
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
965
976
  throw new Error(
966
977
  `Missing info in callback message source header: ${JSON.stringify({
@@ -978,6 +989,7 @@ ${atob(callbackMessage.body ?? "")}`
978
989
  workflowConfig: {
979
990
  workflowRunId,
980
991
  workflowUrl,
992
+ workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
981
993
  telemetry: telemetry2
982
994
  },
983
995
  invokeCount: Number(invokeCount)
@@ -1125,6 +1137,7 @@ var BaseLazyStep = class _BaseLazyStep {
1125
1137
  workflowConfig: {
1126
1138
  workflowRunId: context.workflowRunId,
1127
1139
  workflowUrl: context.url,
1140
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1128
1141
  useJSONContent: false,
1129
1142
  telemetry: telemetry2
1130
1143
  },
@@ -1452,6 +1465,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
1452
1465
  ) : {},
1453
1466
  // note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
1454
1467
  "Upstash-Workflow-Runid": [context.workflowRunId],
1468
+ // same for WORKFLOW_CREATED_AT_HEADER, because Createdat -> CreatedAt:
1469
+ ...context.workflowRunCreatedAt ? { "Upstash-Workflow-Createdat": [context.workflowRunCreatedAt.toString()] } : {},
1455
1470
  [WORKFLOW_INIT_HEADER]: ["false"],
1456
1471
  [WORKFLOW_URL_HEADER]: [context.url],
1457
1472
  "Upstash-Workflow-CallType": ["step"]
@@ -1554,6 +1569,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1554
1569
  workflowConfig: {
1555
1570
  workflowRunId: context.workflowRunId,
1556
1571
  workflowUrl: context.url,
1572
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1557
1573
  telemetry: telemetry2,
1558
1574
  useJSONContent: false
1559
1575
  },
@@ -1563,6 +1579,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1563
1579
  invokerHeaders[key] = value;
1564
1580
  });
1565
1581
  invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1582
+ if (context.workflowRunCreatedAt) {
1583
+ invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
1584
+ }
1566
1585
  const request = {
1567
1586
  body: stringifyBody(this.params.body),
1568
1587
  headers: Object.fromEntries(
@@ -1591,6 +1610,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1591
1610
  workflowConfig: {
1592
1611
  workflowRunId: getWorkflowRunId(workflowRunId),
1593
1612
  workflowUrl: newUrl,
1613
+ // the invoked run doesn't exist yet, so the creation time of the invoker run is sent.
1614
+ // it separates the invocations made by different runs of the invoker.
1615
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1594
1616
  retries,
1595
1617
  retryDelay,
1596
1618
  telemetry: telemetry2,
@@ -1755,6 +1777,9 @@ var WorkflowHeaders = class {
1755
1777
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1756
1778
  ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
1757
1779
  };
1780
+ if (this.workflowConfig.workflowRunCreatedAt) {
1781
+ this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
1782
+ }
1758
1783
  if (this.stepInfo?.lazyStep.stepType !== "Call") {
1759
1784
  this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1760
1785
  }
@@ -1914,6 +1939,7 @@ var submitParallelSteps = async ({
1914
1939
  workflowConfig: {
1915
1940
  workflowRunId: context.workflowRunId,
1916
1941
  workflowUrl: context.url,
1942
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1917
1943
  telemetry: telemetry2
1918
1944
  },
1919
1945
  invokeCount
@@ -3415,7 +3441,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
3415
3441
  const client = new import_qstash11.Client({
3416
3442
  ...clientOptions,
3417
3443
  baseUrl: clientEnv.QSTASH_URL,
3418
- token: clientEnv.QSTASH_TOKEN
3444
+ token: clientEnv.QSTASH_TOKEN,
3445
+ // Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
3446
+ // the client at the local dev server too. Without this the QStash Client
3447
+ // re-derives dev mode from its own process.env, which is blind to the
3448
+ // Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
3449
+ // An explicit devMode in the user's client config still wins.
3450
+ devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
3419
3451
  });
3420
3452
  const receiver = getReceiver(environment, receiverConfig, region);
3421
3453
  return { client, receiver };
@@ -3465,7 +3497,9 @@ var getQStashHandlers = ({
3465
3497
  client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
3466
3498
  ...qstashClientOption,
3467
3499
  baseUrl: environment.QSTASH_URL,
3468
- token: environment.QSTASH_TOKEN
3500
+ token: environment.QSTASH_TOKEN,
3501
+ // Mirror the receiver's dev-mode decision (see createRegionalHandler).
3502
+ devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
3469
3503
  }),
3470
3504
  receiver: getReceiver(environment, receiverConfig)
3471
3505
  }
@@ -3804,7 +3838,8 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3804
3838
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3805
3839
  const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
3806
3840
  const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3807
- const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3841
+ const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3842
+ const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
3808
3843
  const workflowContext = new WorkflowContext({
3809
3844
  qstashClient: regionalClient,
3810
3845
  workflowRunId,
@@ -3817,7 +3852,7 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3817
3852
  invokeCount,
3818
3853
  label,
3819
3854
  retried,
3820
- workflowRunCreatedAt: Number(workflowRunCreatedAt),
3855
+ workflowRunCreatedAt,
3821
3856
  middlewareManager
3822
3857
  });
3823
3858
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
@@ -3940,12 +3975,14 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3940
3975
  };
3941
3976
 
3942
3977
  // platforms/astro.ts
3978
+ var import_qstash13 = require("@upstash/qstash");
3943
3979
  var telemetry = {
3944
3980
  sdk: SDK_TELEMETRY,
3945
3981
  framework: "astro",
3946
3982
  runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
3947
3983
  };
3948
3984
  function serve(routeFunction, options) {
3985
+ void (0, import_qstash13.startDevServer)();
3949
3986
  const POST = (apiContext) => {
3950
3987
  const { handler } = serveBase(
3951
3988
  (workflowContext) => routeFunction(workflowContext, apiContext),
package/astro.mjs CHANGED
@@ -2,15 +2,17 @@ import {
2
2
  SDK_TELEMETRY,
3
3
  serveBase,
4
4
  serveManyBase
5
- } from "./chunk-WEQGCISK.mjs";
5
+ } from "./chunk-DRHEHWMS.mjs";
6
6
 
7
7
  // platforms/astro.ts
8
+ import { startDevServer } from "@upstash/qstash";
8
9
  var telemetry = {
9
10
  sdk: SDK_TELEMETRY,
10
11
  framework: "astro",
11
12
  runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
12
13
  };
13
14
  function serve(routeFunction, options) {
15
+ void startDevServer();
14
16
  const POST = (apiContext) => {
15
17
  const { handler } = serveBase(
16
18
  (workflowContext) => routeFunction(workflowContext, apiContext),
@@ -906,6 +906,10 @@ var triggerFirstInvocation = async (params) => {
906
906
  workflowConfig: {
907
907
  workflowRunId: workflowContext.workflowRunId,
908
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(),
909
913
  failureUrl,
910
914
  retries,
911
915
  retryDelay,
@@ -1009,7 +1013,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
1009
1013
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
1010
1014
  method: "DELETE",
1011
1015
  parseResponseAsJson: false,
1012
- 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
1013
1020
  });
1014
1021
  await dispatchDebug?.("onInfo", {
1015
1022
  info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
@@ -1086,6 +1093,7 @@ ${atob(callbackMessage.body ?? "")}`
1086
1093
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
1087
1094
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
1088
1095
  const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
1096
+ const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
1089
1097
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
1090
1098
  throw new Error(
1091
1099
  `Missing info in callback message source header: ${JSON.stringify({
@@ -1103,6 +1111,7 @@ ${atob(callbackMessage.body ?? "")}`
1103
1111
  workflowConfig: {
1104
1112
  workflowRunId,
1105
1113
  workflowUrl,
1114
+ workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
1106
1115
  telemetry
1107
1116
  },
1108
1117
  invokeCount: Number(invokeCount)
@@ -1250,6 +1259,7 @@ var BaseLazyStep = class _BaseLazyStep {
1250
1259
  workflowConfig: {
1251
1260
  workflowRunId: context.workflowRunId,
1252
1261
  workflowUrl: context.url,
1262
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1253
1263
  useJSONContent: false,
1254
1264
  telemetry
1255
1265
  },
@@ -1577,6 +1587,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
1577
1587
  ) : {},
1578
1588
  // note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
1579
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()] } : {},
1580
1592
  [WORKFLOW_INIT_HEADER]: ["false"],
1581
1593
  [WORKFLOW_URL_HEADER]: [context.url],
1582
1594
  "Upstash-Workflow-CallType": ["step"]
@@ -1679,6 +1691,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1679
1691
  workflowConfig: {
1680
1692
  workflowRunId: context.workflowRunId,
1681
1693
  workflowUrl: context.url,
1694
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1682
1695
  telemetry,
1683
1696
  useJSONContent: false
1684
1697
  },
@@ -1688,6 +1701,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1688
1701
  invokerHeaders[key] = value;
1689
1702
  });
1690
1703
  invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1704
+ if (context.workflowRunCreatedAt) {
1705
+ invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
1706
+ }
1691
1707
  const request = {
1692
1708
  body: stringifyBody(this.params.body),
1693
1709
  headers: Object.fromEntries(
@@ -1716,6 +1732,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1716
1732
  workflowConfig: {
1717
1733
  workflowRunId: getWorkflowRunId(workflowRunId),
1718
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,
1719
1738
  retries,
1720
1739
  retryDelay,
1721
1740
  telemetry,
@@ -1880,6 +1899,9 @@ var WorkflowHeaders = class {
1880
1899
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1881
1900
  ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
1882
1901
  };
1902
+ if (this.workflowConfig.workflowRunCreatedAt) {
1903
+ this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
1904
+ }
1883
1905
  if (this.stepInfo?.lazyStep.stepType !== "Call") {
1884
1906
  this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1885
1907
  }
@@ -2039,6 +2061,7 @@ var submitParallelSteps = async ({
2039
2061
  workflowConfig: {
2040
2062
  workflowRunId: context.workflowRunId,
2041
2063
  workflowUrl: context.url,
2064
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
2042
2065
  telemetry
2043
2066
  },
2044
2067
  invokeCount
@@ -3167,6 +3190,9 @@ var loggingMiddleware = new WorkflowMiddleware({
3167
3190
  }
3168
3191
  });
3169
3192
 
3193
+ // src/serve/index.ts
3194
+ import { startDevServer } from "@upstash/qstash";
3195
+
3170
3196
  // src/serve/authorization.ts
3171
3197
  import { Client as Client2 } from "@upstash/qstash";
3172
3198
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
@@ -3539,7 +3565,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
3539
3565
  const client = new Client3({
3540
3566
  ...clientOptions,
3541
3567
  baseUrl: clientEnv.QSTASH_URL,
3542
- token: clientEnv.QSTASH_TOKEN
3568
+ token: clientEnv.QSTASH_TOKEN,
3569
+ // Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
3570
+ // the client at the local dev server too. Without this the QStash Client
3571
+ // re-derives dev mode from its own process.env, which is blind to the
3572
+ // Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
3573
+ // An explicit devMode in the user's client config still wins.
3574
+ devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
3543
3575
  });
3544
3576
  const receiver = getReceiver(environment, receiverConfig, region);
3545
3577
  return { client, receiver };
@@ -3589,7 +3621,9 @@ var getQStashHandlers = ({
3589
3621
  client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new Client3({
3590
3622
  ...qstashClientOption,
3591
3623
  baseUrl: environment.QSTASH_URL,
3592
- token: environment.QSTASH_TOKEN
3624
+ token: environment.QSTASH_TOKEN,
3625
+ // Mirror the receiver's dev-mode decision (see createRegionalHandler).
3626
+ devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
3593
3627
  }),
3594
3628
  receiver: getReceiver(environment, receiverConfig)
3595
3629
  }
@@ -3854,7 +3888,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3854
3888
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3855
3889
  const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
3856
3890
  const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3857
- const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3891
+ const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3892
+ const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
3858
3893
  const workflowContext = new WorkflowContext({
3859
3894
  qstashClient: regionalClient,
3860
3895
  workflowRunId,
@@ -3867,7 +3902,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3867
3902
  invokeCount,
3868
3903
  label,
3869
3904
  retried,
3870
- workflowRunCreatedAt: Number(workflowRunCreatedAt),
3905
+ workflowRunCreatedAt,
3871
3906
  middlewareManager
3872
3907
  });
3873
3908
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
@@ -3989,6 +4024,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3989
4024
  return { handler: safeHandler };
3990
4025
  };
3991
4026
  var serve = (routeFunction, options) => {
4027
+ void startDevServer();
3992
4028
  return serveBase(
3993
4029
  routeFunction,
3994
4030
  {
@@ -4004,6 +4040,7 @@ export {
4004
4040
  WorkflowAbort,
4005
4041
  WorkflowNonRetryableError,
4006
4042
  WorkflowRetryAfterError,
4043
+ isInstanceOf,
4007
4044
  assertNonEmptyId,
4008
4045
  toNonEmptyIdArray,
4009
4046
  makeNotifyRequest,
package/cloudflare.js CHANGED
@@ -51,6 +51,9 @@ var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
51
51
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
52
52
  var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
53
53
 
54
+ // src/serve/index.ts
55
+ var import_qstash12 = require("@upstash/qstash");
56
+
54
57
  // src/client/utils.ts
55
58
  var import_qstash2 = require("@upstash/qstash");
56
59
 
@@ -781,6 +784,10 @@ var triggerFirstInvocation = async (params) => {
781
784
  workflowConfig: {
782
785
  workflowRunId: workflowContext.workflowRunId,
783
786
  workflowUrl: workflowContext.url,
787
+ // the run doesn't exist yet, so the trigger time is sent when the creation time
788
+ // is not known. serve and client.trigger set it on the context already, so a
789
+ // retry of this request carries the same value and is deduplicated.
790
+ workflowRunCreatedAt: workflowContext.workflowRunCreatedAt || Date.now(),
784
791
  failureUrl,
785
792
  retries,
786
793
  retryDelay,
@@ -884,7 +891,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
884
891
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
885
892
  method: "DELETE",
886
893
  parseResponseAsJson: false,
887
- body: JSON.stringify(result)
894
+ body: JSON.stringify(result),
895
+ // with the creation time, QStash deletes the run only if it is the same run.
896
+ // a late delete of a previous run can't delete a new run with the same id.
897
+ headers: workflowContext.workflowRunCreatedAt ? { [WORKFLOW_CREATED_AT_HEADER]: workflowContext.workflowRunCreatedAt.toString() } : void 0
888
898
  });
889
899
  await dispatchDebug?.("onInfo", {
890
900
  info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
@@ -961,6 +971,7 @@ ${atob(callbackMessage.body ?? "")}`
961
971
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
962
972
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
963
973
  const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
974
+ const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
964
975
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
965
976
  throw new Error(
966
977
  `Missing info in callback message source header: ${JSON.stringify({
@@ -978,6 +989,7 @@ ${atob(callbackMessage.body ?? "")}`
978
989
  workflowConfig: {
979
990
  workflowRunId,
980
991
  workflowUrl,
992
+ workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
981
993
  telemetry: telemetry2
982
994
  },
983
995
  invokeCount: Number(invokeCount)
@@ -1125,6 +1137,7 @@ var BaseLazyStep = class _BaseLazyStep {
1125
1137
  workflowConfig: {
1126
1138
  workflowRunId: context.workflowRunId,
1127
1139
  workflowUrl: context.url,
1140
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1128
1141
  useJSONContent: false,
1129
1142
  telemetry: telemetry2
1130
1143
  },
@@ -1452,6 +1465,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
1452
1465
  ) : {},
1453
1466
  // note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
1454
1467
  "Upstash-Workflow-Runid": [context.workflowRunId],
1468
+ // same for WORKFLOW_CREATED_AT_HEADER, because Createdat -> CreatedAt:
1469
+ ...context.workflowRunCreatedAt ? { "Upstash-Workflow-Createdat": [context.workflowRunCreatedAt.toString()] } : {},
1455
1470
  [WORKFLOW_INIT_HEADER]: ["false"],
1456
1471
  [WORKFLOW_URL_HEADER]: [context.url],
1457
1472
  "Upstash-Workflow-CallType": ["step"]
@@ -1554,6 +1569,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1554
1569
  workflowConfig: {
1555
1570
  workflowRunId: context.workflowRunId,
1556
1571
  workflowUrl: context.url,
1572
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1557
1573
  telemetry: telemetry2,
1558
1574
  useJSONContent: false
1559
1575
  },
@@ -1563,6 +1579,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1563
1579
  invokerHeaders[key] = value;
1564
1580
  });
1565
1581
  invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1582
+ if (context.workflowRunCreatedAt) {
1583
+ invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
1584
+ }
1566
1585
  const request = {
1567
1586
  body: stringifyBody(this.params.body),
1568
1587
  headers: Object.fromEntries(
@@ -1591,6 +1610,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1591
1610
  workflowConfig: {
1592
1611
  workflowRunId: getWorkflowRunId(workflowRunId),
1593
1612
  workflowUrl: newUrl,
1613
+ // the invoked run doesn't exist yet, so the creation time of the invoker run is sent.
1614
+ // it separates the invocations made by different runs of the invoker.
1615
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1594
1616
  retries,
1595
1617
  retryDelay,
1596
1618
  telemetry: telemetry2,
@@ -1755,6 +1777,9 @@ var WorkflowHeaders = class {
1755
1777
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1756
1778
  ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
1757
1779
  };
1780
+ if (this.workflowConfig.workflowRunCreatedAt) {
1781
+ this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
1782
+ }
1758
1783
  if (this.stepInfo?.lazyStep.stepType !== "Call") {
1759
1784
  this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1760
1785
  }
@@ -1914,6 +1939,7 @@ var submitParallelSteps = async ({
1914
1939
  workflowConfig: {
1915
1940
  workflowRunId: context.workflowRunId,
1916
1941
  workflowUrl: context.url,
1942
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1917
1943
  telemetry: telemetry2
1918
1944
  },
1919
1945
  invokeCount
@@ -3415,7 +3441,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
3415
3441
  const client = new import_qstash11.Client({
3416
3442
  ...clientOptions,
3417
3443
  baseUrl: clientEnv.QSTASH_URL,
3418
- token: clientEnv.QSTASH_TOKEN
3444
+ token: clientEnv.QSTASH_TOKEN,
3445
+ // Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
3446
+ // the client at the local dev server too. Without this the QStash Client
3447
+ // re-derives dev mode from its own process.env, which is blind to the
3448
+ // Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
3449
+ // An explicit devMode in the user's client config still wins.
3450
+ devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
3419
3451
  });
3420
3452
  const receiver = getReceiver(environment, receiverConfig, region);
3421
3453
  return { client, receiver };
@@ -3465,7 +3497,9 @@ var getQStashHandlers = ({
3465
3497
  client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
3466
3498
  ...qstashClientOption,
3467
3499
  baseUrl: environment.QSTASH_URL,
3468
- token: environment.QSTASH_TOKEN
3500
+ token: environment.QSTASH_TOKEN,
3501
+ // Mirror the receiver's dev-mode decision (see createRegionalHandler).
3502
+ devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
3469
3503
  }),
3470
3504
  receiver: getReceiver(environment, receiverConfig)
3471
3505
  }
@@ -3804,7 +3838,8 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3804
3838
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3805
3839
  const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
3806
3840
  const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3807
- const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3841
+ const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3842
+ const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
3808
3843
  const workflowContext = new WorkflowContext({
3809
3844
  qstashClient: regionalClient,
3810
3845
  workflowRunId,
@@ -3817,7 +3852,7 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3817
3852
  invokeCount,
3818
3853
  label,
3819
3854
  retried,
3820
- workflowRunCreatedAt: Number(workflowRunCreatedAt),
3855
+ workflowRunCreatedAt,
3821
3856
  middlewareManager
3822
3857
  });
3823
3858
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
@@ -3940,6 +3975,7 @@ var serveBase = (routeFunction, telemetry2, options, internalOptions) => {
3940
3975
  };
3941
3976
 
3942
3977
  // platforms/cloudflare.ts
3978
+ var import_qstash13 = require("@upstash/qstash");
3943
3979
  var getArgs = (args) => {
3944
3980
  if (!Array.isArray(args) || args.length === 0) {
3945
3981
  throw new Error("No arguments passed to serve handler");
@@ -3963,6 +3999,7 @@ var telemetry = {
3963
3999
  framework: "cloudflare"
3964
4000
  };
3965
4001
  var serve = (routeFunction, options) => {
4002
+ void (0, import_qstash13.startDevServer)();
3966
4003
  const fetch = async (...args) => {
3967
4004
  const { request, env } = getArgs(args);
3968
4005
  const { handler: serveHandler } = serveBase(routeFunction, telemetry, {
package/cloudflare.mjs CHANGED
@@ -2,9 +2,10 @@ import {
2
2
  SDK_TELEMETRY,
3
3
  serveBase,
4
4
  serveManyBase
5
- } from "./chunk-WEQGCISK.mjs";
5
+ } from "./chunk-DRHEHWMS.mjs";
6
6
 
7
7
  // platforms/cloudflare.ts
8
+ import { startDevServer } from "@upstash/qstash";
8
9
  var getArgs = (args) => {
9
10
  if (!Array.isArray(args) || args.length === 0) {
10
11
  throw new Error("No arguments passed to serve handler");
@@ -28,6 +29,7 @@ var telemetry = {
28
29
  framework: "cloudflare"
29
30
  };
30
31
  var serve = (routeFunction, options) => {
32
+ void startDevServer();
31
33
  const fetch = async (...args) => {
32
34
  const { request, env } = getArgs(args);
33
35
  const { handler: serveHandler } = serveBase(routeFunction, telemetry, {