@trigger.dev/sdk 3.2.2 → 3.3.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.
@@ -1,6 +1,5 @@
1
1
  import { SpanKind } from "@opentelemetry/api";
2
- import { SEMATTRS_MESSAGING_DESTINATION, SEMATTRS_MESSAGING_OPERATION, SEMATTRS_MESSAGING_SYSTEM, } from "@opentelemetry/semantic-conventions";
3
- import { accessoryAttributes, apiClientManager, conditionallyImportPacket, convertToolParametersToSchema, createErrorTaskError, defaultRetryOptions, getSchemaParseFn, logger, makeIdempotencyKey, parsePacket, runtime, SemanticInternalAttributes, stringifyIO, SubtaskUnwrapError, taskCatalog, taskContext, TaskRunPromise, } from "@trigger.dev/core/v3";
2
+ import { accessoryAttributes, apiClientManager, conditionallyImportPacket, convertToolParametersToSchema, createErrorTaskError, defaultRetryOptions, getSchemaParseFn, makeIdempotencyKey, parsePacket, runtime, SemanticInternalAttributes, stringifyIO, SubtaskUnwrapError, taskCatalog, taskContext, TaskRunPromise, } from "@trigger.dev/core/v3";
4
3
  import { runs } from "./runs.js";
5
4
  import { tracer } from "./tracer.js";
6
5
  export { SubtaskUnwrapError, TaskRunPromise };
@@ -26,11 +25,11 @@ export function createTask(params) {
26
25
  ...options,
27
26
  });
28
27
  },
29
- batchTrigger: async (items) => {
28
+ batchTrigger: async (items, options) => {
30
29
  const taskMetadata = taskCatalog.getTaskManifest(params.id);
31
30
  return await batchTrigger_internal(taskMetadata && taskMetadata.exportName
32
31
  ? `${taskMetadata.exportName}.batchTrigger()`
33
- : `batchTrigger()`, params.id, items, undefined, undefined, customQueue);
32
+ : `batchTrigger()`, params.id, items, options, undefined, undefined, customQueue);
34
33
  },
35
34
  triggerAndWait: (payload, options) => {
36
35
  const taskMetadata = taskCatalog.getTaskManifest(params.id);
@@ -117,11 +116,11 @@ export function createSchemaTask(params) {
117
116
  ...options,
118
117
  }, requestOptions);
119
118
  },
120
- batchTrigger: async (items, requestOptions) => {
119
+ batchTrigger: async (items, options, requestOptions) => {
121
120
  const taskMetadata = taskCatalog.getTaskManifest(params.id);
122
121
  return await batchTrigger_internal(taskMetadata && taskMetadata.exportName
123
122
  ? `${taskMetadata.exportName}.batchTrigger()`
124
- : `batchTrigger()`, params.id, items, parsePayload, requestOptions, customQueue);
123
+ : `batchTrigger()`, params.id, items, options, parsePayload, requestOptions, customQueue);
125
124
  },
126
125
  triggerAndWait: (payload, options) => {
127
126
  const taskMetadata = taskCatalog.getTaskManifest(params.id);
@@ -264,8 +263,540 @@ export async function triggerAndPoll(id, payload, options, requestOptions) {
264
263
  const handle = await trigger(id, payload, options, requestOptions);
265
264
  return runs.poll(handle, options, requestOptions);
266
265
  }
267
- export async function batchTrigger(id, items, requestOptions) {
268
- return await batchTrigger_internal("tasks.batchTrigger()", id, items, undefined, requestOptions);
266
+ export async function batchTrigger(id, items, options, requestOptions) {
267
+ return await batchTrigger_internal("tasks.batchTrigger()", id, items, options, undefined, requestOptions);
268
+ }
269
+ /**
270
+ * Triggers multiple runs of different tasks with specified payloads and options.
271
+ *
272
+ * @template TTask - The type of task(s) to be triggered, extends AnyTask
273
+ *
274
+ * @param {Array<BatchByIdItem<InferRunTypes<TTask>>>} items - Array of task items to trigger
275
+ * @param {BatchTriggerOptions} [options] - Optional batch-level trigger options
276
+ * @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
277
+ *
278
+ * @returns {Promise<BatchRunHandleFromTypes<InferRunTypes<TTask>>>} A promise that resolves with the batch run handle
279
+ * containing batch ID, cached status, idempotency info, runs, and public access token
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * import { batch } from "@trigger.dev/sdk/v3";
284
+ * import type { myTask1, myTask2 } from "~/trigger/myTasks";
285
+ *
286
+ * // Trigger multiple tasks with different payloads
287
+ * const result = await batch.trigger<typeof myTask1 | typeof myTask2>([
288
+ * {
289
+ * id: "my-task-1",
290
+ * payload: { some: "data" },
291
+ * options: {
292
+ * queue: "default",
293
+ * concurrencyKey: "key",
294
+ * idempotencyKey: "unique-key",
295
+ * delay: "5m",
296
+ * tags: ["tag1", "tag2"]
297
+ * }
298
+ * },
299
+ * {
300
+ * id: "my-task-2",
301
+ * payload: { other: "data" }
302
+ * }
303
+ * ]);
304
+ * ```
305
+ *
306
+ * @description
307
+ * Each task item in the array can include:
308
+ * - `id`: The unique identifier of the task
309
+ * - `payload`: The data to pass to the task
310
+ * - `options`: Optional task-specific settings including:
311
+ * - `queue`: Specify a queue for the task
312
+ * - `concurrencyKey`: Control concurrent execution
313
+ * - `idempotencyKey`: Prevent duplicate runs
314
+ * - `idempotencyKeyTTL`: Time-to-live for idempotency key
315
+ * - `delay`: Delay before task execution
316
+ * - `ttl`: Time-to-live for the task
317
+ * - `tags`: Array of tags for the task
318
+ * - `maxAttempts`: Maximum retry attempts
319
+ * - `metadata`: Additional metadata
320
+ * - `maxDuration`: Maximum execution duration
321
+ */
322
+ export async function batchTriggerById(items, options, requestOptions) {
323
+ const apiClient = apiClientManager.clientOrThrow();
324
+ const response = await apiClient.batchTriggerV2({
325
+ items: await Promise.all(items.map(async (item) => {
326
+ const taskMetadata = taskCatalog.getTask(item.id);
327
+ const parsedPayload = taskMetadata?.fns.parsePayload
328
+ ? await taskMetadata?.fns.parsePayload(item.payload)
329
+ : item.payload;
330
+ const payloadPacket = await stringifyIO(parsedPayload);
331
+ return {
332
+ task: item.id,
333
+ payload: payloadPacket.data,
334
+ options: {
335
+ queue: item.options?.queue,
336
+ concurrencyKey: item.options?.concurrencyKey,
337
+ test: taskContext.ctx?.run.isTest,
338
+ payloadType: payloadPacket.dataType,
339
+ idempotencyKey: await makeIdempotencyKey(item.options?.idempotencyKey),
340
+ idempotencyKeyTTL: item.options?.idempotencyKeyTTL,
341
+ delay: item.options?.delay,
342
+ ttl: item.options?.ttl,
343
+ tags: item.options?.tags,
344
+ maxAttempts: item.options?.maxAttempts,
345
+ parentAttempt: taskContext.ctx?.attempt.id,
346
+ metadata: item.options?.metadata,
347
+ maxDuration: item.options?.maxDuration,
348
+ },
349
+ };
350
+ })),
351
+ }, {
352
+ spanParentAsLink: true,
353
+ idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
354
+ idempotencyKeyTTL: options?.idempotencyKeyTTL,
355
+ }, {
356
+ name: "batch.trigger()",
357
+ tracer,
358
+ icon: "trigger",
359
+ onResponseBody(body, span) {
360
+ if (body && typeof body === "object" && !Array.isArray(body)) {
361
+ if ("id" in body && typeof body.id === "string") {
362
+ span.setAttribute("batchId", body.id);
363
+ }
364
+ if ("runs" in body && Array.isArray(body.runs)) {
365
+ span.setAttribute("runCount", body.runs.length);
366
+ }
367
+ if ("isCached" in body && typeof body.isCached === "boolean") {
368
+ if (body.isCached) {
369
+ console.warn(`Result is a cached response because the request was idempotent.`);
370
+ }
371
+ span.setAttribute("isCached", body.isCached);
372
+ }
373
+ if ("idempotencyKey" in body && typeof body.idempotencyKey === "string") {
374
+ span.setAttribute("idempotencyKey", body.idempotencyKey);
375
+ }
376
+ }
377
+ },
378
+ ...requestOptions,
379
+ });
380
+ const handle = {
381
+ batchId: response.id,
382
+ isCached: response.isCached,
383
+ idempotencyKey: response.idempotencyKey,
384
+ runs: response.runs,
385
+ publicAccessToken: response.publicAccessToken,
386
+ };
387
+ return handle;
388
+ }
389
+ /**
390
+ * Triggers multiple tasks and waits for all of them to complete before returning their results.
391
+ * This function must be called from within a task.run() context.
392
+ *
393
+ * @template TTask - Union type of tasks to be triggered, extends AnyTask
394
+ *
395
+ * @param {Array<BatchByIdAndWaitItem<InferRunTypes<TTask>>>} items - Array of task items to trigger
396
+ * @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
397
+ *
398
+ * @returns {Promise<BatchByIdResult<TTask>>} A promise that resolves with the batch results, including
399
+ * success/failure status and strongly-typed outputs for each task
400
+ *
401
+ * @throws {Error} If called outside of a task.run() context
402
+ * @throws {Error} If no API client is configured
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * import { batch, task } from "@trigger.dev/sdk/v3";
407
+ *
408
+ * export const parentTask = task({
409
+ * id: "parent-task",
410
+ * run: async (payload: string) => {
411
+ * const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
412
+ * {
413
+ * id: "child-task-1",
414
+ * payload: { foo: "World" },
415
+ * options: {
416
+ * queue: "default",
417
+ * delay: "5m",
418
+ * tags: ["batch", "child1"]
419
+ * }
420
+ * },
421
+ * {
422
+ * id: "child-task-2",
423
+ * payload: { bar: 42 }
424
+ * }
425
+ * ]);
426
+ *
427
+ * // Type-safe result handling
428
+ * for (const result of results) {
429
+ * if (result.ok) {
430
+ * switch (result.taskIdentifier) {
431
+ * case "child-task-1":
432
+ * console.log("Child task 1 output:", result.output); // string type
433
+ * break;
434
+ * case "child-task-2":
435
+ * console.log("Child task 2 output:", result.output); // number type
436
+ * break;
437
+ * }
438
+ * } else {
439
+ * console.error("Task failed:", result.error);
440
+ * }
441
+ * }
442
+ * }
443
+ * });
444
+ * ```
445
+ *
446
+ * @description
447
+ * Each task item in the array can include:
448
+ * - `id`: The task identifier (must match one of the tasks in the union type)
449
+ * - `payload`: Strongly-typed payload matching the task's input type
450
+ * - `options`: Optional task-specific settings including:
451
+ * - `queue`: Specify a queue for the task
452
+ * - `concurrencyKey`: Control concurrent execution
453
+ * - `delay`: Delay before task execution
454
+ * - `ttl`: Time-to-live for the task
455
+ * - `tags`: Array of tags for the task
456
+ * - `maxAttempts`: Maximum retry attempts
457
+ * - `metadata`: Additional metadata
458
+ * - `maxDuration`: Maximum execution duration
459
+ *
460
+ * The function provides full type safety for:
461
+ * - Task IDs
462
+ * - Payload types
463
+ * - Return value types
464
+ * - Error handling
465
+ */
466
+ export async function batchTriggerByIdAndWait(items, requestOptions) {
467
+ const ctx = taskContext.ctx;
468
+ if (!ctx) {
469
+ throw new Error("batchTriggerAndWait can only be used from inside a task.run()");
470
+ }
471
+ const apiClient = apiClientManager.clientOrThrow();
472
+ return await tracer.startActiveSpan("batch.triggerAndWait()", async (span) => {
473
+ const response = await apiClient.batchTriggerV2({
474
+ items: await Promise.all(items.map(async (item) => {
475
+ const taskMetadata = taskCatalog.getTask(item.id);
476
+ const parsedPayload = taskMetadata?.fns.parsePayload
477
+ ? await taskMetadata?.fns.parsePayload(item.payload)
478
+ : item.payload;
479
+ const payloadPacket = await stringifyIO(parsedPayload);
480
+ return {
481
+ task: item.id,
482
+ payload: payloadPacket.data,
483
+ options: {
484
+ lockToVersion: taskContext.worker?.version,
485
+ queue: item.options?.queue,
486
+ concurrencyKey: item.options?.concurrencyKey,
487
+ test: taskContext.ctx?.run.isTest,
488
+ payloadType: payloadPacket.dataType,
489
+ delay: item.options?.delay,
490
+ ttl: item.options?.ttl,
491
+ tags: item.options?.tags,
492
+ maxAttempts: item.options?.maxAttempts,
493
+ metadata: item.options?.metadata,
494
+ maxDuration: item.options?.maxDuration,
495
+ },
496
+ };
497
+ })),
498
+ dependentAttempt: ctx.attempt.id,
499
+ }, {}, requestOptions);
500
+ span.setAttribute("batchId", response.id);
501
+ span.setAttribute("runCount", response.runs.length);
502
+ span.setAttribute("isCached", response.isCached);
503
+ if (response.isCached) {
504
+ console.warn(`Result is a cached response because the request was idempotent.`);
505
+ }
506
+ if (response.idempotencyKey) {
507
+ span.setAttribute("idempotencyKey", response.idempotencyKey);
508
+ }
509
+ const result = await runtime.waitForBatch({
510
+ id: response.id,
511
+ runs: response.runs.map((run) => run.id),
512
+ ctx,
513
+ });
514
+ const runs = await handleBatchTaskRunExecutionResultV2(result.items);
515
+ return {
516
+ id: result.id,
517
+ runs,
518
+ };
519
+ }, {
520
+ kind: SpanKind.PRODUCER,
521
+ });
522
+ }
523
+ /**
524
+ * Triggers multiple tasks and waits for all of them to complete before returning their results.
525
+ * This function must be called from within a task.run() context.
526
+ *
527
+ * @template TTask - Union type of tasks to be triggered, extends AnyTask
528
+ *
529
+ * @param {Array<BatchByIdAndWaitItem<InferRunTypes<TTask>>>} items - Array of task items to trigger
530
+ * @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
531
+ *
532
+ * @returns {Promise<BatchByIdResult<TTask>>} A promise that resolves with the batch results, including
533
+ * success/failure status and strongly-typed outputs for each task
534
+ *
535
+ * @throws {Error} If called outside of a task.run() context
536
+ * @throws {Error} If no API client is configured
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * import { batch, task } from "@trigger.dev/sdk/v3";
541
+ *
542
+ * export const parentTask = task({
543
+ * id: "parent-task",
544
+ * run: async (payload: string) => {
545
+ * const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
546
+ * {
547
+ * id: "child-task-1",
548
+ * payload: { foo: "World" },
549
+ * options: {
550
+ * queue: "default",
551
+ * delay: "5m",
552
+ * tags: ["batch", "child1"]
553
+ * }
554
+ * },
555
+ * {
556
+ * id: "child-task-2",
557
+ * payload: { bar: 42 }
558
+ * }
559
+ * ]);
560
+ *
561
+ * // Type-safe result handling
562
+ * for (const result of results) {
563
+ * if (result.ok) {
564
+ * switch (result.taskIdentifier) {
565
+ * case "child-task-1":
566
+ * console.log("Child task 1 output:", result.output); // string type
567
+ * break;
568
+ * case "child-task-2":
569
+ * console.log("Child task 2 output:", result.output); // number type
570
+ * break;
571
+ * }
572
+ * } else {
573
+ * console.error("Task failed:", result.error);
574
+ * }
575
+ * }
576
+ * }
577
+ * });
578
+ * ```
579
+ *
580
+ * @description
581
+ * Each task item in the array can include:
582
+ * - `id`: The task identifier (must match one of the tasks in the union type)
583
+ * - `payload`: Strongly-typed payload matching the task's input type
584
+ * - `options`: Optional task-specific settings including:
585
+ * - `queue`: Specify a queue for the task
586
+ * - `concurrencyKey`: Control concurrent execution
587
+ * - `delay`: Delay before task execution
588
+ * - `ttl`: Time-to-live for the task
589
+ * - `tags`: Array of tags for the task
590
+ * - `maxAttempts`: Maximum retry attempts
591
+ * - `metadata`: Additional metadata
592
+ * - `maxDuration`: Maximum execution duration
593
+ *
594
+ * The function provides full type safety for:
595
+ * - Task IDs
596
+ * - Payload types
597
+ * - Return value types
598
+ * - Error handling
599
+ */
600
+ export async function batchTriggerTasks(items, options, requestOptions) {
601
+ const apiClient = apiClientManager.clientOrThrow();
602
+ const response = await apiClient.batchTriggerV2({
603
+ items: await Promise.all(items.map(async (item) => {
604
+ const taskMetadata = taskCatalog.getTask(item.task.id);
605
+ const parsedPayload = taskMetadata?.fns.parsePayload
606
+ ? await taskMetadata?.fns.parsePayload(item.payload)
607
+ : item.payload;
608
+ const payloadPacket = await stringifyIO(parsedPayload);
609
+ return {
610
+ task: item.task.id,
611
+ payload: payloadPacket.data,
612
+ options: {
613
+ queue: item.options?.queue,
614
+ concurrencyKey: item.options?.concurrencyKey,
615
+ test: taskContext.ctx?.run.isTest,
616
+ payloadType: payloadPacket.dataType,
617
+ idempotencyKey: await makeIdempotencyKey(item.options?.idempotencyKey),
618
+ idempotencyKeyTTL: item.options?.idempotencyKeyTTL,
619
+ delay: item.options?.delay,
620
+ ttl: item.options?.ttl,
621
+ tags: item.options?.tags,
622
+ maxAttempts: item.options?.maxAttempts,
623
+ parentAttempt: taskContext.ctx?.attempt.id,
624
+ metadata: item.options?.metadata,
625
+ maxDuration: item.options?.maxDuration,
626
+ },
627
+ };
628
+ })),
629
+ }, {
630
+ spanParentAsLink: true,
631
+ idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
632
+ idempotencyKeyTTL: options?.idempotencyKeyTTL,
633
+ }, {
634
+ name: "batch.triggerByTask()",
635
+ tracer,
636
+ icon: "trigger",
637
+ onResponseBody(body, span) {
638
+ if (body && typeof body === "object" && !Array.isArray(body)) {
639
+ if ("id" in body && typeof body.id === "string") {
640
+ span.setAttribute("batchId", body.id);
641
+ }
642
+ if ("runs" in body && Array.isArray(body.runs)) {
643
+ span.setAttribute("runCount", body.runs.length);
644
+ }
645
+ if ("isCached" in body && typeof body.isCached === "boolean") {
646
+ if (body.isCached) {
647
+ console.warn(`Result is a cached response because the request was idempotent.`);
648
+ }
649
+ span.setAttribute("isCached", body.isCached);
650
+ }
651
+ if ("idempotencyKey" in body && typeof body.idempotencyKey === "string") {
652
+ span.setAttribute("idempotencyKey", body.idempotencyKey);
653
+ }
654
+ }
655
+ },
656
+ ...requestOptions,
657
+ });
658
+ const handle = {
659
+ batchId: response.id,
660
+ isCached: response.isCached,
661
+ idempotencyKey: response.idempotencyKey,
662
+ runs: response.runs,
663
+ publicAccessToken: response.publicAccessToken,
664
+ };
665
+ return handle;
666
+ }
667
+ /**
668
+ * Triggers multiple tasks and waits for all of them to complete before returning their results.
669
+ * This function must be called from within a task.run() context.
670
+ *
671
+ * @template TTask - Union type of tasks to be triggered, extends AnyTask
672
+ *
673
+ * @param {Array<BatchByIdAndWaitItem<InferRunTypes<TTask>>>} items - Array of task items to trigger
674
+ * @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
675
+ *
676
+ * @returns {Promise<BatchByIdResult<TTask>>} A promise that resolves with the batch results, including
677
+ * success/failure status and strongly-typed outputs for each task
678
+ *
679
+ * @throws {Error} If called outside of a task.run() context
680
+ * @throws {Error} If no API client is configured
681
+ *
682
+ * @example
683
+ * ```ts
684
+ * import { batch, task } from "@trigger.dev/sdk/v3";
685
+ *
686
+ * export const parentTask = task({
687
+ * id: "parent-task",
688
+ * run: async (payload: string) => {
689
+ * const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
690
+ * {
691
+ * id: "child-task-1",
692
+ * payload: { foo: "World" },
693
+ * options: {
694
+ * queue: "default",
695
+ * delay: "5m",
696
+ * tags: ["batch", "child1"]
697
+ * }
698
+ * },
699
+ * {
700
+ * id: "child-task-2",
701
+ * payload: { bar: 42 }
702
+ * }
703
+ * ]);
704
+ *
705
+ * // Type-safe result handling
706
+ * for (const result of results) {
707
+ * if (result.ok) {
708
+ * switch (result.taskIdentifier) {
709
+ * case "child-task-1":
710
+ * console.log("Child task 1 output:", result.output); // string type
711
+ * break;
712
+ * case "child-task-2":
713
+ * console.log("Child task 2 output:", result.output); // number type
714
+ * break;
715
+ * }
716
+ * } else {
717
+ * console.error("Task failed:", result.error);
718
+ * }
719
+ * }
720
+ * }
721
+ * });
722
+ * ```
723
+ *
724
+ * @description
725
+ * Each task item in the array can include:
726
+ * - `id`: The task identifier (must match one of the tasks in the union type)
727
+ * - `payload`: Strongly-typed payload matching the task's input type
728
+ * - `options`: Optional task-specific settings including:
729
+ * - `queue`: Specify a queue for the task
730
+ * - `concurrencyKey`: Control concurrent execution
731
+ * - `delay`: Delay before task execution
732
+ * - `ttl`: Time-to-live for the task
733
+ * - `tags`: Array of tags for the task
734
+ * - `maxAttempts`: Maximum retry attempts
735
+ * - `metadata`: Additional metadata
736
+ * - `maxDuration`: Maximum execution duration
737
+ *
738
+ * The function provides full type safety for:
739
+ * - Task IDs
740
+ * - Payload types
741
+ * - Return value types
742
+ * - Error handling
743
+ */
744
+ export async function batchTriggerAndWaitTasks(items, requestOptions) {
745
+ const ctx = taskContext.ctx;
746
+ if (!ctx) {
747
+ throw new Error("batchTriggerAndWait can only be used from inside a task.run()");
748
+ }
749
+ const apiClient = apiClientManager.clientOrThrow();
750
+ return await tracer.startActiveSpan("batch.triggerByTaskAndWait()", async (span) => {
751
+ const response = await apiClient.batchTriggerV2({
752
+ items: await Promise.all(items.map(async (item) => {
753
+ const taskMetadata = taskCatalog.getTask(item.task.id);
754
+ const parsedPayload = taskMetadata?.fns.parsePayload
755
+ ? await taskMetadata?.fns.parsePayload(item.payload)
756
+ : item.payload;
757
+ const payloadPacket = await stringifyIO(parsedPayload);
758
+ return {
759
+ task: item.task.id,
760
+ payload: payloadPacket.data,
761
+ options: {
762
+ lockToVersion: taskContext.worker?.version,
763
+ queue: item.options?.queue,
764
+ concurrencyKey: item.options?.concurrencyKey,
765
+ test: taskContext.ctx?.run.isTest,
766
+ payloadType: payloadPacket.dataType,
767
+ delay: item.options?.delay,
768
+ ttl: item.options?.ttl,
769
+ tags: item.options?.tags,
770
+ maxAttempts: item.options?.maxAttempts,
771
+ metadata: item.options?.metadata,
772
+ maxDuration: item.options?.maxDuration,
773
+ },
774
+ };
775
+ })),
776
+ dependentAttempt: ctx.attempt.id,
777
+ }, {}, requestOptions);
778
+ span.setAttribute("batchId", response.id);
779
+ span.setAttribute("runCount", response.runs.length);
780
+ span.setAttribute("isCached", response.isCached);
781
+ if (response.isCached) {
782
+ console.warn(`Result is a cached response because the request was idempotent.`);
783
+ }
784
+ if (response.idempotencyKey) {
785
+ span.setAttribute("idempotencyKey", response.idempotencyKey);
786
+ }
787
+ const result = await runtime.waitForBatch({
788
+ id: response.id,
789
+ runs: response.runs.map((run) => run.id),
790
+ ctx,
791
+ });
792
+ const runs = await handleBatchTaskRunExecutionResultV2(result.items);
793
+ return {
794
+ id: result.id,
795
+ runs,
796
+ };
797
+ }, {
798
+ kind: SpanKind.PRODUCER,
799
+ });
269
800
  }
270
801
  async function trigger_internal(name, id, payload, parsePayload, options, requestOptions) {
271
802
  const apiClient = apiClientManager.clientOrThrow();
@@ -279,6 +810,7 @@ async function trigger_internal(name, id, payload, parsePayload, options, reques
279
810
  test: taskContext.ctx?.run.isTest,
280
811
  payloadType: payloadPacket.dataType,
281
812
  idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
813
+ idempotencyKeyTTL: options?.idempotencyKeyTTL,
282
814
  delay: options?.delay,
283
815
  ttl: options?.ttl,
284
816
  tags: options?.tags,
@@ -293,30 +825,25 @@ async function trigger_internal(name, id, payload, parsePayload, options, reques
293
825
  name,
294
826
  tracer,
295
827
  icon: "trigger",
296
- attributes: {
297
- [SEMATTRS_MESSAGING_OPERATION]: "publish",
298
- ["messaging.client_id"]: taskContext.worker?.id,
299
- [SEMATTRS_MESSAGING_SYSTEM]: "trigger.dev",
300
- },
301
828
  onResponseBody: (body, span) => {
302
- body &&
303
- typeof body === "object" &&
304
- !Array.isArray(body) &&
305
- "id" in body &&
306
- typeof body.id === "string" &&
307
- span.setAttribute("messaging.message.id", body.id);
829
+ if (body && typeof body === "object" && !Array.isArray(body)) {
830
+ if ("id" in body && typeof body.id === "string") {
831
+ span.setAttribute("runId", body.id);
832
+ }
833
+ }
308
834
  },
309
835
  ...requestOptions,
310
836
  });
311
837
  return handle;
312
838
  }
313
- async function batchTrigger_internal(name, id, items, parsePayload, requestOptions, queue) {
839
+ async function batchTrigger_internal(name, taskIdentifier, items, options, parsePayload, requestOptions, queue) {
314
840
  const apiClient = apiClientManager.clientOrThrow();
315
- const response = await apiClient.batchTriggerTask(id, {
841
+ const response = await apiClient.batchTriggerV2({
316
842
  items: await Promise.all(items.map(async (item) => {
317
843
  const parsedPayload = parsePayload ? await parsePayload(item.payload) : item.payload;
318
844
  const payloadPacket = await stringifyIO(parsedPayload);
319
845
  return {
846
+ task: taskIdentifier,
320
847
  payload: payloadPacket.data,
321
848
  options: {
322
849
  queue: item.options?.queue ?? queue,
@@ -324,6 +851,7 @@ async function batchTrigger_internal(name, id, items, parsePayload, requestOptio
324
851
  test: taskContext.ctx?.run.isTest,
325
852
  payloadType: payloadPacket.dataType,
326
853
  idempotencyKey: await makeIdempotencyKey(item.options?.idempotencyKey),
854
+ idempotencyKeyTTL: item.options?.idempotencyKeyTTL,
327
855
  delay: item.options?.delay,
328
856
  ttl: item.options?.ttl,
329
857
  tags: item.options?.tags,
@@ -334,20 +862,40 @@ async function batchTrigger_internal(name, id, items, parsePayload, requestOptio
334
862
  },
335
863
  };
336
864
  })),
337
- }, { spanParentAsLink: true }, {
865
+ }, {
866
+ spanParentAsLink: true,
867
+ idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
868
+ idempotencyKeyTTL: options?.idempotencyKeyTTL,
869
+ }, {
338
870
  name,
339
871
  tracer,
340
872
  icon: "trigger",
341
- attributes: {
342
- [SEMATTRS_MESSAGING_OPERATION]: "publish",
343
- ["messaging.client_id"]: taskContext.worker?.id,
344
- [SEMATTRS_MESSAGING_SYSTEM]: "trigger.dev",
873
+ onResponseBody(body, span) {
874
+ if (body && typeof body === "object" && !Array.isArray(body)) {
875
+ if ("id" in body && typeof body.id === "string") {
876
+ span.setAttribute("batchId", body.id);
877
+ }
878
+ if ("runs" in body && Array.isArray(body.runs)) {
879
+ span.setAttribute("runCount", body.runs.length);
880
+ }
881
+ if ("isCached" in body && typeof body.isCached === "boolean") {
882
+ if (body.isCached) {
883
+ console.warn(`Result is a cached response because the request was idempotent.`);
884
+ }
885
+ span.setAttribute("isCached", body.isCached);
886
+ }
887
+ if ("idempotencyKey" in body && typeof body.idempotencyKey === "string") {
888
+ span.setAttribute("idempotencyKey", body.idempotencyKey);
889
+ }
890
+ }
345
891
  },
346
892
  ...requestOptions,
347
893
  });
348
894
  const handle = {
349
- batchId: response.batchId,
350
- runs: response.runs.map((id) => ({ id })),
895
+ batchId: response.id,
896
+ isCached: response.isCached,
897
+ idempotencyKey: response.idempotencyKey,
898
+ runs: response.runs,
351
899
  publicAccessToken: response.publicAccessToken,
352
900
  };
353
901
  return handle;
@@ -370,7 +918,6 @@ async function triggerAndWait_internal(name, id, payload, parsePayload, options,
370
918
  concurrencyKey: options?.concurrencyKey,
371
919
  test: taskContext.ctx?.run.isTest,
372
920
  payloadType: payloadPacket.dataType,
373
- idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
374
921
  delay: options?.delay,
375
922
  ttl: options?.ttl,
376
923
  tags: options?.tags,
@@ -379,31 +926,15 @@ async function triggerAndWait_internal(name, id, payload, parsePayload, options,
379
926
  maxDuration: options?.maxDuration,
380
927
  },
381
928
  }, {}, requestOptions);
382
- span.setAttribute("messaging.message.id", response.id);
383
- if (options?.idempotencyKey) {
384
- // If an idempotency key is provided, we can check if the result is already available
385
- const result = await apiClient.getRunResult(response.id);
386
- if (result) {
387
- logger.log(`Result reused from previous task run with idempotency key '${options.idempotencyKey}'.`, {
388
- runId: response.id,
389
- idempotencyKey: options.idempotencyKey,
390
- });
391
- return await handleTaskRunExecutionResult(result);
392
- }
393
- }
929
+ span.setAttribute("runId", response.id);
394
930
  const result = await runtime.waitForTask({
395
931
  id: response.id,
396
932
  ctx,
397
933
  });
398
- return await handleTaskRunExecutionResult(result);
934
+ return await handleTaskRunExecutionResult(result, id);
399
935
  }, {
400
936
  kind: SpanKind.PRODUCER,
401
937
  attributes: {
402
- [SemanticInternalAttributes.STYLE_ICON]: "trigger",
403
- [SEMATTRS_MESSAGING_OPERATION]: "publish",
404
- ["messaging.client_id"]: taskContext.worker?.id,
405
- [SEMATTRS_MESSAGING_DESTINATION]: id,
406
- [SEMATTRS_MESSAGING_SYSTEM]: "trigger.dev",
407
938
  ...accessoryAttributes({
408
939
  items: [
409
940
  {
@@ -423,11 +954,12 @@ async function batchTriggerAndWait_internal(name, id, items, parsePayload, reque
423
954
  }
424
955
  const apiClient = apiClientManager.clientOrThrow();
425
956
  return await tracer.startActiveSpan(name, async (span) => {
426
- const response = await apiClient.batchTriggerTask(id, {
957
+ const response = await apiClient.batchTriggerV2({
427
958
  items: await Promise.all(items.map(async (item) => {
428
959
  const parsedPayload = parsePayload ? await parsePayload(item.payload) : item.payload;
429
960
  const payloadPacket = await stringifyIO(parsedPayload);
430
961
  return {
962
+ task: id,
431
963
  payload: payloadPacket.data,
432
964
  options: {
433
965
  lockToVersion: taskContext.worker?.version,
@@ -435,7 +967,6 @@ async function batchTriggerAndWait_internal(name, id, items, parsePayload, reque
435
967
  concurrencyKey: item.options?.concurrencyKey,
436
968
  test: taskContext.ctx?.run.isTest,
437
969
  payloadType: payloadPacket.dataType,
438
- idempotencyKey: await makeIdempotencyKey(item.options?.idempotencyKey),
439
970
  delay: item.options?.delay,
440
971
  ttl: item.options?.ttl,
441
972
  tags: item.options?.tags,
@@ -447,52 +978,21 @@ async function batchTriggerAndWait_internal(name, id, items, parsePayload, reque
447
978
  })),
448
979
  dependentAttempt: ctx.attempt.id,
449
980
  }, {}, requestOptions);
450
- span.setAttribute("messaging.message.id", response.batchId);
451
- const getBatchResults = async () => {
452
- // We need to check if the results are already available, but only if any of the items options has an idempotency key
453
- const hasIdempotencyKey = items.some((item) => item.options?.idempotencyKey);
454
- if (hasIdempotencyKey) {
455
- const results = await apiClient.getBatchResults(response.batchId);
456
- if (results) {
457
- return results;
458
- }
459
- }
460
- return {
461
- id: response.batchId,
462
- items: [],
463
- };
464
- };
465
- const existingResults = await getBatchResults();
466
- const incompleteRuns = response.runs.filter((runId) => !existingResults.items.some((item) => item.id === runId));
467
- if (incompleteRuns.length === 0) {
468
- logger.log(`Results reused from previous task runs because of the provided idempotency keys.`);
469
- // All runs are already completed
470
- const runs = await handleBatchTaskRunExecutionResult(existingResults.items);
471
- return {
472
- id: existingResults.id,
473
- runs,
474
- };
981
+ span.setAttribute("batchId", response.id);
982
+ span.setAttribute("runCount", response.runs.length);
983
+ span.setAttribute("isCached", response.isCached);
984
+ if (response.isCached) {
985
+ console.warn(`Result is a cached response because the request was idempotent.`);
986
+ }
987
+ if (response.idempotencyKey) {
988
+ span.setAttribute("idempotencyKey", response.idempotencyKey);
475
989
  }
476
990
  const result = await runtime.waitForBatch({
477
- id: response.batchId,
478
- runs: incompleteRuns,
991
+ id: response.id,
992
+ runs: response.runs.map((run) => run.id),
479
993
  ctx,
480
994
  });
481
- // Combine the already completed runs with the newly completed runs, ordered by the original order
482
- const combinedItems = [];
483
- for (const runId of response.runs) {
484
- const existingItem = existingResults.items.find((item) => item.id === runId);
485
- if (existingItem) {
486
- combinedItems.push(existingItem);
487
- }
488
- else {
489
- const newItem = result.items.find((item) => item.id === runId);
490
- if (newItem) {
491
- combinedItems.push(newItem);
492
- }
493
- }
494
- }
495
- const runs = await handleBatchTaskRunExecutionResult(combinedItems);
995
+ const runs = await handleBatchTaskRunExecutionResult(result.items, id);
496
996
  return {
497
997
  id: result.id,
498
998
  runs,
@@ -500,12 +1000,6 @@ async function batchTriggerAndWait_internal(name, id, items, parsePayload, reque
500
1000
  }, {
501
1001
  kind: SpanKind.PRODUCER,
502
1002
  attributes: {
503
- [SemanticInternalAttributes.STYLE_ICON]: "trigger",
504
- ["messaging.batch.message_count"]: items.length,
505
- [SEMATTRS_MESSAGING_OPERATION]: "publish",
506
- ["messaging.client_id"]: taskContext.worker?.id,
507
- [SEMATTRS_MESSAGING_DESTINATION]: id,
508
- [SEMATTRS_MESSAGING_SYSTEM]: "trigger.dev",
509
1003
  ...accessoryAttributes({
510
1004
  items: [
511
1005
  {
@@ -518,17 +1012,35 @@ async function batchTriggerAndWait_internal(name, id, items, parsePayload, reque
518
1012
  },
519
1013
  });
520
1014
  }
521
- async function handleBatchTaskRunExecutionResult(items) {
1015
+ async function handleBatchTaskRunExecutionResult(items, taskIdentifier) {
1016
+ const someObjectStoreOutputs = items.some((item) => item.ok && item.outputType === "application/store");
1017
+ if (!someObjectStoreOutputs) {
1018
+ const results = await Promise.all(items.map(async (item) => {
1019
+ return await handleTaskRunExecutionResult(item, taskIdentifier);
1020
+ }));
1021
+ return results;
1022
+ }
1023
+ return await tracer.startActiveSpan("store.downloadPayloads", async (span) => {
1024
+ const results = await Promise.all(items.map(async (item) => {
1025
+ return await handleTaskRunExecutionResult(item, taskIdentifier);
1026
+ }));
1027
+ return results;
1028
+ }, {
1029
+ kind: SpanKind.INTERNAL,
1030
+ [SemanticInternalAttributes.STYLE_ICON]: "cloud-download",
1031
+ });
1032
+ }
1033
+ async function handleBatchTaskRunExecutionResultV2(items) {
522
1034
  const someObjectStoreOutputs = items.some((item) => item.ok && item.outputType === "application/store");
523
1035
  if (!someObjectStoreOutputs) {
524
1036
  const results = await Promise.all(items.map(async (item) => {
525
- return await handleTaskRunExecutionResult(item);
1037
+ return await handleTaskRunExecutionResult(item, item.taskIdentifier ?? "unknown");
526
1038
  }));
527
1039
  return results;
528
1040
  }
529
1041
  return await tracer.startActiveSpan("store.downloadPayloads", async (span) => {
530
1042
  const results = await Promise.all(items.map(async (item) => {
531
- return await handleTaskRunExecutionResult(item);
1043
+ return await handleTaskRunExecutionResult(item, item.taskIdentifier ?? "unknown");
532
1044
  }));
533
1045
  return results;
534
1046
  }, {
@@ -536,13 +1048,14 @@ async function handleBatchTaskRunExecutionResult(items) {
536
1048
  [SemanticInternalAttributes.STYLE_ICON]: "cloud-download",
537
1049
  });
538
1050
  }
539
- async function handleTaskRunExecutionResult(execution) {
1051
+ async function handleTaskRunExecutionResult(execution, taskIdentifier) {
540
1052
  if (execution.ok) {
541
1053
  const outputPacket = { data: execution.output, dataType: execution.outputType };
542
1054
  const importedPacket = await conditionallyImportPacket(outputPacket, tracer);
543
1055
  return {
544
1056
  ok: true,
545
1057
  id: execution.id,
1058
+ taskIdentifier: (execution.taskIdentifier ?? taskIdentifier),
546
1059
  output: await parsePacket(importedPacket),
547
1060
  };
548
1061
  }
@@ -550,6 +1063,7 @@ async function handleTaskRunExecutionResult(execution) {
550
1063
  return {
551
1064
  ok: false,
552
1065
  id: execution.id,
1066
+ taskIdentifier: (execution.taskIdentifier ?? taskIdentifier),
553
1067
  error: createErrorTaskError(execution.error),
554
1068
  };
555
1069
  }