@upstash/workflow 0.2.13 → 0.2.14

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
@@ -1,6 +1,6 @@
1
1
  import { R as RouteFunction, W as WorkflowServeOptions, E as ExclusiveValidationOptions, T as Telemetry, S as StepType, a as RawStep, N as NotifyResponse, b as Waiter, c as Step } from './types-C1WIgVLA.js';
2
2
  export { A as AsyncStepFunction, C as CallResponse, r as CallSettings, D as Duration, l as FailureFunctionPayload, F as FinishCondition, H as HeaderParams, t as InvokableWorkflow, s as InvokeStepResponse, I as InvokeWorkflowRequest, L as LazyInvokeStepParams, u as LogLevel, p as NotifyStepResponse, P as ParallelCallState, k as PublicServeOptions, m as RequiredExceptFields, j as StepFunction, h as StepTypes, i as SyncStepFunction, q as WaitEventOptions, n as WaitRequest, o as WaitStepResponse, f as WorkflowClient, e as WorkflowContext, w as WorkflowLogger, v as WorkflowLoggerOptions, g as WorkflowReceiver, d as WorkflowTool } from './types-C1WIgVLA.js';
3
- import { HTTPMethods, State, FlowControl, PublishRequest, Client as Client$1, QstashError } from '@upstash/qstash';
3
+ import { FlowControl, PublishRequest, HTTPMethods, State, Client as Client$1, QstashError } from '@upstash/qstash';
4
4
  import 'zod';
5
5
  import 'ai';
6
6
  import '@ai-sdk/openai';
@@ -78,6 +78,10 @@ type BaseStepLog = {
78
78
  * headers
79
79
  */
80
80
  headers: Record<string, string[]>;
81
+ /**
82
+ * retries
83
+ */
84
+ retries: number;
81
85
  };
82
86
  type CallUrlGroup = {
83
87
  /**
@@ -184,6 +188,19 @@ type StepLogGroup = {
184
188
  steps: {
185
189
  messageId: string;
186
190
  state: "STEP_PROGRESS" | "STEP_RETRY" | "STEP_FAILED" | "STEP_CANCELED";
191
+ /**
192
+ * retries
193
+ */
194
+ retries: number;
195
+ /**
196
+ * errors which occured in the step
197
+ */
198
+ errors?: {
199
+ error: string;
200
+ headers: Record<string, string[]>;
201
+ status: number;
202
+ time: number;
203
+ }[];
187
204
  }[];
188
205
  /**
189
206
  * Log which belongs to the next step
@@ -215,6 +232,9 @@ type FailureFunctionLog = {
215
232
  * Response body of the step which caused the workflow to fail
216
233
  */
217
234
  failResponse: string;
235
+ /**
236
+ * @deprecated use dlqId field of the workflow run itself
237
+ */
218
238
  dlqId: string;
219
239
  };
220
240
  type WorkflowRunLog = {
@@ -281,11 +301,73 @@ type WorkflowRunLog = {
281
301
  */
282
302
  workflowRunCreatedAt: number;
283
303
  };
304
+ /**
305
+ * If the workflow run has failed, id of the run in DLQ
306
+ */
307
+ dlqId?: string;
284
308
  };
285
309
  type WorkflowRunLogs = {
286
310
  cursor: string;
287
311
  runs: WorkflowRunLog[];
288
312
  };
313
+ type TriggerOptions = {
314
+ /**
315
+ * URL of the workflow to trigger
316
+ */
317
+ url: string;
318
+ /**
319
+ * Body to send to the workflow
320
+ */
321
+ body?: unknown;
322
+ /**
323
+ * Headers to send to the workflow
324
+ */
325
+ headers?: Record<string, string>;
326
+ /**
327
+ * Workflow run id to use for the workflow run.
328
+ * If not provided, a random workflow run id will be generated.
329
+ */
330
+ workflowRunId?: string;
331
+ /**
332
+ * Number of retries to perform if the request fails.
333
+ *
334
+ * @default 3
335
+ */
336
+ retries?: number;
337
+ /**
338
+ * Flow control to use for the workflow run.
339
+ * If not provided, no flow control will be used.
340
+ */
341
+ flowControl?: FlowControl;
342
+ /**
343
+ * Delay to apply before triggering the workflow.
344
+ */
345
+ delay?: PublishRequest["delay"];
346
+ } & ({
347
+ /**
348
+ * URL to call if the first request to the workflow endpoint fails
349
+ */
350
+ failureUrl?: never;
351
+ /**
352
+ * Whether the workflow endpoint has a failure function
353
+ * defined to be invoked if the first request fails.
354
+ *
355
+ * If true, the failureUrl will be ignored.
356
+ */
357
+ useFailureFunction?: true;
358
+ } | {
359
+ /**
360
+ * URL to call if the first request to the workflow endpoint fails
361
+ */
362
+ failureUrl?: string;
363
+ /**
364
+ * Whether the workflow endpoint has a failure function
365
+ * defined to be invoked if the first request fails.
366
+ *
367
+ * If true, the failureUrl will be ignored.
368
+ */
369
+ useFailureFunction?: never;
370
+ });
289
371
 
290
372
  type ClientConfig = ConstructorParameters<typeof Client$1>[0];
291
373
  /**
@@ -398,8 +480,9 @@ declare class Client {
398
480
  eventId: string;
399
481
  }): Promise<Required<Waiter>[]>;
400
482
  /**
401
- * Trigger new workflow run and returns the workflow run id
483
+ * Trigger new workflow run and returns the workflow run id or an array of workflow run ids
402
484
  *
485
+ * trigger a single workflow run:
403
486
  * ```ts
404
487
  * const { workflowRunId } = await client.trigger({
405
488
  * url: "https://workflow-endpoint.com",
@@ -412,6 +495,31 @@ declare class Client {
412
495
  * console.log(workflowRunId)
413
496
  * // wfr_my-workflow
414
497
  * ```
498
+ * trigger multiple workflow runs:
499
+ * ```ts
500
+ * const result = await client.trigger([
501
+ * {
502
+ * url: "https://workflow-endpoint.com",
503
+ * body: "hello there!", // Optional body
504
+ * headers: { ... }, // Optional headers
505
+ * workflowRunId: "my-workflow", // Optional workflow run ID
506
+ * retries: 3 // Optional retries for the initial request
507
+ * },
508
+ * {
509
+ * url: "https://workflow-endpoint-2.com",
510
+ * body: "hello world!", // Optional body
511
+ * headers: { ... }, // Optional headers
512
+ * workflowRunId: "my-workflow-2", // Optional workflow run ID
513
+ * retries: 5 // Optional retries for the initial request
514
+ * },
515
+ * ]);
516
+ *
517
+ * console.log(result)
518
+ * // [
519
+ * // { workflowRunId: "wfr_my-workflow" },
520
+ * // { workflowRunId: "wfr_my-workflow-2" },
521
+ * // ]
522
+ * ```
415
523
  *
416
524
  * @param url URL of the workflow
417
525
  * @param body body to start the workflow with
@@ -428,19 +536,14 @@ declare class Client {
428
536
  * @param delay Delay for the workflow run. This is used to delay the
429
537
  * execution of the workflow run. The delay is in seconds or can be passed
430
538
  * as a string with a time unit (e.g. "1h", "30m", "15s").
431
- * @returns workflow run id
432
- */
433
- trigger({ url, body, headers, workflowRunId, retries, flowControl, delay, }: {
434
- url: string;
435
- body?: unknown;
436
- headers?: Record<string, string>;
437
- workflowRunId?: string;
438
- retries?: number;
439
- flowControl?: FlowControl;
440
- delay?: PublishRequest["delay"];
441
- }): Promise<{
539
+ * @returns workflow run id or an array of workflow run ids
540
+ */
541
+ trigger(params: TriggerOptions): Promise<{
442
542
  workflowRunId: string;
443
543
  }>;
544
+ trigger(params: TriggerOptions[]): Promise<{
545
+ workflowRunId: string;
546
+ }[]>;
444
547
  /**
445
548
  * Fetches logs for workflow runs.
446
549
  *
@@ -504,4 +607,4 @@ declare class WorkflowAbort extends Error {
504
607
  constructor(stepName: string, stepInfo?: Step, cancelWorkflow?: boolean);
505
608
  }
506
609
 
507
- export { Client, ExclusiveValidationOptions, NotifyResponse, RawStep, RouteFunction, Step, type StepLog, StepType, Telemetry, Waiter, WorkflowAbort, WorkflowError, type WorkflowRunLog, type WorkflowRunLogs, WorkflowServeOptions, serve };
610
+ export { Client, ExclusiveValidationOptions, NotifyResponse, RawStep, RouteFunction, Step, type StepLog, StepType, Telemetry, type TriggerOptions, Waiter, WorkflowAbort, WorkflowError, type WorkflowRunLog, type WorkflowRunLogs, WorkflowServeOptions, serve };
package/index.js CHANGED
@@ -103,7 +103,7 @@ var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
103
103
  var DEFAULT_CONTENT_TYPE = "application/json";
104
104
  var NO_CONCURRENCY = 1;
105
105
  var DEFAULT_RETRIES = 3;
106
- var VERSION = "v0.2.13";
106
+ var VERSION = "v0.2.14";
107
107
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
108
108
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
109
109
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
@@ -611,59 +611,72 @@ var StepTypes = [
611
611
 
612
612
  // src/workflow-requests.ts
613
613
  var import_qstash3 = require("@upstash/qstash");
614
- var triggerFirstInvocation = async ({
615
- workflowContext,
616
- useJSONContent,
617
- telemetry,
618
- debug,
619
- invokeCount,
620
- delay
621
- }) => {
622
- const { headers } = getHeaders({
623
- initHeaderValue: "true",
624
- workflowConfig: {
625
- workflowRunId: workflowContext.workflowRunId,
626
- workflowUrl: workflowContext.url,
627
- failureUrl: workflowContext.failureUrl,
628
- retries: workflowContext.retries,
629
- telemetry,
630
- flowControl: workflowContext.flowControl,
631
- useJSONContent: useJSONContent ?? false
632
- },
633
- invokeCount: invokeCount ?? 0,
634
- userHeaders: workflowContext.headers
635
- });
636
- if (workflowContext.headers.get("content-type")) {
637
- headers["content-type"] = workflowContext.headers.get("content-type");
638
- }
639
- if (useJSONContent) {
640
- headers["content-type"] = "application/json";
641
- }
642
- try {
643
- const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
644
- const result = await workflowContext.qstashClient.publish({
645
- headers,
646
- method: "POST",
647
- body,
648
- url: workflowContext.url,
649
- delay
650
- });
651
- if (result.deduplicated) {
652
- await debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
653
- message: `Workflow run ${workflowContext.workflowRunId} already exists. A new one isn't created.`,
614
+ var triggerFirstInvocation = async (params) => {
615
+ const firstInvocationParams = Array.isArray(params) ? params : [params];
616
+ const workflowContextClient = firstInvocationParams[0].workflowContext.qstashClient;
617
+ const invocationBatch = firstInvocationParams.map(
618
+ ({ workflowContext, useJSONContent, telemetry, invokeCount, delay }) => {
619
+ const { headers } = getHeaders({
620
+ initHeaderValue: "true",
621
+ workflowConfig: {
622
+ workflowRunId: workflowContext.workflowRunId,
623
+ workflowUrl: workflowContext.url,
624
+ failureUrl: workflowContext.failureUrl,
625
+ retries: workflowContext.retries,
626
+ telemetry,
627
+ flowControl: workflowContext.flowControl,
628
+ useJSONContent: useJSONContent ?? false
629
+ },
630
+ invokeCount: invokeCount ?? 0,
631
+ userHeaders: workflowContext.headers
632
+ });
633
+ if (workflowContext.headers.get("content-type")) {
634
+ headers["content-type"] = workflowContext.headers.get("content-type");
635
+ }
636
+ if (useJSONContent) {
637
+ headers["content-type"] = "application/json";
638
+ }
639
+ const body = typeof workflowContext.requestPayload === "string" ? workflowContext.requestPayload : JSON.stringify(workflowContext.requestPayload);
640
+ return {
654
641
  headers,
655
- requestPayload: workflowContext.requestPayload,
642
+ method: "POST",
643
+ body,
656
644
  url: workflowContext.url,
657
- messageId: result.messageId
658
- });
645
+ delay
646
+ };
647
+ }
648
+ );
649
+ try {
650
+ const results = await workflowContextClient.batch(invocationBatch);
651
+ const invocationStatuses = [];
652
+ for (let i = 0; i < results.length; i++) {
653
+ const result = results[i];
654
+ const invocationParams = firstInvocationParams[i];
655
+ if (result.deduplicated) {
656
+ await invocationParams.debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
657
+ message: `Workflow run ${invocationParams.workflowContext.workflowRunId} already exists. A new one isn't created.`,
658
+ headers: invocationBatch[i].headers,
659
+ requestPayload: invocationParams.workflowContext.requestPayload,
660
+ url: invocationParams.workflowContext.url,
661
+ messageId: result.messageId
662
+ });
663
+ invocationStatuses.push("workflow-run-already-exists");
664
+ } else {
665
+ await invocationParams.debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
666
+ headers: invocationBatch[i].headers,
667
+ requestPayload: invocationParams.workflowContext.requestPayload,
668
+ url: invocationParams.workflowContext.url,
669
+ messageId: result.messageId
670
+ });
671
+ invocationStatuses.push("success");
672
+ }
673
+ }
674
+ const hasAnyDeduplicated = invocationStatuses.some(
675
+ (status) => status === "workflow-run-already-exists"
676
+ );
677
+ if (hasAnyDeduplicated) {
659
678
  return ok("workflow-run-already-exists");
660
679
  } else {
661
- await debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
662
- headers,
663
- requestPayload: workflowContext.requestPayload,
664
- url: workflowContext.url,
665
- messageId: result.messageId
666
- });
667
680
  return ok("success");
668
681
  }
669
682
  } catch (error) {
@@ -1153,7 +1166,7 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1153
1166
  return { header, status, body };
1154
1167
  }
1155
1168
  }
1156
- static applicationHeaders = /* @__PURE__ */ new Set([
1169
+ static applicationContentTypes = [
1157
1170
  "application/json",
1158
1171
  "application/xml",
1159
1172
  "application/javascript",
@@ -1162,12 +1175,12 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1162
1175
  "application/ld+json",
1163
1176
  "application/rss+xml",
1164
1177
  "application/atom+xml"
1165
- ]);
1178
+ ];
1166
1179
  static isText = (contentTypeHeader) => {
1167
1180
  if (!contentTypeHeader) {
1168
1181
  return false;
1169
1182
  }
1170
- if (_LazyCallStep.applicationHeaders.has(contentTypeHeader)) {
1183
+ if (_LazyCallStep.applicationContentTypes.some((type) => contentTypeHeader.includes(type))) {
1171
1184
  return true;
1172
1185
  }
1173
1186
  if (contentTypeHeader.startsWith("text/")) {
@@ -3557,70 +3570,39 @@ var Client4 = class {
3557
3570
  async getWaiters({ eventId }) {
3558
3571
  return await makeGetWaitersRequest(this.client.http, eventId);
3559
3572
  }
3560
- /**
3561
- * Trigger new workflow run and returns the workflow run id
3562
- *
3563
- * ```ts
3564
- * const { workflowRunId } = await client.trigger({
3565
- * url: "https://workflow-endpoint.com",
3566
- * body: "hello there!", // Optional body
3567
- * headers: { ... }, // Optional headers
3568
- * workflowRunId: "my-workflow", // Optional workflow run ID
3569
- * retries: 3 // Optional retries for the initial request
3570
- * });
3571
- *
3572
- * console.log(workflowRunId)
3573
- * // wfr_my-workflow
3574
- * ```
3575
- *
3576
- * @param url URL of the workflow
3577
- * @param body body to start the workflow with
3578
- * @param headers headers to use in the request
3579
- * @param workflowRunId optional workflow run id to use. mind that
3580
- * you should pass different workflow run ids for different runs.
3581
- * The final workflowRunId will be `wfr_${workflowRunId}`, in
3582
- * other words: the workflow run id you pass will be prefixed
3583
- * with `wfr_`.
3584
- * @param retries retry to use in the initial request. in the rest of
3585
- * the workflow, `retries` option of the `serve` will be used.
3586
- * @param flowControl Settings for controlling the number of active requests
3587
- * and number of requests per second with the same key.
3588
- * @param delay Delay for the workflow run. This is used to delay the
3589
- * execution of the workflow run. The delay is in seconds or can be passed
3590
- * as a string with a time unit (e.g. "1h", "30m", "15s").
3591
- * @returns workflow run id
3592
- */
3593
- async trigger({
3594
- url,
3595
- body,
3596
- headers,
3597
- workflowRunId,
3598
- retries,
3599
- flowControl,
3600
- delay
3601
- }) {
3602
- const finalWorkflowRunId = getWorkflowRunId(workflowRunId);
3603
- const context = new WorkflowContext({
3604
- qstashClient: this.client,
3605
- // @ts-expect-error headers type mismatch
3606
- headers: new Headers(headers ?? {}),
3607
- initialPayload: body,
3608
- steps: [],
3609
- url,
3610
- workflowRunId: finalWorkflowRunId,
3611
- retries,
3612
- telemetry: void 0,
3613
- // can't know workflow telemetry here
3614
- flowControl
3615
- });
3616
- const result = await triggerFirstInvocation({
3617
- workflowContext: context,
3618
- telemetry: void 0,
3619
- // can't know workflow telemetry here
3620
- delay
3573
+ async trigger(params) {
3574
+ const isBatchInput = Array.isArray(params);
3575
+ const options = isBatchInput ? params : [params];
3576
+ const invocations = options.map((option) => {
3577
+ const failureUrl = option.useFailureFunction ? option.url : option.failureUrl;
3578
+ const finalWorkflowRunId = getWorkflowRunId(option.workflowRunId);
3579
+ const context = new WorkflowContext({
3580
+ qstashClient: this.client,
3581
+ // @ts-expect-error headers type mismatch
3582
+ headers: new Headers(option.headers ?? {}),
3583
+ initialPayload: option.body,
3584
+ steps: [],
3585
+ url: option.url,
3586
+ workflowRunId: finalWorkflowRunId,
3587
+ retries: option.retries,
3588
+ telemetry: void 0,
3589
+ // can't know workflow telemetry here
3590
+ flowControl: option.flowControl,
3591
+ failureUrl
3592
+ });
3593
+ return {
3594
+ workflowContext: context,
3595
+ telemetry: void 0,
3596
+ // can't know workflow telemetry here
3597
+ delay: option.delay
3598
+ };
3621
3599
  });
3600
+ const result = await triggerFirstInvocation(invocations);
3601
+ const workflowRunIds = invocations.map(
3602
+ (invocation) => invocation.workflowContext.workflowRunId
3603
+ );
3622
3604
  if (result.isOk()) {
3623
- return { workflowRunId: finalWorkflowRunId };
3605
+ return isBatchInput ? workflowRunIds.map((id) => ({ workflowRunId: id })) : { workflowRunId: workflowRunIds[0] };
3624
3606
  } else {
3625
3607
  throw result.error;
3626
3608
  }
package/index.mjs CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  makeNotifyRequest,
11
11
  serve,
12
12
  triggerFirstInvocation
13
- } from "./chunk-XVNSBBDC.mjs";
13
+ } from "./chunk-RMS2NQ3K.mjs";
14
14
 
15
15
  // src/client/index.ts
16
16
  import { Client as QStashClient } from "@upstash/qstash";
@@ -140,70 +140,39 @@ var Client = class {
140
140
  async getWaiters({ eventId }) {
141
141
  return await makeGetWaitersRequest(this.client.http, eventId);
142
142
  }
143
- /**
144
- * Trigger new workflow run and returns the workflow run id
145
- *
146
- * ```ts
147
- * const { workflowRunId } = await client.trigger({
148
- * url: "https://workflow-endpoint.com",
149
- * body: "hello there!", // Optional body
150
- * headers: { ... }, // Optional headers
151
- * workflowRunId: "my-workflow", // Optional workflow run ID
152
- * retries: 3 // Optional retries for the initial request
153
- * });
154
- *
155
- * console.log(workflowRunId)
156
- * // wfr_my-workflow
157
- * ```
158
- *
159
- * @param url URL of the workflow
160
- * @param body body to start the workflow with
161
- * @param headers headers to use in the request
162
- * @param workflowRunId optional workflow run id to use. mind that
163
- * you should pass different workflow run ids for different runs.
164
- * The final workflowRunId will be `wfr_${workflowRunId}`, in
165
- * other words: the workflow run id you pass will be prefixed
166
- * with `wfr_`.
167
- * @param retries retry to use in the initial request. in the rest of
168
- * the workflow, `retries` option of the `serve` will be used.
169
- * @param flowControl Settings for controlling the number of active requests
170
- * and number of requests per second with the same key.
171
- * @param delay Delay for the workflow run. This is used to delay the
172
- * execution of the workflow run. The delay is in seconds or can be passed
173
- * as a string with a time unit (e.g. "1h", "30m", "15s").
174
- * @returns workflow run id
175
- */
176
- async trigger({
177
- url,
178
- body,
179
- headers,
180
- workflowRunId,
181
- retries,
182
- flowControl,
183
- delay
184
- }) {
185
- const finalWorkflowRunId = getWorkflowRunId(workflowRunId);
186
- const context = new WorkflowContext({
187
- qstashClient: this.client,
188
- // @ts-expect-error headers type mismatch
189
- headers: new Headers(headers ?? {}),
190
- initialPayload: body,
191
- steps: [],
192
- url,
193
- workflowRunId: finalWorkflowRunId,
194
- retries,
195
- telemetry: void 0,
196
- // can't know workflow telemetry here
197
- flowControl
198
- });
199
- const result = await triggerFirstInvocation({
200
- workflowContext: context,
201
- telemetry: void 0,
202
- // can't know workflow telemetry here
203
- delay
143
+ async trigger(params) {
144
+ const isBatchInput = Array.isArray(params);
145
+ const options = isBatchInput ? params : [params];
146
+ const invocations = options.map((option) => {
147
+ const failureUrl = option.useFailureFunction ? option.url : option.failureUrl;
148
+ const finalWorkflowRunId = getWorkflowRunId(option.workflowRunId);
149
+ const context = new WorkflowContext({
150
+ qstashClient: this.client,
151
+ // @ts-expect-error headers type mismatch
152
+ headers: new Headers(option.headers ?? {}),
153
+ initialPayload: option.body,
154
+ steps: [],
155
+ url: option.url,
156
+ workflowRunId: finalWorkflowRunId,
157
+ retries: option.retries,
158
+ telemetry: void 0,
159
+ // can't know workflow telemetry here
160
+ flowControl: option.flowControl,
161
+ failureUrl
162
+ });
163
+ return {
164
+ workflowContext: context,
165
+ telemetry: void 0,
166
+ // can't know workflow telemetry here
167
+ delay: option.delay
168
+ };
204
169
  });
170
+ const result = await triggerFirstInvocation(invocations);
171
+ const workflowRunIds = invocations.map(
172
+ (invocation) => invocation.workflowContext.workflowRunId
173
+ );
205
174
  if (result.isOk()) {
206
- return { workflowRunId: finalWorkflowRunId };
175
+ return isBatchInput ? workflowRunIds.map((id) => ({ workflowRunId: id })) : { workflowRunId: workflowRunIds[0] };
207
176
  } else {
208
177
  throw result.error;
209
178
  }