@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/index.d.ts CHANGED
@@ -209,6 +209,11 @@ type StepLogGroup = {
209
209
  */
210
210
  type: "next";
211
211
  };
212
+ /**
213
+ * State of the failure function (`failureUrl`/`failureFunction`) call of a
214
+ * failed workflow run.
215
+ */
216
+ type FailureFunctionState = "CALLBACK_INPROGRESS" | "CALLBACK_SUCCESS" | "CALLBACK_FAIL" | "CALLBACK_CANCELED";
212
217
  type FailureFunctionLog = {
213
218
  /**
214
219
  * messageId of the message published for handling the failure
@@ -221,7 +226,7 @@ type FailureFunctionLog = {
221
226
  /**
222
227
  * State of the message published for failure
223
228
  */
224
- state: "CALLBACK_INPROGRESS" | "CALLBACK_SUCCESS" | "CALLBACK_FAIL" | "CALLBACK_CANCELED";
229
+ state: FailureFunctionState;
225
230
  /**
226
231
  * Headers received from the step which caused the workflow to fail
227
232
  */
@@ -570,7 +575,11 @@ type WorkflowFilterFields = {
570
575
  workflowUrl?: string | string[];
571
576
  workflowRunId?: string;
572
577
  workflowCreatedAt?: number;
573
- failureFunctionState?: string;
578
+ /**
579
+ * Filter by the state of the failure function call. Supports multiple values:
580
+ * pass an array to match runs in any of the given states (OR semantics).
581
+ */
582
+ failureFunctionState?: FailureFunctionState | FailureFunctionState[];
574
583
  };
575
584
  type WorkflowLogsFilterFields = {
576
585
  state?: WorkflowRunLog["workflowState"];
@@ -680,7 +689,7 @@ type ResumeRestartOptions = {
680
689
  retries?: number;
681
690
  };
682
691
  type FailureCallbackInfo = {
683
- state?: "CALLBACK_FAIL" | "CALLBACK_SUCCESS" | "CALLBACK_INPROGRESS" | "CALLBACK_CANCELED";
692
+ state?: FailureFunctionState;
684
693
  responseStatus?: number;
685
694
  responseBody?: string;
686
695
  responseHeaders?: Record<string, string[]>;
@@ -834,6 +843,15 @@ declare class DLQ {
834
843
  retryFailureFunction({ dlqId }: Pick<DLQResumeRestartOptions<string>, "dlqId">): Promise<DLQResumeRestartResponse & {
835
844
  error?: string;
836
845
  }>;
846
+ /**
847
+ * Cancel the in-progress failure function (failureUrl/failureFunction) call
848
+ * of a workflow run.
849
+ *
850
+ * Fails if the run has no failure function call in progress.
851
+ *
852
+ * @param dlqId - The ID of the DLQ message whose failure function call will be canceled
853
+ */
854
+ cancelFailureFunction({ dlqId }: Pick<DLQResumeRestartOptions<string>, "dlqId">): Promise<void>;
837
855
  /**
838
856
  * Delete DLQ messages.
839
857
  *
@@ -1071,4 +1089,4 @@ declare class Client {
1071
1089
  get dlq(): DLQ;
1072
1090
  }
1073
1091
 
1074
- export { Client, type DLQResumeRestartOptions, type DLQResumeRestartResponse, NotifyResponse, RawStep, RouteFunction, type StepError, type StepLog, StepType, Telemetry, type TriggerOptions, Waiter, WorkflowClient, WorkflowMiddleware, WorkflowReceiver, type WorkflowRunLog, type WorkflowRunLogs, WorkflowServeOptions, loggingMiddleware, serve };
1092
+ export { Client, type DLQResumeRestartOptions, type DLQResumeRestartResponse, type FailureFunctionState, NotifyResponse, RawStep, RouteFunction, type StepError, type StepLog, StepType, Telemetry, type TriggerOptions, Waiter, WorkflowClient, WorkflowMiddleware, WorkflowReceiver, type WorkflowRunLog, type WorkflowRunLogs, WorkflowServeOptions, loggingMiddleware, serve };
package/index.js CHANGED
@@ -33,6 +33,9 @@ __export(src_exports, {
33
33
  });
34
34
  module.exports = __toCommonJS(src_exports);
35
35
 
36
+ // src/serve/index.ts
37
+ var import_qstash12 = require("@upstash/qstash");
38
+
36
39
  // src/client/utils.ts
37
40
  var import_qstash2 = require("@upstash/qstash");
38
41
 
@@ -868,6 +871,10 @@ var triggerFirstInvocation = async (params) => {
868
871
  workflowConfig: {
869
872
  workflowRunId: workflowContext.workflowRunId,
870
873
  workflowUrl: workflowContext.url,
874
+ // the run doesn't exist yet, so the trigger time is sent when the creation time
875
+ // is not known. serve and client.trigger set it on the context already, so a
876
+ // retry of this request carries the same value and is deduplicated.
877
+ workflowRunCreatedAt: workflowContext.workflowRunCreatedAt || Date.now(),
871
878
  failureUrl,
872
879
  retries,
873
880
  retryDelay,
@@ -971,7 +978,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
971
978
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
972
979
  method: "DELETE",
973
980
  parseResponseAsJson: false,
974
- body: JSON.stringify(result)
981
+ body: JSON.stringify(result),
982
+ // with the creation time, QStash deletes the run only if it is the same run.
983
+ // a late delete of a previous run can't delete a new run with the same id.
984
+ headers: workflowContext.workflowRunCreatedAt ? { [WORKFLOW_CREATED_AT_HEADER]: workflowContext.workflowRunCreatedAt.toString() } : void 0
975
985
  });
976
986
  await dispatchDebug?.("onInfo", {
977
987
  info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
@@ -1048,6 +1058,7 @@ ${atob(callbackMessage.body ?? "")}`
1048
1058
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
1049
1059
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
1050
1060
  const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
1061
+ const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
1051
1062
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
1052
1063
  throw new Error(
1053
1064
  `Missing info in callback message source header: ${JSON.stringify({
@@ -1065,6 +1076,7 @@ ${atob(callbackMessage.body ?? "")}`
1065
1076
  workflowConfig: {
1066
1077
  workflowRunId,
1067
1078
  workflowUrl,
1079
+ workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
1068
1080
  telemetry
1069
1081
  },
1070
1082
  invokeCount: Number(invokeCount)
@@ -1212,6 +1224,7 @@ var BaseLazyStep = class _BaseLazyStep {
1212
1224
  workflowConfig: {
1213
1225
  workflowRunId: context.workflowRunId,
1214
1226
  workflowUrl: context.url,
1227
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1215
1228
  useJSONContent: false,
1216
1229
  telemetry
1217
1230
  },
@@ -1539,6 +1552,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
1539
1552
  ) : {},
1540
1553
  // note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
1541
1554
  "Upstash-Workflow-Runid": [context.workflowRunId],
1555
+ // same for WORKFLOW_CREATED_AT_HEADER, because Createdat -> CreatedAt:
1556
+ ...context.workflowRunCreatedAt ? { "Upstash-Workflow-Createdat": [context.workflowRunCreatedAt.toString()] } : {},
1542
1557
  [WORKFLOW_INIT_HEADER]: ["false"],
1543
1558
  [WORKFLOW_URL_HEADER]: [context.url],
1544
1559
  "Upstash-Workflow-CallType": ["step"]
@@ -1641,6 +1656,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1641
1656
  workflowConfig: {
1642
1657
  workflowRunId: context.workflowRunId,
1643
1658
  workflowUrl: context.url,
1659
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1644
1660
  telemetry,
1645
1661
  useJSONContent: false
1646
1662
  },
@@ -1650,6 +1666,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1650
1666
  invokerHeaders[key] = value;
1651
1667
  });
1652
1668
  invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1669
+ if (context.workflowRunCreatedAt) {
1670
+ invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
1671
+ }
1653
1672
  const request = {
1654
1673
  body: stringifyBody(this.params.body),
1655
1674
  headers: Object.fromEntries(
@@ -1678,6 +1697,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1678
1697
  workflowConfig: {
1679
1698
  workflowRunId: getWorkflowRunId(workflowRunId),
1680
1699
  workflowUrl: newUrl,
1700
+ // the invoked run doesn't exist yet, so the creation time of the invoker run is sent.
1701
+ // it separates the invocations made by different runs of the invoker.
1702
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1681
1703
  retries,
1682
1704
  retryDelay,
1683
1705
  telemetry,
@@ -1842,6 +1864,9 @@ var WorkflowHeaders = class {
1842
1864
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1843
1865
  ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
1844
1866
  };
1867
+ if (this.workflowConfig.workflowRunCreatedAt) {
1868
+ this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
1869
+ }
1845
1870
  if (this.stepInfo?.lazyStep.stepType !== "Call") {
1846
1871
  this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1847
1872
  }
@@ -2001,6 +2026,7 @@ var submitParallelSteps = async ({
2001
2026
  workflowConfig: {
2002
2027
  workflowRunId: context.workflowRunId,
2003
2028
  workflowUrl: context.url,
2029
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
2004
2030
  telemetry
2005
2031
  },
2006
2032
  invokeCount
@@ -3435,7 +3461,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
3435
3461
  const client = new import_qstash11.Client({
3436
3462
  ...clientOptions,
3437
3463
  baseUrl: clientEnv.QSTASH_URL,
3438
- token: clientEnv.QSTASH_TOKEN
3464
+ token: clientEnv.QSTASH_TOKEN,
3465
+ // Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
3466
+ // the client at the local dev server too. Without this the QStash Client
3467
+ // re-derives dev mode from its own process.env, which is blind to the
3468
+ // Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
3469
+ // An explicit devMode in the user's client config still wins.
3470
+ devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
3439
3471
  });
3440
3472
  const receiver = getReceiver(environment, receiverConfig, region);
3441
3473
  return { client, receiver };
@@ -3485,7 +3517,9 @@ var getQStashHandlers = ({
3485
3517
  client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
3486
3518
  ...qstashClientOption,
3487
3519
  baseUrl: environment.QSTASH_URL,
3488
- token: environment.QSTASH_TOKEN
3520
+ token: environment.QSTASH_TOKEN,
3521
+ // Mirror the receiver's dev-mode decision (see createRegionalHandler).
3522
+ devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
3489
3523
  }),
3490
3524
  receiver: getReceiver(environment, receiverConfig)
3491
3525
  }
@@ -3824,7 +3858,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3824
3858
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3825
3859
  const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
3826
3860
  const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3827
- const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3861
+ const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3862
+ const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
3828
3863
  const workflowContext = new WorkflowContext({
3829
3864
  qstashClient: regionalClient,
3830
3865
  workflowRunId,
@@ -3837,7 +3872,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3837
3872
  invokeCount,
3838
3873
  label,
3839
3874
  retried,
3840
- workflowRunCreatedAt: Number(workflowRunCreatedAt),
3875
+ workflowRunCreatedAt,
3841
3876
  middlewareManager
3842
3877
  });
3843
3878
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
@@ -3959,6 +3994,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3959
3994
  return { handler: safeHandler };
3960
3995
  };
3961
3996
  var serve = (routeFunction, options) => {
3997
+ void (0, import_qstash12.startDevServer)();
3962
3998
  return serveBase(
3963
3999
  routeFunction,
3964
4000
  {
@@ -3970,9 +4006,10 @@ var serve = (routeFunction, options) => {
3970
4006
  };
3971
4007
 
3972
4008
  // src/client/index.ts
3973
- var import_qstash12 = require("@upstash/qstash");
4009
+ var import_qstash14 = require("@upstash/qstash");
3974
4010
 
3975
4011
  // src/client/dlq.ts
4012
+ var import_qstash13 = require("@upstash/qstash");
3976
4013
  function buildResumeRestartHeaders(options) {
3977
4014
  const headers = {};
3978
4015
  if (options?.flowControl) {
@@ -4090,6 +4127,27 @@ var DLQ = class {
4090
4127
  });
4091
4128
  return response;
4092
4129
  }
4130
+ /**
4131
+ * Cancel the in-progress failure function (failureUrl/failureFunction) call
4132
+ * of a workflow run.
4133
+ *
4134
+ * Fails if the run has no failure function call in progress.
4135
+ *
4136
+ * @param dlqId - The ID of the DLQ message whose failure function call will be canceled
4137
+ */
4138
+ async cancelFailureFunction({ dlqId }) {
4139
+ assertNonEmptyId(dlqId, "DLQ id");
4140
+ try {
4141
+ await this.client.http.request({
4142
+ path: ["v2", "workflows", "dlq", "callback", dlqId],
4143
+ method: "DELETE",
4144
+ parseResponseAsJson: false
4145
+ });
4146
+ } catch (error) {
4147
+ if (isInstanceOf(error, import_qstash13.QstashError) && error.status === 302) return;
4148
+ throw error;
4149
+ }
4150
+ }
4093
4151
  /**
4094
4152
  * Delete DLQ messages.
4095
4153
  *
@@ -4131,7 +4189,7 @@ var DLQ = class {
4131
4189
  var Client4 = class {
4132
4190
  client;
4133
4191
  constructor(clientConfig) {
4134
- this.client = new import_qstash12.Client(clientConfig);
4192
+ this.client = new import_qstash14.Client(clientConfig);
4135
4193
  }
4136
4194
  async cancel(request) {
4137
4195
  if (typeof request === "object" && !Array.isArray(request) && ("ids" in request || "urlStartingWith" in request)) {
package/index.mjs CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  assertNonEmptyId,
12
12
  buildBulkActionQueryParameters,
13
13
  getWorkflowRunId,
14
+ isInstanceOf,
14
15
  loggingMiddleware,
15
16
  makeGetWaitersRequest,
16
17
  makeNotifyRequest,
@@ -22,12 +23,13 @@ import {
22
23
  triggerFirstInvocation,
23
24
  validateFlowControl,
24
25
  validateLabel
25
- } from "./chunk-WEQGCISK.mjs";
26
+ } from "./chunk-DRHEHWMS.mjs";
26
27
 
27
28
  // src/client/index.ts
28
- import { Client as QStashClient } from "@upstash/qstash";
29
+ import { Client as QStashClient2 } from "@upstash/qstash";
29
30
 
30
31
  // src/client/dlq.ts
32
+ import { QstashError } from "@upstash/qstash";
31
33
  function buildResumeRestartHeaders(options) {
32
34
  const headers = {};
33
35
  if (options?.flowControl) {
@@ -145,6 +147,27 @@ var DLQ = class {
145
147
  });
146
148
  return response;
147
149
  }
150
+ /**
151
+ * Cancel the in-progress failure function (failureUrl/failureFunction) call
152
+ * of a workflow run.
153
+ *
154
+ * Fails if the run has no failure function call in progress.
155
+ *
156
+ * @param dlqId - The ID of the DLQ message whose failure function call will be canceled
157
+ */
158
+ async cancelFailureFunction({ dlqId }) {
159
+ assertNonEmptyId(dlqId, "DLQ id");
160
+ try {
161
+ await this.client.http.request({
162
+ path: ["v2", "workflows", "dlq", "callback", dlqId],
163
+ method: "DELETE",
164
+ parseResponseAsJson: false
165
+ });
166
+ } catch (error) {
167
+ if (isInstanceOf(error, QstashError) && error.status === 302) return;
168
+ throw error;
169
+ }
170
+ }
148
171
  /**
149
172
  * Delete DLQ messages.
150
173
  *
@@ -186,7 +209,7 @@ var DLQ = class {
186
209
  var Client = class {
187
210
  client;
188
211
  constructor(clientConfig) {
189
- this.client = new QStashClient(clientConfig);
212
+ this.client = new QStashClient2(clientConfig);
190
213
  }
191
214
  async cancel(request) {
192
215
  if (typeof request === "object" && !Array.isArray(request) && ("ids" in request || "urlStartingWith" in request)) {
package/nextjs.js CHANGED
@@ -29,6 +29,9 @@ __export(nextjs_exports, {
29
29
  });
30
30
  module.exports = __toCommonJS(nextjs_exports);
31
31
 
32
+ // src/serve/index.ts
33
+ var import_qstash12 = require("@upstash/qstash");
34
+
32
35
  // src/client/utils.ts
33
36
  var import_qstash2 = require("@upstash/qstash");
34
37
 
@@ -784,6 +787,10 @@ var triggerFirstInvocation = async (params) => {
784
787
  workflowConfig: {
785
788
  workflowRunId: workflowContext.workflowRunId,
786
789
  workflowUrl: workflowContext.url,
790
+ // the run doesn't exist yet, so the trigger time is sent when the creation time
791
+ // is not known. serve and client.trigger set it on the context already, so a
792
+ // retry of this request carries the same value and is deduplicated.
793
+ workflowRunCreatedAt: workflowContext.workflowRunCreatedAt || Date.now(),
787
794
  failureUrl,
788
795
  retries,
789
796
  retryDelay,
@@ -887,7 +894,10 @@ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, disp
887
894
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
888
895
  method: "DELETE",
889
896
  parseResponseAsJson: false,
890
- body: JSON.stringify(result)
897
+ body: JSON.stringify(result),
898
+ // with the creation time, QStash deletes the run only if it is the same run.
899
+ // a late delete of a previous run can't delete a new run with the same id.
900
+ headers: workflowContext.workflowRunCreatedAt ? { [WORKFLOW_CREATED_AT_HEADER]: workflowContext.workflowRunCreatedAt.toString() } : void 0
891
901
  });
892
902
  await dispatchDebug?.("onInfo", {
893
903
  info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
@@ -964,6 +974,7 @@ ${atob(callbackMessage.body ?? "")}`
964
974
  const concurrentString = request.headers.get("Upstash-Workflow-Concurrent");
965
975
  const contentType = request.headers.get("Upstash-Workflow-ContentType");
966
976
  const invokeCount = request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER);
977
+ const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
967
978
  if (!(workflowRunId && stepIdString && stepName && StepTypes.includes(stepType) && concurrentString && contentType)) {
968
979
  throw new Error(
969
980
  `Missing info in callback message source header: ${JSON.stringify({
@@ -981,6 +992,7 @@ ${atob(callbackMessage.body ?? "")}`
981
992
  workflowConfig: {
982
993
  workflowRunId,
983
994
  workflowUrl,
995
+ workflowRunCreatedAt: Number(workflowRunCreatedAt ?? "0"),
984
996
  telemetry
985
997
  },
986
998
  invokeCount: Number(invokeCount)
@@ -1128,6 +1140,7 @@ var BaseLazyStep = class _BaseLazyStep {
1128
1140
  workflowConfig: {
1129
1141
  workflowRunId: context.workflowRunId,
1130
1142
  workflowUrl: context.url,
1143
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1131
1144
  useJSONContent: false,
1132
1145
  telemetry
1133
1146
  },
@@ -1455,6 +1468,8 @@ var LazyWaitEventStep = class extends BaseLazyStep {
1455
1468
  ) : {},
1456
1469
  // note: using WORKFLOW_ID_HEADER doesn't work, because Runid -> RunId:
1457
1470
  "Upstash-Workflow-Runid": [context.workflowRunId],
1471
+ // same for WORKFLOW_CREATED_AT_HEADER, because Createdat -> CreatedAt:
1472
+ ...context.workflowRunCreatedAt ? { "Upstash-Workflow-Createdat": [context.workflowRunCreatedAt.toString()] } : {},
1458
1473
  [WORKFLOW_INIT_HEADER]: ["false"],
1459
1474
  [WORKFLOW_URL_HEADER]: [context.url],
1460
1475
  "Upstash-Workflow-CallType": ["step"]
@@ -1557,6 +1572,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1557
1572
  workflowConfig: {
1558
1573
  workflowRunId: context.workflowRunId,
1559
1574
  workflowUrl: context.url,
1575
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1560
1576
  telemetry,
1561
1577
  useJSONContent: false
1562
1578
  },
@@ -1566,6 +1582,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1566
1582
  invokerHeaders[key] = value;
1567
1583
  });
1568
1584
  invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1585
+ if (context.workflowRunCreatedAt) {
1586
+ invokerHeaders["Upstash-Workflow-Createdat"] = context.workflowRunCreatedAt.toString();
1587
+ }
1569
1588
  const request = {
1570
1589
  body: stringifyBody(this.params.body),
1571
1590
  headers: Object.fromEntries(
@@ -1594,6 +1613,9 @@ var LazyInvokeStep = class extends BaseLazyStep {
1594
1613
  workflowConfig: {
1595
1614
  workflowRunId: getWorkflowRunId(workflowRunId),
1596
1615
  workflowUrl: newUrl,
1616
+ // the invoked run doesn't exist yet, so the creation time of the invoker run is sent.
1617
+ // it separates the invocations made by different runs of the invoker.
1618
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1597
1619
  retries,
1598
1620
  retryDelay,
1599
1621
  telemetry,
@@ -1758,6 +1780,9 @@ var WorkflowHeaders = class {
1758
1780
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1759
1781
  ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
1760
1782
  };
1783
+ if (this.workflowConfig.workflowRunCreatedAt) {
1784
+ this.headers.rawHeaders[WORKFLOW_CREATED_AT_HEADER] = this.workflowConfig.workflowRunCreatedAt.toString();
1785
+ }
1761
1786
  if (this.stepInfo?.lazyStep.stepType !== "Call") {
1762
1787
  this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
1763
1788
  }
@@ -1917,6 +1942,7 @@ var submitParallelSteps = async ({
1917
1942
  workflowConfig: {
1918
1943
  workflowRunId: context.workflowRunId,
1919
1944
  workflowUrl: context.url,
1945
+ workflowRunCreatedAt: context.workflowRunCreatedAt,
1920
1946
  telemetry
1921
1947
  },
1922
1948
  invokeCount
@@ -3418,7 +3444,13 @@ var createRegionalHandler = (environment, receiverConfig, region, clientOptions)
3418
3444
  const client = new import_qstash11.Client({
3419
3445
  ...clientOptions,
3420
3446
  baseUrl: clientEnv.QSTASH_URL,
3421
- token: clientEnv.QSTASH_TOKEN
3447
+ token: clientEnv.QSTASH_TOKEN,
3448
+ // Mirror the receiver's dev-mode decision so a single QSTASH_DEV=true points
3449
+ // the client at the local dev server too. Without this the QStash Client
3450
+ // re-derives dev mode from its own process.env, which is blind to the
3451
+ // Cloudflare worker `env` binding that actually carries QSTASH_DEV there.
3452
+ // An explicit devMode in the user's client config still wins.
3453
+ devMode: clientOptions?.devMode ?? isQStashDevModeEnabled(environment)
3422
3454
  });
3423
3455
  const receiver = getReceiver(environment, receiverConfig, region);
3424
3456
  return { client, receiver };
@@ -3468,7 +3500,9 @@ var getQStashHandlers = ({
3468
3500
  client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash11.Client({
3469
3501
  ...qstashClientOption,
3470
3502
  baseUrl: environment.QSTASH_URL,
3471
- token: environment.QSTASH_TOKEN
3503
+ token: environment.QSTASH_TOKEN,
3504
+ // Mirror the receiver's dev-mode decision (see createRegionalHandler).
3505
+ devMode: qstashClientOption?.devMode ?? isQStashDevModeEnabled(environment)
3472
3506
  }),
3473
3507
  receiver: getReceiver(environment, receiverConfig)
3474
3508
  }
@@ -3807,7 +3841,8 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3807
3841
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3808
3842
  const retried = Number(request.headers.get(WORKFLOW_RETRIED_HEADER) ?? "0");
3809
3843
  const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3810
- const workflowRunCreatedAt = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3844
+ const workflowRunCreatedAtHeader = request.headers.get(WORKFLOW_CREATED_AT_HEADER);
3845
+ const workflowRunCreatedAt = workflowRunCreatedAtHeader ? Number(workflowRunCreatedAtHeader) : isFirstInvocation ? Date.now() : 0;
3811
3846
  const workflowContext = new WorkflowContext({
3812
3847
  qstashClient: regionalClient,
3813
3848
  workflowRunId,
@@ -3820,7 +3855,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3820
3855
  invokeCount,
3821
3856
  label,
3822
3857
  retried,
3823
- workflowRunCreatedAt: Number(workflowRunCreatedAt),
3858
+ workflowRunCreatedAt,
3824
3859
  middlewareManager
3825
3860
  });
3826
3861
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
@@ -3943,6 +3978,7 @@ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3943
3978
  };
3944
3979
 
3945
3980
  // platforms/nextjs.ts
3981
+ var import_qstash13 = require("@upstash/qstash");
3946
3982
  var appTelemetry = {
3947
3983
  sdk: SDK_TELEMETRY,
3948
3984
  framework: "nextjs",
@@ -3954,6 +3990,7 @@ var pagesTelemetry = {
3954
3990
  runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
3955
3991
  };
3956
3992
  var serve = (routeFunction, options) => {
3993
+ void (0, import_qstash13.startDevServer)();
3957
3994
  const { handler: serveHandler } = serveBase(
3958
3995
  routeFunction,
3959
3996
  appTelemetry,
@@ -3986,6 +4023,7 @@ var serveMany = (workflows, options) => {
3986
4023
  };
3987
4024
  };
3988
4025
  var servePagesRouter = (routeFunction, options) => {
4026
+ void (0, import_qstash13.startDevServer)();
3989
4027
  const { handler: serveHandler } = serveBase(routeFunction, pagesTelemetry, options, void 0);
3990
4028
  const handler = async (request_, res) => {
3991
4029
  if (request_.method?.toUpperCase() !== "POST") {
package/nextjs.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/nextjs.ts
8
+ import { startDevServer } from "@upstash/qstash";
8
9
  var appTelemetry = {
9
10
  sdk: SDK_TELEMETRY,
10
11
  framework: "nextjs",
@@ -16,6 +17,7 @@ var pagesTelemetry = {
16
17
  runtime: process.versions.bun ? `bun@${process.versions.bun}/node@${process.version}` : `node@${process.version}`
17
18
  };
18
19
  var serve = (routeFunction, options) => {
20
+ void startDevServer();
19
21
  const { handler: serveHandler } = serveBase(
20
22
  routeFunction,
21
23
  appTelemetry,
@@ -48,6 +50,7 @@ var serveMany = (workflows, options) => {
48
50
  };
49
51
  };
50
52
  var servePagesRouter = (routeFunction, options) => {
53
+ void startDevServer();
51
54
  const { handler: serveHandler } = serveBase(routeFunction, pagesTelemetry, options, void 0);
52
55
  const handler = async (request_, res) => {
53
56
  if (request_.method?.toUpperCase() !== "POST") {
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@upstash/workflow","version":"1.3.3","description":"Durable, Reliable and Performant Serverless Functions","main":"./index.js","module":"./index.mjs","types":"./index.d.ts","files":["./*"],"exports":{".":{"import":"./index.mjs","require":"./index.js"},"./dist/nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./h3":{"import":"./h3.mjs","require":"./h3.js"},"./svelte":{"import":"./svelte.mjs","require":"./svelte.js"},"./solidjs":{"import":"./solidjs.mjs","require":"./solidjs.js"},"./workflow":{"import":"./workflow.mjs","require":"./workflow.js"},"./hono":{"import":"./hono.mjs","require":"./hono.js"},"./cloudflare":{"import":"./cloudflare.mjs","require":"./cloudflare.js"},"./astro":{"import":"./astro.mjs","require":"./astro.js"},"./express":{"import":"./express.mjs","require":"./express.js"},"./tanstack":{"import":"./tanstack.mjs","require":"./tanstack.js"},"./react-router":{"import":"./react-router.mjs","require":"./react-router.js"}},"scripts":{"build":"tsup && cp README.md ./dist/ && cp package.json ./dist/ && cp LICENSE ./dist/","test":"bun test src","fmt":"prettier --write .","lint":"tsc && eslint \"{src,platforms}/**/*.{js,ts,tsx}\" --quiet --fix","check-exports":"bun run build && cd dist && attw -P"},"repository":{"type":"git","url":"git@github.com:upstash/workflow-js.git"},"keywords":["upstash","qstash","workflow","serverless"],"author":"Cahid Arda Oz","license":"MIT","bugs":{"url":"https://github.com/upstash/workflow-ts/issues"},"homepage":"https://github.com/upstash/workflow-ts#readme","devDependencies":{"@commitlint/cli":"^19.5.0","@commitlint/config-conventional":"^19.5.0","@eslint/js":"^9.11.1","@solidjs/start":"^1.0.8","@sveltejs/kit":"^2.6.1","@types/bun":"^1.1.10","@types/express":"^5.0.6","astro":"^4.16.7","eslint":"^9.11.1","eslint-plugin-unicorn":"^55.0.0","express":"^5.1.0","globals":"^15.10.0","h3":"^1.12.0","hono":"^4.6.20","husky":"^9.1.6","next":"^14.2.14","prettier":"3.3.3","tsup":"^8.3.0","typescript":"^5.7.2","typescript-eslint":"^8.18.0"},"dependencies":{"@upstash/qstash":"^2.11.0"},"directories":{"example":"examples"},"peerDependencies":{"zod":"^3.25.0 || ^4.0.0"}}
1
+ {"name":"@upstash/workflow","version":"1.4.0","description":"Durable, Reliable and Performant Serverless Functions","main":"./index.js","module":"./index.mjs","types":"./index.d.ts","files":["./*"],"exports":{".":{"import":"./index.mjs","require":"./index.js"},"./dist/nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./nextjs":{"import":"./nextjs.mjs","require":"./nextjs.js"},"./h3":{"import":"./h3.mjs","require":"./h3.js"},"./svelte":{"import":"./svelte.mjs","require":"./svelte.js"},"./solidjs":{"import":"./solidjs.mjs","require":"./solidjs.js"},"./workflow":{"import":"./workflow.mjs","require":"./workflow.js"},"./hono":{"import":"./hono.mjs","require":"./hono.js"},"./cloudflare":{"import":"./cloudflare.mjs","require":"./cloudflare.js"},"./astro":{"import":"./astro.mjs","require":"./astro.js"},"./express":{"import":"./express.mjs","require":"./express.js"},"./tanstack":{"import":"./tanstack.mjs","require":"./tanstack.js"},"./react-router":{"import":"./react-router.mjs","require":"./react-router.js"}},"scripts":{"build":"tsup && cp README.md ./dist/ && cp package.json ./dist/ && cp LICENSE ./dist/","test":"bun test src","fmt":"prettier --write .","lint":"tsc && eslint \"{src,platforms}/**/*.{js,ts,tsx}\" --quiet --fix","check-exports":"bun run build && cd dist && attw -P"},"repository":{"type":"git","url":"git@github.com:upstash/workflow-js.git"},"keywords":["upstash","qstash","workflow","serverless"],"author":"Cahid Arda Oz","license":"MIT","bugs":{"url":"https://github.com/upstash/workflow-ts/issues"},"homepage":"https://github.com/upstash/workflow-ts#readme","devDependencies":{"@commitlint/cli":"^19.5.0","@commitlint/config-conventional":"^19.5.0","@eslint/js":"^9.11.1","@solidjs/start":"^1.0.8","@sveltejs/kit":"^2.6.1","@types/bun":"^1.1.10","@types/express":"^5.0.6","astro":"^4.16.7","eslint":"^9.11.1","eslint-plugin-unicorn":"^55.0.0","express":"^5.1.0","globals":"^15.10.0","h3":"^1.12.0","hono":"^4.6.20","husky":"^9.1.6","next":"^14.2.14","prettier":"3.3.3","tsup":"^8.3.0","typescript":"^5.7.2","typescript-eslint":"^8.18.0"},"dependencies":{"@upstash/qstash":"^2.11.3"},"directories":{"example":"examples"},"peerDependencies":{"zod":"^3.25.0 || ^4.0.0"}}