@upstash/workflow 0.2.1 → 0.2.2

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/h3.js CHANGED
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // platforms/h3.ts
21
21
  var h3_exports = {};
22
22
  __export(h3_exports, {
23
- serve: () => serve2
23
+ serve: () => serve
24
24
  });
25
25
  module.exports = __toCommonJS(h3_exports);
26
26
 
@@ -336,48 +336,8 @@ async function _callHandler(event, handler, hooks) {
336
336
  var H3Headers = globalThis.Headers;
337
337
  var H3Response = globalThis.Response;
338
338
 
339
- // src/error.ts
340
- var import_qstash = require("@upstash/qstash");
341
- var WorkflowError = class extends import_qstash.QstashError {
342
- constructor(message) {
343
- super(message);
344
- this.name = "WorkflowError";
345
- }
346
- };
347
- var WorkflowAbort = class extends Error {
348
- stepInfo;
349
- stepName;
350
- /**
351
- * whether workflow is to be canceled on abort
352
- */
353
- cancelWorkflow;
354
- /**
355
- *
356
- * @param stepName name of the aborting step
357
- * @param stepInfo step information
358
- * @param cancelWorkflow
359
- */
360
- constructor(stepName, stepInfo, cancelWorkflow = false) {
361
- super(
362
- `This is an Upstash Workflow error thrown after a step executes. It is expected to be raised. Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch. Aborting workflow after executing step '${stepName}'.`
363
- );
364
- this.name = "WorkflowAbort";
365
- this.stepName = stepName;
366
- this.stepInfo = stepInfo;
367
- this.cancelWorkflow = cancelWorkflow;
368
- }
369
- };
370
- var formatWorkflowError = (error) => {
371
- return error instanceof Error ? {
372
- error: error.name,
373
- message: error.message
374
- } : {
375
- error: "Error",
376
- message: "An error occured while executing workflow."
377
- };
378
- };
379
-
380
339
  // src/client/utils.ts
340
+ var import_qstash = require("@upstash/qstash");
381
341
  var makeNotifyRequest = async (requester, eventId, eventData) => {
382
342
  const result = await requester.request({
383
343
  path: ["v2", "notify", eventId],
@@ -404,26 +364,71 @@ var getSteps = async (requester, workflowRunId, messageId, debug) => {
404
364
  await debug?.log("INFO", "ENDPOINT_START", {
405
365
  message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
406
366
  });
407
- return steps;
367
+ return { steps, workflowRunEnded: false };
408
368
  } else {
409
369
  const index = steps.findIndex((item) => item.messageId === messageId);
410
370
  if (index === -1) {
411
- return [];
371
+ return { steps: [], workflowRunEnded: false };
412
372
  }
413
373
  const filteredSteps = steps.slice(0, index + 1);
414
374
  await debug?.log("INFO", "ENDPOINT_START", {
415
375
  message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
416
376
  });
417
- return filteredSteps;
377
+ return { steps: filteredSteps, workflowRunEnded: false };
418
378
  }
419
379
  } catch (error) {
420
- await debug?.log("ERROR", "ERROR", {
421
- message: "failed while fetching steps.",
422
- error
423
- });
424
- throw new WorkflowError(`Failed while pulling steps. ${error}`);
380
+ if (error instanceof import_qstash.QstashError && error.status === 404) {
381
+ await debug?.log("WARN", "ENDPOINT_START", {
382
+ message: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed.",
383
+ error
384
+ });
385
+ return { steps: void 0, workflowRunEnded: true };
386
+ } else {
387
+ throw error;
388
+ }
389
+ }
390
+ };
391
+
392
+ // src/error.ts
393
+ var import_qstash2 = require("@upstash/qstash");
394
+ var WorkflowError = class extends import_qstash2.QstashError {
395
+ constructor(message) {
396
+ super(message);
397
+ this.name = "WorkflowError";
398
+ }
399
+ };
400
+ var WorkflowAbort = class extends Error {
401
+ stepInfo;
402
+ stepName;
403
+ /**
404
+ * whether workflow is to be canceled on abort
405
+ */
406
+ cancelWorkflow;
407
+ /**
408
+ *
409
+ * @param stepName name of the aborting step
410
+ * @param stepInfo step information
411
+ * @param cancelWorkflow
412
+ */
413
+ constructor(stepName, stepInfo, cancelWorkflow = false) {
414
+ super(
415
+ `This is an Upstash Workflow error thrown after a step executes. It is expected to be raised. Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch. Aborting workflow after executing step '${stepName}'.`
416
+ );
417
+ this.name = "WorkflowAbort";
418
+ this.stepName = stepName;
419
+ this.stepInfo = stepInfo;
420
+ this.cancelWorkflow = cancelWorkflow;
425
421
  }
426
422
  };
423
+ var formatWorkflowError = (error) => {
424
+ return error instanceof Error ? {
425
+ error: error.name,
426
+ message: error.message
427
+ } : {
428
+ error: "Error",
429
+ message: "An error occured while executing workflow."
430
+ };
431
+ };
427
432
 
428
433
  // src/context/steps.ts
429
434
  var BaseLazyStep = class {
@@ -1045,8 +1050,8 @@ var StepTypes = [
1045
1050
  ];
1046
1051
 
1047
1052
  // src/workflow-requests.ts
1048
- var import_qstash2 = require("@upstash/qstash");
1049
- var triggerFirstInvocation = async (workflowContext, retries, debug) => {
1053
+ var import_qstash3 = require("@upstash/qstash");
1054
+ var triggerFirstInvocation = async (workflowContext, retries, useJSONContent, debug) => {
1050
1055
  const { headers } = getHeaders(
1051
1056
  "true",
1052
1057
  workflowContext.workflowRunId,
@@ -1056,6 +1061,9 @@ var triggerFirstInvocation = async (workflowContext, retries, debug) => {
1056
1061
  workflowContext.failureUrl,
1057
1062
  retries
1058
1063
  );
1064
+ if (useJSONContent) {
1065
+ headers["content-type"] = "application/json";
1066
+ }
1059
1067
  try {
1060
1068
  const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
1061
1069
  const result = await workflowContext.qstashClient.publish({
@@ -1099,7 +1107,7 @@ var triggerRouteFunction = async ({
1099
1107
  return ok("workflow-finished");
1100
1108
  } catch (error) {
1101
1109
  const error_ = error;
1102
- if (error instanceof import_qstash2.QstashError && error.status === 400) {
1110
+ if (error instanceof import_qstash3.QstashError && error.status === 400) {
1103
1111
  await debug?.log("WARN", "RESPONSE_WORKFLOW", {
1104
1112
  message: `tried to append to a cancelled workflow. exiting without publishing.`,
1105
1113
  name: error.name,
@@ -1133,7 +1141,7 @@ var triggerWorkflowDelete = async (workflowContext, debug, cancel = false) => {
1133
1141
  );
1134
1142
  return { deleted: true };
1135
1143
  } catch (error) {
1136
- if (error instanceof import_qstash2.QstashError && error.status === 404) {
1144
+ if (error instanceof import_qstash3.QstashError && error.status === 404) {
1137
1145
  await debug?.log("WARN", "SUBMIT_CLEANUP", {
1138
1146
  message: `Failed to remove workflow run ${workflowContext.workflowRunId} as it doesn't exist.`,
1139
1147
  name: error.name,
@@ -1170,11 +1178,19 @@ var handleThirdPartyCallResult = async (request, requestPayload, client, workflo
1170
1178
  if (!workflowRunId2)
1171
1179
  throw new WorkflowError("workflow run id missing in context.call lazy fetch.");
1172
1180
  if (!messageId) throw new WorkflowError("message id missing in context.call lazy fetch.");
1173
- const steps = await getSteps(client.http, workflowRunId2, messageId, debug);
1181
+ const { steps, workflowRunEnded } = await getSteps(
1182
+ client.http,
1183
+ workflowRunId2,
1184
+ messageId,
1185
+ debug
1186
+ );
1187
+ if (workflowRunEnded) {
1188
+ return ok("workflow-ended");
1189
+ }
1174
1190
  const failingStep = steps.find((step) => step.messageId === messageId);
1175
1191
  if (!failingStep)
1176
1192
  throw new WorkflowError(
1177
- "Failed to submit the context.call." + (steps.length === 0 ? "No steps found." : `No step was found with matching messageId ${messageId} out of ${steps.length} steps.`)
1193
+ "Failed to submit the context.call. " + (steps.length === 0 ? "No steps found." : `No step was found with matching messageId ${messageId} out of ${steps.length} steps.`)
1178
1194
  );
1179
1195
  callbackPayload = atob(failingStep.body);
1180
1196
  }
@@ -2213,7 +2229,7 @@ function decodeBase64(base64) {
2213
2229
  }
2214
2230
 
2215
2231
  // src/serve/authorization.ts
2216
- var import_qstash3 = require("@upstash/qstash");
2232
+ var import_qstash4 = require("@upstash/qstash");
2217
2233
  var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowContext {
2218
2234
  static disabledMessage = "disabled-qstash-worklfow-run";
2219
2235
  /**
@@ -2244,7 +2260,7 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
2244
2260
  */
2245
2261
  static async tryAuthentication(routeFunction, context) {
2246
2262
  const disabledContext = new _DisabledWorkflowContext({
2247
- qstashClient: new import_qstash3.Client({
2263
+ qstashClient: new import_qstash4.Client({
2248
2264
  baseUrl: "disabled-client",
2249
2265
  token: "disabled-client"
2250
2266
  }),
@@ -2368,7 +2384,8 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2368
2384
  return {
2369
2385
  rawInitialPayload: requestPayload ?? "",
2370
2386
  steps: [],
2371
- isLastDuplicate: false
2387
+ isLastDuplicate: false,
2388
+ workflowRunEnded: false
2372
2389
  };
2373
2390
  } else {
2374
2391
  let rawSteps;
@@ -2378,7 +2395,21 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2378
2395
  "ENDPOINT_START",
2379
2396
  "request payload is empty, steps will be fetched from QStash."
2380
2397
  );
2381
- rawSteps = await getSteps(requester, workflowRunId, messageId, debug);
2398
+ const { steps: fetchedSteps, workflowRunEnded } = await getSteps(
2399
+ requester,
2400
+ workflowRunId,
2401
+ messageId,
2402
+ debug
2403
+ );
2404
+ if (workflowRunEnded) {
2405
+ return {
2406
+ rawInitialPayload: void 0,
2407
+ steps: void 0,
2408
+ isLastDuplicate: void 0,
2409
+ workflowRunEnded: true
2410
+ };
2411
+ }
2412
+ rawSteps = fetchedSteps;
2382
2413
  } else {
2383
2414
  rawSteps = JSON.parse(requestPayload);
2384
2415
  }
@@ -2388,7 +2419,8 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
2388
2419
  return {
2389
2420
  rawInitialPayload,
2390
2421
  steps: deduplicatedSteps,
2391
- isLastDuplicate
2422
+ isLastDuplicate,
2423
+ workflowRunEnded: false
2392
2424
  };
2393
2425
  }
2394
2426
  };
@@ -2442,15 +2474,15 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
2442
2474
  };
2443
2475
 
2444
2476
  // src/serve/options.ts
2445
- var import_qstash4 = require("@upstash/qstash");
2446
2477
  var import_qstash5 = require("@upstash/qstash");
2478
+ var import_qstash6 = require("@upstash/qstash");
2447
2479
  var processOptions = (options) => {
2448
2480
  const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
2449
2481
  const receiverEnvironmentVariablesSet = Boolean(
2450
2482
  environment.QSTASH_CURRENT_SIGNING_KEY && environment.QSTASH_NEXT_SIGNING_KEY
2451
2483
  );
2452
2484
  return {
2453
- qstashClient: new import_qstash5.Client({
2485
+ qstashClient: new import_qstash6.Client({
2454
2486
  baseUrl: environment.QSTASH_URL,
2455
2487
  token: environment.QSTASH_TOKEN
2456
2488
  }),
@@ -2484,13 +2516,14 @@ var processOptions = (options) => {
2484
2516
  throw error;
2485
2517
  }
2486
2518
  },
2487
- receiver: receiverEnvironmentVariablesSet ? new import_qstash4.Receiver({
2519
+ receiver: receiverEnvironmentVariablesSet ? new import_qstash5.Receiver({
2488
2520
  currentSigningKey: environment.QSTASH_CURRENT_SIGNING_KEY,
2489
2521
  nextSigningKey: environment.QSTASH_NEXT_SIGNING_KEY
2490
2522
  }) : void 0,
2491
2523
  baseUrl: environment.UPSTASH_WORKFLOW_URL,
2492
2524
  env: environment,
2493
2525
  retries: DEFAULT_RETRIES,
2526
+ useJSONContent: false,
2494
2527
  ...options
2495
2528
  };
2496
2529
  };
@@ -2507,6 +2540,16 @@ var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, d
2507
2540
  });
2508
2541
  }
2509
2542
  const workflowFailureUrl = failureFunction ? workflowUrl : failureUrl;
2543
+ if (workflowUrl.includes("localhost")) {
2544
+ await debug?.log("WARN", "ENDPOINT_START", {
2545
+ message: `Workflow URL contains localhost. This can happen in local development, but shouldn't happen in production unless you have a route which contains localhost. Received: ${workflowUrl}`
2546
+ });
2547
+ }
2548
+ if (!(workflowUrl.startsWith("http://") || workflowUrl.startsWith("https://"))) {
2549
+ throw new WorkflowError(
2550
+ `Workflow URL should start with 'http://' or 'https://'. Recevied is '${workflowUrl}'`
2551
+ );
2552
+ }
2510
2553
  return {
2511
2554
  workflowUrl,
2512
2555
  workflowFailureUrl
@@ -2515,7 +2558,7 @@ var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, d
2515
2558
  var AUTH_FAIL_MESSAGE = `Failed to authenticate Workflow request. If this is unexpected, see the caveat https://upstash.com/docs/workflow/basics/caveats#avoid-non-deterministic-code-outside-context-run`;
2516
2559
 
2517
2560
  // src/serve/index.ts
2518
- var serve = (routeFunction, options) => {
2561
+ var serveBase = (routeFunction, options) => {
2519
2562
  const {
2520
2563
  qstashClient,
2521
2564
  onStepFinish,
@@ -2527,7 +2570,8 @@ var serve = (routeFunction, options) => {
2527
2570
  failureFunction,
2528
2571
  baseUrl,
2529
2572
  env,
2530
- retries
2573
+ retries,
2574
+ useJSONContent
2531
2575
  } = processOptions(options);
2532
2576
  const debug = WorkflowLogger.getLogger(verbose);
2533
2577
  const handler = async (request) => {
@@ -2544,7 +2588,7 @@ var serve = (routeFunction, options) => {
2544
2588
  await verifyRequest(requestPayload, request.headers.get("upstash-signature"), receiver);
2545
2589
  const { isFirstInvocation, workflowRunId } = validateRequest(request);
2546
2590
  debug?.setWorkflowRunId(workflowRunId);
2547
- const { rawInitialPayload, steps, isLastDuplicate } = await parseRequest(
2591
+ const { rawInitialPayload, steps, isLastDuplicate, workflowRunEnded } = await parseRequest(
2548
2592
  requestPayload,
2549
2593
  isFirstInvocation,
2550
2594
  workflowRunId,
@@ -2552,8 +2596,11 @@ var serve = (routeFunction, options) => {
2552
2596
  request.headers.get("upstash-message-id"),
2553
2597
  debug
2554
2598
  );
2599
+ if (workflowRunEnded) {
2600
+ return onStepFinish(workflowRunId, "workflow-already-ended");
2601
+ }
2555
2602
  if (isLastDuplicate) {
2556
- return onStepFinish("no-workflow-id", "duplicate-step");
2603
+ return onStepFinish(workflowRunId, "duplicate-step");
2557
2604
  }
2558
2605
  const failureCheck = await handleFailure(
2559
2606
  request,
@@ -2567,7 +2614,7 @@ var serve = (routeFunction, options) => {
2567
2614
  throw failureCheck.error;
2568
2615
  } else if (failureCheck.value === "is-failure-callback") {
2569
2616
  await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
2570
- return onStepFinish("no-workflow-id", "failure-callback");
2617
+ return onStepFinish(workflowRunId, "failure-callback");
2571
2618
  }
2572
2619
  const workflowContext = new WorkflowContext({
2573
2620
  qstashClient,
@@ -2610,7 +2657,7 @@ var serve = (routeFunction, options) => {
2610
2657
  });
2611
2658
  throw callReturnCheck.error;
2612
2659
  } else if (callReturnCheck.value === "continue-workflow") {
2613
- const result = isFirstInvocation ? await triggerFirstInvocation(workflowContext, retries, debug) : await triggerRouteFunction({
2660
+ const result = isFirstInvocation ? await triggerFirstInvocation(workflowContext, retries, useJSONContent, debug) : await triggerRouteFunction({
2614
2661
  onStep: async () => routeFunction(workflowContext),
2615
2662
  onCleanup: async () => {
2616
2663
  await triggerWorkflowDelete(workflowContext, debug);
@@ -2626,6 +2673,8 @@ var serve = (routeFunction, options) => {
2626
2673
  }
2627
2674
  await debug?.log("INFO", "RESPONSE_WORKFLOW");
2628
2675
  return onStepFinish(workflowContext.workflowRunId, "success");
2676
+ } else if (callReturnCheck.value === "workflow-ended") {
2677
+ return onStepFinish(workflowContext.workflowRunId, "workflow-already-ended");
2629
2678
  }
2630
2679
  await debug?.log("INFO", "RESPONSE_DEFAULT");
2631
2680
  return onStepFinish("no-workflow-id", "fromCallback");
@@ -2643,9 +2692,6 @@ var serve = (routeFunction, options) => {
2643
2692
  return { handler: safeHandler };
2644
2693
  };
2645
2694
 
2646
- // src/client/index.ts
2647
- var import_qstash6 = require("@upstash/qstash");
2648
-
2649
2695
  // platforms/h3.ts
2650
2696
  function transformHeaders(headers) {
2651
2697
  const formattedHeaders = Object.entries(headers).map(([key, value]) => [
@@ -2654,7 +2700,7 @@ function transformHeaders(headers) {
2654
2700
  ]);
2655
2701
  return formattedHeaders;
2656
2702
  }
2657
- var serve2 = (routeFunction, options) => {
2703
+ var serve = (routeFunction, options) => {
2658
2704
  const handler = defineEventHandler(async (event) => {
2659
2705
  const method = event.node.req.method;
2660
2706
  if (method?.toUpperCase() !== "POST") {
@@ -2673,7 +2719,7 @@ var serve2 = (routeFunction, options) => {
2673
2719
  body: await readRawBody(event),
2674
2720
  method: "POST"
2675
2721
  });
2676
- const { handler: serveHandler } = serve(routeFunction, options);
2722
+ const { handler: serveHandler } = serveBase(routeFunction, options);
2677
2723
  return await serveHandler(request);
2678
2724
  });
2679
2725
  return { handler };
package/h3.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
- serve
3
- } from "./chunk-ADOBNR4O.mjs";
2
+ serveBase
3
+ } from "./chunk-Z7WS5XIR.mjs";
4
4
 
5
5
  // node_modules/defu/dist/defu.mjs
6
6
  function isPlainObject(value) {
@@ -322,7 +322,7 @@ function transformHeaders(headers) {
322
322
  ]);
323
323
  return formattedHeaders;
324
324
  }
325
- var serve2 = (routeFunction, options) => {
325
+ var serve = (routeFunction, options) => {
326
326
  const handler = defineEventHandler(async (event) => {
327
327
  const method = event.node.req.method;
328
328
  if (method?.toUpperCase() !== "POST") {
@@ -341,11 +341,11 @@ var serve2 = (routeFunction, options) => {
341
341
  body: await readRawBody(event),
342
342
  method: "POST"
343
343
  });
344
- const { handler: serveHandler } = serve(routeFunction, options);
344
+ const { handler: serveHandler } = serveBase(routeFunction, options);
345
345
  return await serveHandler(request);
346
346
  });
347
347
  return { handler };
348
348
  };
349
349
  export {
350
- serve2 as serve
350
+ serve
351
351
  };
package/hono.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Context } from 'hono';
2
- import { R as RouteFunction, W as WorkflowServeOptions } from './types-Be4hC1mu.mjs';
2
+ import { R as RouteFunction, j as PublicServeOptions } from './types-APRap-aV.mjs';
3
+ import { Variables } from 'hono/types';
3
4
  import '@upstash/qstash';
4
5
 
5
6
  type WorkflowBindings = {
@@ -18,8 +19,9 @@ type WorkflowBindings = {
18
19
  * @param options workflow options
19
20
  * @returns
20
21
  */
21
- declare const serve: <TInitialPayload = unknown, TBindings extends WorkflowBindings = WorkflowBindings>(routeFunction: RouteFunction<TInitialPayload>, options?: Omit<WorkflowServeOptions<Response, TInitialPayload>, "onStepFinish">) => ((context: Context<{
22
+ declare const serve: <TInitialPayload = unknown, TBindings extends WorkflowBindings = WorkflowBindings, TVariables extends Variables = object>(routeFunction: RouteFunction<TInitialPayload>, options?: PublicServeOptions<TInitialPayload>) => ((context: Context<{
22
23
  Bindings: TBindings;
24
+ Variables: TVariables;
23
25
  }>) => Promise<Response>);
24
26
 
25
27
  export { type WorkflowBindings, serve };
package/hono.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Context } from 'hono';
2
- import { R as RouteFunction, W as WorkflowServeOptions } from './types-Be4hC1mu.js';
2
+ import { R as RouteFunction, j as PublicServeOptions } from './types-APRap-aV.js';
3
+ import { Variables } from 'hono/types';
3
4
  import '@upstash/qstash';
4
5
 
5
6
  type WorkflowBindings = {
@@ -18,8 +19,9 @@ type WorkflowBindings = {
18
19
  * @param options workflow options
19
20
  * @returns
20
21
  */
21
- declare const serve: <TInitialPayload = unknown, TBindings extends WorkflowBindings = WorkflowBindings>(routeFunction: RouteFunction<TInitialPayload>, options?: Omit<WorkflowServeOptions<Response, TInitialPayload>, "onStepFinish">) => ((context: Context<{
22
+ declare const serve: <TInitialPayload = unknown, TBindings extends WorkflowBindings = WorkflowBindings, TVariables extends Variables = object>(routeFunction: RouteFunction<TInitialPayload>, options?: PublicServeOptions<TInitialPayload>) => ((context: Context<{
22
23
  Bindings: TBindings;
24
+ Variables: TVariables;
23
25
  }>) => Promise<Response>);
24
26
 
25
27
  export { type WorkflowBindings, serve };