@trigger.dev/sdk 4.3.0 → 4.3.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/dist/commonjs/v3/batch.d.ts +3 -3
- package/dist/commonjs/v3/batch.js.map +1 -1
- package/dist/commonjs/v3/idempotencyKeys.d.ts +2 -1
- package/dist/commonjs/v3/idempotencyKeys.js +1 -0
- package/dist/commonjs/v3/idempotencyKeys.js.map +1 -1
- package/dist/commonjs/v3/index.d.ts +1 -1
- package/dist/commonjs/v3/index.js +2 -1
- package/dist/commonjs/v3/index.js.map +1 -1
- package/dist/commonjs/v3/shared.d.ts +83 -1
- package/dist/commonjs/v3/shared.js +933 -541
- package/dist/commonjs/v3/shared.js.map +1 -1
- package/dist/commonjs/v3/shared.test.d.ts +1 -0
- package/dist/commonjs/v3/shared.test.js +190 -0
- package/dist/commonjs/v3/shared.test.js.map +1 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/batch.d.ts +3 -3
- package/dist/esm/v3/batch.js +1 -1
- package/dist/esm/v3/batch.js.map +1 -1
- package/dist/esm/v3/idempotencyKeys.d.ts +2 -1
- package/dist/esm/v3/idempotencyKeys.js +2 -1
- package/dist/esm/v3/idempotencyKeys.js.map +1 -1
- package/dist/esm/v3/index.d.ts +1 -1
- package/dist/esm/v3/index.js +1 -1
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/esm/v3/shared.d.ts +83 -1
- package/dist/esm/v3/shared.js +931 -541
- package/dist/esm/v3/shared.js.map +1 -1
- package/dist/esm/v3/shared.test.d.ts +1 -0
- package/dist/esm/v3/shared.test.js +188 -0
- package/dist/esm/v3/shared.test.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/package.json +3 -2
package/dist/esm/v3/shared.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SpanKind } from "@opentelemetry/api";
|
|
2
|
-
import { accessoryAttributes, apiClientManager, conditionallyImportPacket, convertToolParametersToSchema, createErrorTaskError, defaultRetryOptions, flattenIdempotencyKey, getEnvVar, getSchemaParseFn, lifecycleHooks, makeIdempotencyKey, parsePacket, resourceCatalog, runtime, SemanticInternalAttributes, stringifyIO, SubtaskUnwrapError, taskContext, TaskRunPromise, } from "@trigger.dev/core/v3";
|
|
2
|
+
import { accessoryAttributes, ApiError, apiClientManager, conditionallyImportPacket, convertToolParametersToSchema, createErrorTaskError, defaultRetryOptions, flattenIdempotencyKey, getEnvVar, getSchemaParseFn, lifecycleHooks, makeIdempotencyKey, parsePacket, RateLimitError, resourceCatalog, runtime, SemanticInternalAttributes, stringifyIO, SubtaskUnwrapError, taskContext, TaskRunPromise, } from "@trigger.dev/core/v3";
|
|
3
3
|
import { tracer } from "./tracer.js";
|
|
4
4
|
export { SubtaskUnwrapError, TaskRunPromise };
|
|
5
5
|
export function queue(options) {
|
|
@@ -221,63 +221,14 @@ export async function batchTriggerAndWait(id, items, options, requestOptions) {
|
|
|
221
221
|
export async function batchTrigger(id, items, options, requestOptions) {
|
|
222
222
|
return await batchTrigger_internal("tasks.batchTrigger()", id, items, options, undefined, requestOptions);
|
|
223
223
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
* @template TTask - The type of task(s) to be triggered, extends AnyTask
|
|
228
|
-
*
|
|
229
|
-
* @param {Array<BatchByIdItem<InferRunTypes<TTask>>>} items - Array of task items to trigger
|
|
230
|
-
* @param {BatchTriggerOptions} [options] - Optional batch-level trigger options
|
|
231
|
-
* @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
|
|
232
|
-
*
|
|
233
|
-
* @returns {Promise<BatchRunHandleFromTypes<InferRunTypes<TTask>>>} A promise that resolves with the batch run handle
|
|
234
|
-
* containing batch ID, cached status, idempotency info, runs, and public access token
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```ts
|
|
238
|
-
* import { batch } from "@trigger.dev/sdk/v3";
|
|
239
|
-
* import type { myTask1, myTask2 } from "~/trigger/myTasks";
|
|
240
|
-
*
|
|
241
|
-
* // Trigger multiple tasks with different payloads
|
|
242
|
-
* const result = await batch.trigger<typeof myTask1 | typeof myTask2>([
|
|
243
|
-
* {
|
|
244
|
-
* id: "my-task-1",
|
|
245
|
-
* payload: { some: "data" },
|
|
246
|
-
* options: {
|
|
247
|
-
* queue: "default",
|
|
248
|
-
* concurrencyKey: "key",
|
|
249
|
-
* idempotencyKey: "unique-key",
|
|
250
|
-
* delay: "5m",
|
|
251
|
-
* tags: ["tag1", "tag2"]
|
|
252
|
-
* }
|
|
253
|
-
* },
|
|
254
|
-
* {
|
|
255
|
-
* id: "my-task-2",
|
|
256
|
-
* payload: { other: "data" }
|
|
257
|
-
* }
|
|
258
|
-
* ]);
|
|
259
|
-
* ```
|
|
260
|
-
*
|
|
261
|
-
* @description
|
|
262
|
-
* Each task item in the array can include:
|
|
263
|
-
* - `id`: The unique identifier of the task
|
|
264
|
-
* - `payload`: The data to pass to the task
|
|
265
|
-
* - `options`: Optional task-specific settings including:
|
|
266
|
-
* - `queue`: Specify a queue for the task
|
|
267
|
-
* - `concurrencyKey`: Control concurrent execution
|
|
268
|
-
* - `idempotencyKey`: Prevent duplicate runs
|
|
269
|
-
* - `idempotencyKeyTTL`: Time-to-live for idempotency key
|
|
270
|
-
* - `delay`: Delay before task execution
|
|
271
|
-
* - `ttl`: Time-to-live for the task
|
|
272
|
-
* - `tags`: Array of tags for the task
|
|
273
|
-
* - `maxAttempts`: Maximum retry attempts
|
|
274
|
-
* - `metadata`: Additional metadata
|
|
275
|
-
* - `maxDuration`: Maximum execution duration
|
|
276
|
-
*/
|
|
277
|
-
export async function batchTriggerById(items, options, requestOptions) {
|
|
224
|
+
// Implementation
|
|
225
|
+
export async function batchTriggerById(...args) {
|
|
226
|
+
const [items, options, requestOptions] = args;
|
|
278
227
|
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
279
|
-
|
|
280
|
-
|
|
228
|
+
// Check if items is an array or a stream
|
|
229
|
+
if (Array.isArray(items)) {
|
|
230
|
+
// Array path: existing logic
|
|
231
|
+
const ndJsonItems = await Promise.all(items.map(async (item, index) => {
|
|
281
232
|
const taskMetadata = resourceCatalog.getTask(item.id);
|
|
282
233
|
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
283
234
|
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
@@ -285,6 +236,7 @@ export async function batchTriggerById(items, options, requestOptions) {
|
|
|
285
236
|
const payloadPacket = await stringifyIO(parsedPayload);
|
|
286
237
|
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
287
238
|
return {
|
|
239
|
+
index,
|
|
288
240
|
task: item.id,
|
|
289
241
|
payload: payloadPacket.data,
|
|
290
242
|
options: {
|
|
@@ -304,257 +256,266 @@ export async function batchTriggerById(items, options, requestOptions) {
|
|
|
304
256
|
priority: item.options?.priority,
|
|
305
257
|
region: item.options?.region,
|
|
306
258
|
lockToVersion: item.options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
259
|
+
debounce: item.options?.debounce,
|
|
307
260
|
},
|
|
308
261
|
};
|
|
309
|
-
}))
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
262
|
+
}));
|
|
263
|
+
// Execute 2-phase batch
|
|
264
|
+
const response = await tracer.startActiveSpan("batch.trigger()", async (span) => {
|
|
265
|
+
const result = await executeBatchTwoPhase(apiClient, ndJsonItems, {
|
|
266
|
+
parentRunId: taskContext.ctx?.run.id,
|
|
267
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
268
|
+
spanParentAsLink: true, // Fire-and-forget: child runs get separate trace IDs
|
|
269
|
+
}, requestOptions);
|
|
270
|
+
span.setAttribute("batchId", result.id);
|
|
271
|
+
span.setAttribute("runCount", result.runCount);
|
|
272
|
+
return result;
|
|
273
|
+
}, {
|
|
274
|
+
kind: SpanKind.PRODUCER,
|
|
275
|
+
attributes: {
|
|
276
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const handle = {
|
|
280
|
+
batchId: response.id,
|
|
281
|
+
runCount: response.runCount,
|
|
282
|
+
publicAccessToken: response.publicAccessToken,
|
|
283
|
+
};
|
|
284
|
+
return handle;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// Stream path: convert to AsyncIterable and transform
|
|
288
|
+
const asyncItems = normalizeToAsyncIterable(items);
|
|
289
|
+
const transformedItems = transformBatchItemsStream(asyncItems, options);
|
|
290
|
+
// Execute streaming 2-phase batch
|
|
291
|
+
const response = await tracer.startActiveSpan("batch.trigger()", async (span) => {
|
|
292
|
+
const result = await executeBatchTwoPhaseStreaming(apiClient, transformedItems, {
|
|
293
|
+
parentRunId: taskContext.ctx?.run.id,
|
|
294
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
295
|
+
spanParentAsLink: true, // Fire-and-forget: child runs get separate trace IDs
|
|
296
|
+
}, requestOptions);
|
|
297
|
+
span.setAttribute("batchId", result.id);
|
|
298
|
+
span.setAttribute("runCount", result.runCount);
|
|
299
|
+
return result;
|
|
300
|
+
}, {
|
|
301
|
+
kind: SpanKind.PRODUCER,
|
|
302
|
+
attributes: {
|
|
303
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const handle = {
|
|
307
|
+
batchId: response.id,
|
|
308
|
+
runCount: response.runCount,
|
|
309
|
+
publicAccessToken: response.publicAccessToken,
|
|
310
|
+
};
|
|
311
|
+
return handle;
|
|
312
|
+
}
|
|
336
313
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
*
|
|
341
|
-
* @template TTask - Union type of tasks to be triggered, extends AnyTask
|
|
342
|
-
*
|
|
343
|
-
* @param {Array<BatchByIdAndWaitItem<InferRunTypes<TTask>>>} items - Array of task items to trigger
|
|
344
|
-
* @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
|
|
345
|
-
*
|
|
346
|
-
* @returns {Promise<BatchByIdResult<TTask>>} A promise that resolves with the batch results, including
|
|
347
|
-
* success/failure status and strongly-typed outputs for each task
|
|
348
|
-
*
|
|
349
|
-
* @throws {Error} If called outside of a task.run() context
|
|
350
|
-
* @throws {Error} If no API client is configured
|
|
351
|
-
*
|
|
352
|
-
* @example
|
|
353
|
-
* ```ts
|
|
354
|
-
* import { batch, task } from "@trigger.dev/sdk/v3";
|
|
355
|
-
*
|
|
356
|
-
* export const parentTask = task({
|
|
357
|
-
* id: "parent-task",
|
|
358
|
-
* run: async (payload: string) => {
|
|
359
|
-
* const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
|
|
360
|
-
* {
|
|
361
|
-
* id: "child-task-1",
|
|
362
|
-
* payload: { foo: "World" },
|
|
363
|
-
* options: {
|
|
364
|
-
* queue: "default",
|
|
365
|
-
* delay: "5m",
|
|
366
|
-
* tags: ["batch", "child1"]
|
|
367
|
-
* }
|
|
368
|
-
* },
|
|
369
|
-
* {
|
|
370
|
-
* id: "child-task-2",
|
|
371
|
-
* payload: { bar: 42 }
|
|
372
|
-
* }
|
|
373
|
-
* ]);
|
|
374
|
-
*
|
|
375
|
-
* // Type-safe result handling
|
|
376
|
-
* for (const result of results) {
|
|
377
|
-
* if (result.ok) {
|
|
378
|
-
* switch (result.taskIdentifier) {
|
|
379
|
-
* case "child-task-1":
|
|
380
|
-
* console.log("Child task 1 output:", result.output); // string type
|
|
381
|
-
* break;
|
|
382
|
-
* case "child-task-2":
|
|
383
|
-
* console.log("Child task 2 output:", result.output); // number type
|
|
384
|
-
* break;
|
|
385
|
-
* }
|
|
386
|
-
* } else {
|
|
387
|
-
* console.error("Task failed:", result.error);
|
|
388
|
-
* }
|
|
389
|
-
* }
|
|
390
|
-
* }
|
|
391
|
-
* });
|
|
392
|
-
* ```
|
|
393
|
-
*
|
|
394
|
-
* @description
|
|
395
|
-
* Each task item in the array can include:
|
|
396
|
-
* - `id`: The task identifier (must match one of the tasks in the union type)
|
|
397
|
-
* - `payload`: Strongly-typed payload matching the task's input type
|
|
398
|
-
* - `options`: Optional task-specific settings including:
|
|
399
|
-
* - `queue`: Specify a queue for the task
|
|
400
|
-
* - `concurrencyKey`: Control concurrent execution
|
|
401
|
-
* - `delay`: Delay before task execution
|
|
402
|
-
* - `ttl`: Time-to-live for the task
|
|
403
|
-
* - `tags`: Array of tags for the task
|
|
404
|
-
* - `maxAttempts`: Maximum retry attempts
|
|
405
|
-
* - `metadata`: Additional metadata
|
|
406
|
-
* - `maxDuration`: Maximum execution duration
|
|
407
|
-
*
|
|
408
|
-
* The function provides full type safety for:
|
|
409
|
-
* - Task IDs
|
|
410
|
-
* - Payload types
|
|
411
|
-
* - Return value types
|
|
412
|
-
* - Error handling
|
|
413
|
-
*/
|
|
414
|
-
export async function batchTriggerByIdAndWait(items, options, requestOptions) {
|
|
314
|
+
// Implementation
|
|
315
|
+
export async function batchTriggerByIdAndWait(...args) {
|
|
316
|
+
const [items, options, requestOptions] = args;
|
|
415
317
|
const ctx = taskContext.ctx;
|
|
416
318
|
if (!ctx) {
|
|
417
319
|
throw new Error("batchTriggerAndWait can only be used from inside a task.run()");
|
|
418
320
|
}
|
|
419
321
|
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
322
|
+
// Check if items is an array or a stream
|
|
323
|
+
if (Array.isArray(items)) {
|
|
324
|
+
// Array path: existing logic
|
|
325
|
+
const ndJsonItems = await Promise.all(items.map(async (item, index) => {
|
|
326
|
+
const taskMetadata = resourceCatalog.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
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
332
|
+
return {
|
|
333
|
+
index,
|
|
334
|
+
task: item.id,
|
|
335
|
+
payload: payloadPacket.data,
|
|
336
|
+
options: {
|
|
337
|
+
lockToVersion: taskContext.worker?.version,
|
|
338
|
+
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
339
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
340
|
+
test: taskContext.ctx?.run.isTest,
|
|
341
|
+
payloadType: payloadPacket.dataType,
|
|
342
|
+
delay: item.options?.delay,
|
|
343
|
+
ttl: item.options?.ttl,
|
|
344
|
+
tags: item.options?.tags,
|
|
345
|
+
maxAttempts: item.options?.maxAttempts,
|
|
346
|
+
metadata: item.options?.metadata,
|
|
347
|
+
maxDuration: item.options?.maxDuration,
|
|
348
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
349
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
350
|
+
machine: item.options?.machine,
|
|
351
|
+
priority: item.options?.priority,
|
|
352
|
+
region: item.options?.region,
|
|
353
|
+
debounce: item.options?.debounce,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}));
|
|
357
|
+
return await tracer.startActiveSpan("batch.triggerAndWait()", async (span) => {
|
|
358
|
+
// Execute 2-phase batch
|
|
359
|
+
const response = await executeBatchTwoPhase(apiClient, ndJsonItems, {
|
|
360
|
+
parentRunId: ctx.run.id,
|
|
361
|
+
resumeParentOnCompletion: true,
|
|
362
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
363
|
+
spanParentAsLink: false, // Waiting: child runs share parent's trace ID
|
|
364
|
+
}, requestOptions);
|
|
365
|
+
span.setAttribute("batchId", response.id);
|
|
366
|
+
span.setAttribute("runCount", response.runCount);
|
|
367
|
+
const result = await runtime.waitForBatch({
|
|
368
|
+
id: response.id,
|
|
369
|
+
runCount: response.runCount,
|
|
370
|
+
ctx,
|
|
371
|
+
});
|
|
372
|
+
const runs = await handleBatchTaskRunExecutionResultV2(result.items);
|
|
373
|
+
return {
|
|
374
|
+
id: result.id,
|
|
375
|
+
runs,
|
|
376
|
+
};
|
|
455
377
|
}, {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
378
|
+
kind: SpanKind.PRODUCER,
|
|
379
|
+
attributes: {
|
|
380
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// Stream path: convert to AsyncIterable and transform
|
|
386
|
+
const asyncItems = normalizeToAsyncIterable(items);
|
|
387
|
+
const transformedItems = transformBatchItemsStreamForWait(asyncItems, options);
|
|
388
|
+
return await tracer.startActiveSpan("batch.triggerAndWait()", async (span) => {
|
|
389
|
+
// Execute streaming 2-phase batch
|
|
390
|
+
const response = await executeBatchTwoPhaseStreaming(apiClient, transformedItems, {
|
|
391
|
+
parentRunId: ctx.run.id,
|
|
392
|
+
resumeParentOnCompletion: true,
|
|
393
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
394
|
+
spanParentAsLink: false, // Waiting: child runs share parent's trace ID
|
|
395
|
+
}, requestOptions);
|
|
396
|
+
span.setAttribute("batchId", response.id);
|
|
397
|
+
span.setAttribute("runCount", response.runCount);
|
|
398
|
+
const result = await runtime.waitForBatch({
|
|
399
|
+
id: response.id,
|
|
400
|
+
runCount: response.runCount,
|
|
401
|
+
ctx,
|
|
402
|
+
});
|
|
403
|
+
const runs = await handleBatchTaskRunExecutionResultV2(result.items);
|
|
404
|
+
return {
|
|
405
|
+
id: result.id,
|
|
406
|
+
runs,
|
|
407
|
+
};
|
|
408
|
+
}, {
|
|
409
|
+
kind: SpanKind.PRODUCER,
|
|
410
|
+
attributes: {
|
|
411
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Implementation
|
|
417
|
+
export async function batchTriggerTasks(...args) {
|
|
418
|
+
const [items, options, requestOptions] = args;
|
|
419
|
+
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
420
|
+
// Check if items is an array or a stream
|
|
421
|
+
if (Array.isArray(items)) {
|
|
422
|
+
// Array path: existing logic
|
|
423
|
+
const ndJsonItems = await Promise.all(items.map(async (item, index) => {
|
|
424
|
+
const taskMetadata = resourceCatalog.getTask(item.task.id);
|
|
425
|
+
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
426
|
+
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
427
|
+
: item.payload;
|
|
428
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
429
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
430
|
+
return {
|
|
431
|
+
index,
|
|
432
|
+
task: item.task.id,
|
|
433
|
+
payload: payloadPacket.data,
|
|
434
|
+
options: {
|
|
435
|
+
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
436
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
437
|
+
test: taskContext.ctx?.run.isTest,
|
|
438
|
+
payloadType: payloadPacket.dataType,
|
|
439
|
+
delay: item.options?.delay,
|
|
440
|
+
ttl: item.options?.ttl,
|
|
441
|
+
tags: item.options?.tags,
|
|
442
|
+
maxAttempts: item.options?.maxAttempts,
|
|
443
|
+
metadata: item.options?.metadata,
|
|
444
|
+
maxDuration: item.options?.maxDuration,
|
|
445
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
446
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
447
|
+
machine: item.options?.machine,
|
|
448
|
+
priority: item.options?.priority,
|
|
449
|
+
region: item.options?.region,
|
|
450
|
+
lockToVersion: item.options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
451
|
+
debounce: item.options?.debounce,
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
}));
|
|
455
|
+
// Execute 2-phase batch
|
|
456
|
+
const response = await tracer.startActiveSpan("batch.triggerByTask()", async (span) => {
|
|
457
|
+
const result = await executeBatchTwoPhase(apiClient, ndJsonItems, {
|
|
458
|
+
parentRunId: taskContext.ctx?.run.id,
|
|
459
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
460
|
+
spanParentAsLink: true, // Fire-and-forget: child runs get separate trace IDs
|
|
461
|
+
}, requestOptions);
|
|
462
|
+
span.setAttribute("batchId", result.id);
|
|
463
|
+
span.setAttribute("runCount", result.runCount);
|
|
464
|
+
return result;
|
|
465
|
+
}, {
|
|
466
|
+
kind: SpanKind.PRODUCER,
|
|
467
|
+
attributes: {
|
|
468
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
const handle = {
|
|
472
|
+
batchId: response.id,
|
|
462
473
|
runCount: response.runCount,
|
|
463
|
-
|
|
474
|
+
publicAccessToken: response.publicAccessToken,
|
|
475
|
+
};
|
|
476
|
+
return handle;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Stream path: convert to AsyncIterable and transform
|
|
480
|
+
const streamItems = items;
|
|
481
|
+
const asyncItems = normalizeToAsyncIterable(streamItems);
|
|
482
|
+
const transformedItems = transformBatchByTaskItemsStream(asyncItems, options);
|
|
483
|
+
// Execute streaming 2-phase batch
|
|
484
|
+
const response = await tracer.startActiveSpan("batch.triggerByTask()", async (span) => {
|
|
485
|
+
const result = await executeBatchTwoPhaseStreaming(apiClient, transformedItems, {
|
|
486
|
+
parentRunId: taskContext.ctx?.run.id,
|
|
487
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
488
|
+
spanParentAsLink: true, // Fire-and-forget: child runs get separate trace IDs
|
|
489
|
+
}, requestOptions);
|
|
490
|
+
span.setAttribute("batchId", result.id);
|
|
491
|
+
span.setAttribute("runCount", result.runCount);
|
|
492
|
+
return result;
|
|
493
|
+
}, {
|
|
494
|
+
kind: SpanKind.PRODUCER,
|
|
495
|
+
attributes: {
|
|
496
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
497
|
+
},
|
|
464
498
|
});
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
499
|
+
const handle = {
|
|
500
|
+
batchId: response.id,
|
|
501
|
+
runCount: response.runCount,
|
|
502
|
+
publicAccessToken: response.publicAccessToken,
|
|
469
503
|
};
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
attributes: {
|
|
473
|
-
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
474
|
-
},
|
|
475
|
-
});
|
|
504
|
+
return handle;
|
|
505
|
+
}
|
|
476
506
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
* @param {TriggerApiRequestOptions} [requestOptions] - Optional API request configuration
|
|
485
|
-
*
|
|
486
|
-
* @returns {Promise<BatchByIdResult<TTask>>} A promise that resolves with the batch results, including
|
|
487
|
-
* success/failure status and strongly-typed outputs for each task
|
|
488
|
-
*
|
|
489
|
-
* @throws {Error} If called outside of a task.run() context
|
|
490
|
-
* @throws {Error} If no API client is configured
|
|
491
|
-
*
|
|
492
|
-
* @example
|
|
493
|
-
* ```ts
|
|
494
|
-
* import { batch, task } from "@trigger.dev/sdk/v3";
|
|
495
|
-
*
|
|
496
|
-
* export const parentTask = task({
|
|
497
|
-
* id: "parent-task",
|
|
498
|
-
* run: async (payload: string) => {
|
|
499
|
-
* const results = await batch.triggerAndWait<typeof childTask1 | typeof childTask2>([
|
|
500
|
-
* {
|
|
501
|
-
* id: "child-task-1",
|
|
502
|
-
* payload: { foo: "World" },
|
|
503
|
-
* options: {
|
|
504
|
-
* queue: "default",
|
|
505
|
-
* delay: "5m",
|
|
506
|
-
* tags: ["batch", "child1"]
|
|
507
|
-
* }
|
|
508
|
-
* },
|
|
509
|
-
* {
|
|
510
|
-
* id: "child-task-2",
|
|
511
|
-
* payload: { bar: 42 }
|
|
512
|
-
* }
|
|
513
|
-
* ]);
|
|
514
|
-
*
|
|
515
|
-
* // Type-safe result handling
|
|
516
|
-
* for (const result of results) {
|
|
517
|
-
* if (result.ok) {
|
|
518
|
-
* switch (result.taskIdentifier) {
|
|
519
|
-
* case "child-task-1":
|
|
520
|
-
* console.log("Child task 1 output:", result.output); // string type
|
|
521
|
-
* break;
|
|
522
|
-
* case "child-task-2":
|
|
523
|
-
* console.log("Child task 2 output:", result.output); // number type
|
|
524
|
-
* break;
|
|
525
|
-
* }
|
|
526
|
-
* } else {
|
|
527
|
-
* console.error("Task failed:", result.error);
|
|
528
|
-
* }
|
|
529
|
-
* }
|
|
530
|
-
* }
|
|
531
|
-
* });
|
|
532
|
-
* ```
|
|
533
|
-
*
|
|
534
|
-
* @description
|
|
535
|
-
* Each task item in the array can include:
|
|
536
|
-
* - `id`: The task identifier (must match one of the tasks in the union type)
|
|
537
|
-
* - `payload`: Strongly-typed payload matching the task's input type
|
|
538
|
-
* - `options`: Optional task-specific settings including:
|
|
539
|
-
* - `queue`: Specify a queue for the task
|
|
540
|
-
* - `concurrencyKey`: Control concurrent execution
|
|
541
|
-
* - `delay`: Delay before task execution
|
|
542
|
-
* - `ttl`: Time-to-live for the task
|
|
543
|
-
* - `tags`: Array of tags for the task
|
|
544
|
-
* - `maxAttempts`: Maximum retry attempts
|
|
545
|
-
* - `metadata`: Additional metadata
|
|
546
|
-
* - `maxDuration`: Maximum execution duration
|
|
547
|
-
*
|
|
548
|
-
* The function provides full type safety for:
|
|
549
|
-
* - Task IDs
|
|
550
|
-
* - Payload types
|
|
551
|
-
* - Return value types
|
|
552
|
-
* - Error handling
|
|
553
|
-
*/
|
|
554
|
-
export async function batchTriggerTasks(items, options, requestOptions) {
|
|
507
|
+
// Implementation
|
|
508
|
+
export async function batchTriggerAndWaitTasks(...args) {
|
|
509
|
+
const [items, options, requestOptions] = args;
|
|
510
|
+
const ctx = taskContext.ctx;
|
|
511
|
+
if (!ctx) {
|
|
512
|
+
throw new Error("batchTriggerAndWait can only be used from inside a task.run()");
|
|
513
|
+
}
|
|
555
514
|
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
556
|
-
|
|
557
|
-
|
|
515
|
+
// Check if items is an array or a stream
|
|
516
|
+
if (Array.isArray(items)) {
|
|
517
|
+
// Array path: existing logic
|
|
518
|
+
const ndJsonItems = await Promise.all(items.map(async (item, index) => {
|
|
558
519
|
const taskMetadata = resourceCatalog.getTask(item.task.id);
|
|
559
520
|
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
560
521
|
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
@@ -562,9 +523,11 @@ export async function batchTriggerTasks(items, options, requestOptions) {
|
|
|
562
523
|
const payloadPacket = await stringifyIO(parsedPayload);
|
|
563
524
|
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
564
525
|
return {
|
|
526
|
+
index,
|
|
565
527
|
task: item.task.id,
|
|
566
528
|
payload: payloadPacket.data,
|
|
567
529
|
options: {
|
|
530
|
+
lockToVersion: taskContext.worker?.version,
|
|
568
531
|
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
569
532
|
concurrencyKey: item.options?.concurrencyKey,
|
|
570
533
|
test: taskContext.ctx?.run.isTest,
|
|
@@ -580,176 +543,511 @@ export async function batchTriggerTasks(items, options, requestOptions) {
|
|
|
580
543
|
machine: item.options?.machine,
|
|
581
544
|
priority: item.options?.priority,
|
|
582
545
|
region: item.options?.region,
|
|
583
|
-
|
|
546
|
+
debounce: item.options?.debounce,
|
|
584
547
|
},
|
|
585
548
|
};
|
|
586
|
-
}))
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
549
|
+
}));
|
|
550
|
+
return await tracer.startActiveSpan("batch.triggerByTaskAndWait()", async (span) => {
|
|
551
|
+
// Execute 2-phase batch
|
|
552
|
+
const response = await executeBatchTwoPhase(apiClient, ndJsonItems, {
|
|
553
|
+
parentRunId: ctx.run.id,
|
|
554
|
+
resumeParentOnCompletion: true,
|
|
555
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
556
|
+
spanParentAsLink: false, // Waiting: child runs share parent's trace ID
|
|
557
|
+
}, requestOptions);
|
|
558
|
+
span.setAttribute("batchId", response.id);
|
|
559
|
+
span.setAttribute("runCount", response.runCount);
|
|
560
|
+
const result = await runtime.waitForBatch({
|
|
561
|
+
id: response.id,
|
|
562
|
+
runCount: response.runCount,
|
|
563
|
+
ctx,
|
|
564
|
+
});
|
|
565
|
+
const runs = await handleBatchTaskRunExecutionResultV2(result.items);
|
|
566
|
+
return {
|
|
567
|
+
id: result.id,
|
|
568
|
+
runs,
|
|
569
|
+
};
|
|
570
|
+
}, {
|
|
571
|
+
kind: SpanKind.PRODUCER,
|
|
572
|
+
attributes: {
|
|
573
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
// Stream path: convert to AsyncIterable and transform
|
|
579
|
+
const streamItems = items;
|
|
580
|
+
const asyncItems = normalizeToAsyncIterable(streamItems);
|
|
581
|
+
const transformedItems = transformBatchByTaskItemsStreamForWait(asyncItems, options);
|
|
582
|
+
return await tracer.startActiveSpan("batch.triggerByTaskAndWait()", async (span) => {
|
|
583
|
+
// Execute streaming 2-phase batch
|
|
584
|
+
const response = await executeBatchTwoPhaseStreaming(apiClient, transformedItems, {
|
|
585
|
+
parentRunId: ctx.run.id,
|
|
586
|
+
resumeParentOnCompletion: true,
|
|
587
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
588
|
+
spanParentAsLink: false, // Waiting: child runs share parent's trace ID
|
|
589
|
+
}, requestOptions);
|
|
590
|
+
span.setAttribute("batchId", response.id);
|
|
591
|
+
span.setAttribute("runCount", response.runCount);
|
|
592
|
+
const result = await runtime.waitForBatch({
|
|
593
|
+
id: response.id,
|
|
594
|
+
runCount: response.runCount,
|
|
595
|
+
ctx,
|
|
596
|
+
});
|
|
597
|
+
const runs = await handleBatchTaskRunExecutionResultV2(result.items);
|
|
598
|
+
return {
|
|
599
|
+
id: result.id,
|
|
600
|
+
runs,
|
|
601
|
+
};
|
|
602
|
+
}, {
|
|
603
|
+
kind: SpanKind.PRODUCER,
|
|
604
|
+
attributes: {
|
|
605
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
}
|
|
613
609
|
}
|
|
614
610
|
/**
|
|
615
|
-
*
|
|
616
|
-
*
|
|
611
|
+
* Helper function that executes a 2-phase batch trigger:
|
|
612
|
+
* 1. Creates the batch record with expected run count
|
|
613
|
+
* 2. Streams items as NDJSON to the server
|
|
617
614
|
*
|
|
618
|
-
* @
|
|
615
|
+
* @param apiClient - The API client instance
|
|
616
|
+
* @param items - Array of batch items
|
|
617
|
+
* @param options - Batch options including trace context settings
|
|
618
|
+
* @param options.spanParentAsLink - If true, child runs will have separate trace IDs with a link to parent.
|
|
619
|
+
* Use true for batchTrigger (fire-and-forget), false for batchTriggerAndWait.
|
|
620
|
+
* @param requestOptions - Optional request options
|
|
621
|
+
* @internal
|
|
622
|
+
*/
|
|
623
|
+
async function executeBatchTwoPhase(apiClient, items, options, requestOptions) {
|
|
624
|
+
let batch;
|
|
625
|
+
try {
|
|
626
|
+
// Phase 1: Create batch
|
|
627
|
+
batch = await apiClient.createBatch({
|
|
628
|
+
runCount: items.length,
|
|
629
|
+
parentRunId: options.parentRunId,
|
|
630
|
+
resumeParentOnCompletion: options.resumeParentOnCompletion,
|
|
631
|
+
idempotencyKey: options.idempotencyKey,
|
|
632
|
+
}, { spanParentAsLink: options.spanParentAsLink }, requestOptions);
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
// Wrap with context about which phase failed
|
|
636
|
+
throw new BatchTriggerError(`Failed to create batch with ${items.length} items`, {
|
|
637
|
+
cause: error,
|
|
638
|
+
phase: "create",
|
|
639
|
+
itemCount: items.length,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
// If the batch was cached (idempotent replay), skip streaming items
|
|
643
|
+
if (!batch.isCached) {
|
|
644
|
+
try {
|
|
645
|
+
// Phase 2: Stream items
|
|
646
|
+
await apiClient.streamBatchItems(batch.id, items, requestOptions);
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
// Wrap with context about which phase failed and include batch ID
|
|
650
|
+
throw new BatchTriggerError(`Failed to stream items for batch ${batch.id} (${items.length} items)`, { cause: error, phase: "stream", batchId: batch.id, itemCount: items.length });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
id: batch.id,
|
|
655
|
+
runCount: batch.runCount,
|
|
656
|
+
publicAccessToken: batch.publicAccessToken,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Error thrown when batch trigger operations fail.
|
|
661
|
+
* Includes context about which phase failed and the batch details.
|
|
619
662
|
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
663
|
+
* When the underlying error is a rate limit (429), additional properties are exposed:
|
|
664
|
+
* - `isRateLimited`: true
|
|
665
|
+
* - `retryAfterMs`: milliseconds until the rate limit resets
|
|
666
|
+
*/
|
|
667
|
+
export class BatchTriggerError extends Error {
|
|
668
|
+
phase;
|
|
669
|
+
batchId;
|
|
670
|
+
itemCount;
|
|
671
|
+
/** True if the error was caused by rate limiting (HTTP 429) */
|
|
672
|
+
isRateLimited;
|
|
673
|
+
/** Milliseconds until the rate limit resets. Only set when `isRateLimited` is true. */
|
|
674
|
+
retryAfterMs;
|
|
675
|
+
/** The underlying API error, if the cause was an ApiError */
|
|
676
|
+
apiError;
|
|
677
|
+
/** The underlying cause of the error */
|
|
678
|
+
cause;
|
|
679
|
+
constructor(message, options) {
|
|
680
|
+
// Build enhanced message that includes the cause's message
|
|
681
|
+
const fullMessage = buildBatchErrorMessage(message, options.cause);
|
|
682
|
+
super(fullMessage, { cause: options.cause });
|
|
683
|
+
this.name = "BatchTriggerError";
|
|
684
|
+
this.cause = options.cause;
|
|
685
|
+
this.phase = options.phase;
|
|
686
|
+
this.batchId = options.batchId;
|
|
687
|
+
this.itemCount = options.itemCount;
|
|
688
|
+
// Extract rate limit info from cause
|
|
689
|
+
if (options.cause instanceof RateLimitError) {
|
|
690
|
+
this.isRateLimited = true;
|
|
691
|
+
this.retryAfterMs = options.cause.millisecondsUntilReset;
|
|
692
|
+
this.apiError = options.cause;
|
|
693
|
+
}
|
|
694
|
+
else if (options.cause instanceof ApiError) {
|
|
695
|
+
this.isRateLimited = options.cause.status === 429;
|
|
696
|
+
this.apiError = options.cause;
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
this.isRateLimited = false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Build an enhanced error message that includes context from the cause.
|
|
705
|
+
*/
|
|
706
|
+
function buildBatchErrorMessage(baseMessage, cause) {
|
|
707
|
+
if (!cause) {
|
|
708
|
+
return baseMessage;
|
|
709
|
+
}
|
|
710
|
+
// Handle RateLimitError specifically for better messaging
|
|
711
|
+
if (cause instanceof RateLimitError) {
|
|
712
|
+
const retryMs = cause.millisecondsUntilReset;
|
|
713
|
+
if (retryMs !== undefined) {
|
|
714
|
+
const retrySeconds = Math.ceil(retryMs / 1000);
|
|
715
|
+
return `${baseMessage}: Rate limit exceeded - retry after ${retrySeconds}s`;
|
|
716
|
+
}
|
|
717
|
+
return `${baseMessage}: Rate limit exceeded`;
|
|
718
|
+
}
|
|
719
|
+
// Handle other ApiErrors
|
|
720
|
+
if (cause instanceof ApiError) {
|
|
721
|
+
return `${baseMessage}: ${cause.message}`;
|
|
722
|
+
}
|
|
723
|
+
// Handle generic errors
|
|
724
|
+
if (cause instanceof Error) {
|
|
725
|
+
return `${baseMessage}: ${cause.message}`;
|
|
726
|
+
}
|
|
727
|
+
return baseMessage;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Execute a streaming 2-phase batch trigger where items are streamed from an AsyncIterable.
|
|
731
|
+
* Unlike executeBatchTwoPhase, this doesn't know the count upfront.
|
|
622
732
|
*
|
|
623
|
-
* @
|
|
624
|
-
*
|
|
733
|
+
* @param apiClient - The API client instance
|
|
734
|
+
* @param items - AsyncIterable of batch items
|
|
735
|
+
* @param options - Batch options including trace context settings
|
|
736
|
+
* @param options.spanParentAsLink - If true, child runs will have separate trace IDs with a link to parent.
|
|
737
|
+
* Use true for batchTrigger (fire-and-forget), false for batchTriggerAndWait.
|
|
738
|
+
* @param requestOptions - Optional request options
|
|
739
|
+
* @internal
|
|
740
|
+
*/
|
|
741
|
+
async function executeBatchTwoPhaseStreaming(apiClient, items, options, requestOptions) {
|
|
742
|
+
// For streaming, we need to buffer items to get the count first
|
|
743
|
+
// This is because createBatch requires runCount upfront
|
|
744
|
+
// In the future, we could add a streaming-first endpoint that doesn't require this
|
|
745
|
+
const itemsArray = [];
|
|
746
|
+
for await (const item of items) {
|
|
747
|
+
itemsArray.push(item);
|
|
748
|
+
}
|
|
749
|
+
// Now we can use the regular 2-phase approach
|
|
750
|
+
return executeBatchTwoPhase(apiClient, itemsArray, options, requestOptions);
|
|
751
|
+
}
|
|
752
|
+
// ============================================================================
|
|
753
|
+
// Streaming Helpers
|
|
754
|
+
// ============================================================================
|
|
755
|
+
/**
|
|
756
|
+
* Type guard to check if a value is an AsyncIterable
|
|
757
|
+
*/
|
|
758
|
+
function isAsyncIterable(value) {
|
|
759
|
+
return (value != null &&
|
|
760
|
+
typeof value === "object" &&
|
|
761
|
+
Symbol.asyncIterator in value &&
|
|
762
|
+
typeof value[Symbol.asyncIterator] === "function");
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Type guard to check if a value is a ReadableStream
|
|
766
|
+
*/
|
|
767
|
+
function isReadableStream(value) {
|
|
768
|
+
return (value != null &&
|
|
769
|
+
typeof value === "object" &&
|
|
770
|
+
"getReader" in value &&
|
|
771
|
+
typeof value.getReader === "function");
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Convert a ReadableStream to an AsyncIterable.
|
|
775
|
+
* Properly cancels the stream when the consumer terminates early.
|
|
625
776
|
*
|
|
626
|
-
* @
|
|
627
|
-
|
|
777
|
+
* @internal Exported for testing purposes
|
|
778
|
+
*/
|
|
779
|
+
export async function* readableStreamToAsyncIterable(stream) {
|
|
780
|
+
const reader = stream.getReader();
|
|
781
|
+
try {
|
|
782
|
+
while (true) {
|
|
783
|
+
const { done, value } = await reader.read();
|
|
784
|
+
if (done)
|
|
785
|
+
break;
|
|
786
|
+
yield value;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
try {
|
|
791
|
+
await reader.cancel();
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
// Ignore errors - stream might already be errored or closed
|
|
795
|
+
}
|
|
796
|
+
reader.releaseLock();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Normalize stream input to AsyncIterable
|
|
801
|
+
*/
|
|
802
|
+
function normalizeToAsyncIterable(input) {
|
|
803
|
+
if (isReadableStream(input)) {
|
|
804
|
+
return readableStreamToAsyncIterable(input);
|
|
805
|
+
}
|
|
806
|
+
return input;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Transform a stream of BatchByIdItem to BatchItemNDJSON format.
|
|
810
|
+
* Handles payload serialization and idempotency key generation.
|
|
628
811
|
*
|
|
629
|
-
* @
|
|
630
|
-
|
|
631
|
-
*
|
|
812
|
+
* @internal
|
|
813
|
+
*/
|
|
814
|
+
async function* transformBatchItemsStream(items, options) {
|
|
815
|
+
let index = 0;
|
|
816
|
+
for await (const item of items) {
|
|
817
|
+
const taskMetadata = resourceCatalog.getTask(item.id);
|
|
818
|
+
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
819
|
+
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
820
|
+
: item.payload;
|
|
821
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
822
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
823
|
+
yield {
|
|
824
|
+
index: index++,
|
|
825
|
+
task: item.id,
|
|
826
|
+
payload: payloadPacket.data,
|
|
827
|
+
options: {
|
|
828
|
+
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
829
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
830
|
+
test: taskContext.ctx?.run.isTest,
|
|
831
|
+
payloadType: payloadPacket.dataType,
|
|
832
|
+
delay: item.options?.delay,
|
|
833
|
+
ttl: item.options?.ttl,
|
|
834
|
+
tags: item.options?.tags,
|
|
835
|
+
maxAttempts: item.options?.maxAttempts,
|
|
836
|
+
metadata: item.options?.metadata,
|
|
837
|
+
maxDuration: item.options?.maxDuration,
|
|
838
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
839
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
840
|
+
machine: item.options?.machine,
|
|
841
|
+
priority: item.options?.priority,
|
|
842
|
+
region: item.options?.region,
|
|
843
|
+
lockToVersion: item.options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
844
|
+
debounce: item.options?.debounce,
|
|
845
|
+
},
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Transform a stream of BatchByIdAndWaitItem to BatchItemNDJSON format for triggerAndWait.
|
|
851
|
+
* Uses the current worker version for lockToVersion.
|
|
632
852
|
*
|
|
633
|
-
*
|
|
634
|
-
|
|
635
|
-
*
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
853
|
+
* @internal
|
|
854
|
+
*/
|
|
855
|
+
async function* transformBatchItemsStreamForWait(items, options) {
|
|
856
|
+
let index = 0;
|
|
857
|
+
for await (const item of items) {
|
|
858
|
+
const taskMetadata = resourceCatalog.getTask(item.id);
|
|
859
|
+
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
860
|
+
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
861
|
+
: item.payload;
|
|
862
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
863
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
864
|
+
yield {
|
|
865
|
+
index: index++,
|
|
866
|
+
task: item.id,
|
|
867
|
+
payload: payloadPacket.data,
|
|
868
|
+
options: {
|
|
869
|
+
lockToVersion: taskContext.worker?.version,
|
|
870
|
+
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
871
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
872
|
+
test: taskContext.ctx?.run.isTest,
|
|
873
|
+
payloadType: payloadPacket.dataType,
|
|
874
|
+
delay: item.options?.delay,
|
|
875
|
+
ttl: item.options?.ttl,
|
|
876
|
+
tags: item.options?.tags,
|
|
877
|
+
maxAttempts: item.options?.maxAttempts,
|
|
878
|
+
metadata: item.options?.metadata,
|
|
879
|
+
maxDuration: item.options?.maxDuration,
|
|
880
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
881
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
882
|
+
machine: item.options?.machine,
|
|
883
|
+
priority: item.options?.priority,
|
|
884
|
+
region: item.options?.region,
|
|
885
|
+
debounce: item.options?.debounce,
|
|
886
|
+
},
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Transform a stream of BatchByTaskItem to BatchItemNDJSON format.
|
|
651
892
|
*
|
|
652
|
-
*
|
|
653
|
-
|
|
654
|
-
*
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
893
|
+
* @internal
|
|
894
|
+
*/
|
|
895
|
+
async function* transformBatchByTaskItemsStream(items, options) {
|
|
896
|
+
let index = 0;
|
|
897
|
+
for await (const item of items) {
|
|
898
|
+
const taskMetadata = resourceCatalog.getTask(item.task.id);
|
|
899
|
+
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
900
|
+
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
901
|
+
: item.payload;
|
|
902
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
903
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
904
|
+
yield {
|
|
905
|
+
index: index++,
|
|
906
|
+
task: item.task.id,
|
|
907
|
+
payload: payloadPacket.data,
|
|
908
|
+
options: {
|
|
909
|
+
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
910
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
911
|
+
test: taskContext.ctx?.run.isTest,
|
|
912
|
+
payloadType: payloadPacket.dataType,
|
|
913
|
+
delay: item.options?.delay,
|
|
914
|
+
ttl: item.options?.ttl,
|
|
915
|
+
tags: item.options?.tags,
|
|
916
|
+
maxAttempts: item.options?.maxAttempts,
|
|
917
|
+
metadata: item.options?.metadata,
|
|
918
|
+
maxDuration: item.options?.maxDuration,
|
|
919
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
920
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
921
|
+
machine: item.options?.machine,
|
|
922
|
+
priority: item.options?.priority,
|
|
923
|
+
region: item.options?.region,
|
|
924
|
+
lockToVersion: item.options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
925
|
+
debounce: item.options?.debounce,
|
|
926
|
+
},
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Transform a stream of BatchByTaskAndWaitItem to BatchItemNDJSON format for triggerAndWait.
|
|
670
932
|
*
|
|
671
|
-
* @
|
|
672
|
-
|
|
673
|
-
*
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
933
|
+
* @internal
|
|
934
|
+
*/
|
|
935
|
+
async function* transformBatchByTaskItemsStreamForWait(items, options) {
|
|
936
|
+
let index = 0;
|
|
937
|
+
for await (const item of items) {
|
|
938
|
+
const taskMetadata = resourceCatalog.getTask(item.task.id);
|
|
939
|
+
const parsedPayload = taskMetadata?.fns.parsePayload
|
|
940
|
+
? await taskMetadata?.fns.parsePayload(item.payload)
|
|
941
|
+
: item.payload;
|
|
942
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
943
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
944
|
+
yield {
|
|
945
|
+
index: index++,
|
|
946
|
+
task: item.task.id,
|
|
947
|
+
payload: payloadPacket.data,
|
|
948
|
+
options: {
|
|
949
|
+
lockToVersion: taskContext.worker?.version,
|
|
950
|
+
queue: item.options?.queue ? { name: item.options.queue } : undefined,
|
|
951
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
952
|
+
test: taskContext.ctx?.run.isTest,
|
|
953
|
+
payloadType: payloadPacket.dataType,
|
|
954
|
+
delay: item.options?.delay,
|
|
955
|
+
ttl: item.options?.ttl,
|
|
956
|
+
tags: item.options?.tags,
|
|
957
|
+
maxAttempts: item.options?.maxAttempts,
|
|
958
|
+
metadata: item.options?.metadata,
|
|
959
|
+
maxDuration: item.options?.maxDuration,
|
|
960
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
961
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
962
|
+
machine: item.options?.machine,
|
|
963
|
+
priority: item.options?.priority,
|
|
964
|
+
region: item.options?.region,
|
|
965
|
+
debounce: item.options?.debounce,
|
|
966
|
+
},
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Transform a stream of BatchItem (single task type) to BatchItemNDJSON format.
|
|
684
972
|
*
|
|
685
|
-
*
|
|
686
|
-
* - Task IDs
|
|
687
|
-
* - Payload types
|
|
688
|
-
* - Return value types
|
|
689
|
-
* - Error handling
|
|
973
|
+
* @internal
|
|
690
974
|
*/
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
975
|
+
async function* transformSingleTaskBatchItemsStream(taskIdentifier, items, parsePayload, options, queue) {
|
|
976
|
+
let index = 0;
|
|
977
|
+
for await (const item of items) {
|
|
978
|
+
const parsedPayload = parsePayload ? await parsePayload(item.payload) : item.payload;
|
|
979
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
980
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
981
|
+
yield {
|
|
982
|
+
index: index++,
|
|
983
|
+
task: taskIdentifier,
|
|
984
|
+
payload: payloadPacket.data,
|
|
985
|
+
options: {
|
|
986
|
+
queue: item.options?.queue
|
|
987
|
+
? { name: item.options.queue }
|
|
988
|
+
: queue
|
|
989
|
+
? { name: queue }
|
|
990
|
+
: undefined,
|
|
991
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
992
|
+
test: taskContext.ctx?.run.isTest,
|
|
993
|
+
payloadType: payloadPacket.dataType,
|
|
994
|
+
delay: item.options?.delay,
|
|
995
|
+
ttl: item.options?.ttl,
|
|
996
|
+
tags: item.options?.tags,
|
|
997
|
+
maxAttempts: item.options?.maxAttempts,
|
|
998
|
+
metadata: item.options?.metadata,
|
|
999
|
+
maxDuration: item.options?.maxDuration,
|
|
1000
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
1001
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
1002
|
+
machine: item.options?.machine,
|
|
1003
|
+
priority: item.options?.priority,
|
|
1004
|
+
region: item.options?.region,
|
|
1005
|
+
lockToVersion: item.options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
1006
|
+
debounce: item.options?.debounce,
|
|
1007
|
+
},
|
|
1008
|
+
};
|
|
695
1009
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
span.setAttribute("batchId", response.id);
|
|
736
|
-
span.setAttribute("runCount", response.runCount);
|
|
737
|
-
const result = await runtime.waitForBatch({
|
|
738
|
-
id: response.id,
|
|
739
|
-
runCount: response.runCount,
|
|
740
|
-
ctx,
|
|
741
|
-
});
|
|
742
|
-
const runs = await handleBatchTaskRunExecutionResultV2(result.items);
|
|
743
|
-
return {
|
|
744
|
-
id: result.id,
|
|
745
|
-
runs,
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Transform a stream of BatchTriggerAndWaitItem (single task type) to BatchItemNDJSON format.
|
|
1013
|
+
*
|
|
1014
|
+
* @internal
|
|
1015
|
+
*/
|
|
1016
|
+
async function* transformSingleTaskBatchItemsStreamForWait(taskIdentifier, items, parsePayload, options, queue) {
|
|
1017
|
+
let index = 0;
|
|
1018
|
+
for await (const item of items) {
|
|
1019
|
+
const parsedPayload = parsePayload ? await parsePayload(item.payload) : item.payload;
|
|
1020
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
1021
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
1022
|
+
yield {
|
|
1023
|
+
index: index++,
|
|
1024
|
+
task: taskIdentifier,
|
|
1025
|
+
payload: payloadPacket.data,
|
|
1026
|
+
options: {
|
|
1027
|
+
lockToVersion: taskContext.worker?.version,
|
|
1028
|
+
queue: item.options?.queue
|
|
1029
|
+
? { name: item.options.queue }
|
|
1030
|
+
: queue
|
|
1031
|
+
? { name: queue }
|
|
1032
|
+
: undefined,
|
|
1033
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
1034
|
+
test: taskContext.ctx?.run.isTest,
|
|
1035
|
+
payloadType: payloadPacket.dataType,
|
|
1036
|
+
delay: item.options?.delay,
|
|
1037
|
+
ttl: item.options?.ttl,
|
|
1038
|
+
tags: item.options?.tags,
|
|
1039
|
+
maxAttempts: item.options?.maxAttempts,
|
|
1040
|
+
metadata: item.options?.metadata,
|
|
1041
|
+
maxDuration: item.options?.maxDuration,
|
|
1042
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
1043
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
1044
|
+
machine: item.options?.machine,
|
|
1045
|
+
priority: item.options?.priority,
|
|
1046
|
+
region: item.options?.region,
|
|
1047
|
+
debounce: item.options?.debounce,
|
|
1048
|
+
},
|
|
746
1049
|
};
|
|
747
|
-
}
|
|
748
|
-
kind: SpanKind.PRODUCER,
|
|
749
|
-
attributes: {
|
|
750
|
-
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
751
|
-
},
|
|
752
|
-
});
|
|
1050
|
+
}
|
|
753
1051
|
}
|
|
754
1052
|
async function trigger_internal(name, id, payload, parsePayload, options, requestOptions) {
|
|
755
1053
|
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
@@ -775,6 +1073,7 @@ async function trigger_internal(name, id, payload, parsePayload, options, reques
|
|
|
775
1073
|
priority: options?.priority,
|
|
776
1074
|
region: options?.region,
|
|
777
1075
|
lockToVersion: options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
1076
|
+
debounce: options?.debounce,
|
|
778
1077
|
},
|
|
779
1078
|
}, {
|
|
780
1079
|
spanParentAsLink: true,
|
|
@@ -796,12 +1095,15 @@ async function trigger_internal(name, id, payload, parsePayload, options, reques
|
|
|
796
1095
|
async function batchTrigger_internal(name, taskIdentifier, items, options, parsePayload, requestOptions, queue) {
|
|
797
1096
|
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
798
1097
|
const ctx = taskContext.ctx;
|
|
799
|
-
|
|
800
|
-
|
|
1098
|
+
// Check if items is an array or a stream
|
|
1099
|
+
if (Array.isArray(items)) {
|
|
1100
|
+
// Prepare items as BatchItemNDJSON
|
|
1101
|
+
const ndJsonItems = await Promise.all(items.map(async (item, index) => {
|
|
801
1102
|
const parsedPayload = parsePayload ? await parsePayload(item.payload) : item.payload;
|
|
802
1103
|
const payloadPacket = await stringifyIO(parsedPayload);
|
|
803
1104
|
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
804
1105
|
return {
|
|
1106
|
+
index,
|
|
805
1107
|
task: taskIdentifier,
|
|
806
1108
|
payload: payloadPacket.data,
|
|
807
1109
|
options: {
|
|
@@ -827,33 +1129,75 @@ async function batchTrigger_internal(name, taskIdentifier, items, options, parse
|
|
|
827
1129
|
lockToVersion: item.options?.version ?? getEnvVar("TRIGGER_VERSION"),
|
|
828
1130
|
},
|
|
829
1131
|
};
|
|
830
|
-
}))
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1132
|
+
}));
|
|
1133
|
+
// Execute 2-phase batch
|
|
1134
|
+
const response = await tracer.startActiveSpan(name, async (span) => {
|
|
1135
|
+
const result = await executeBatchTwoPhase(apiClient, ndJsonItems, {
|
|
1136
|
+
parentRunId: ctx?.run.id,
|
|
1137
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
1138
|
+
spanParentAsLink: true, // Fire-and-forget: child runs get separate trace IDs
|
|
1139
|
+
}, requestOptions);
|
|
1140
|
+
span.setAttribute("batchId", result.id);
|
|
1141
|
+
span.setAttribute("runCount", result.runCount);
|
|
1142
|
+
return result;
|
|
1143
|
+
}, {
|
|
1144
|
+
kind: SpanKind.PRODUCER,
|
|
1145
|
+
attributes: {
|
|
1146
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
1147
|
+
...accessoryAttributes({
|
|
1148
|
+
items: [
|
|
1149
|
+
{
|
|
1150
|
+
text: taskIdentifier,
|
|
1151
|
+
variant: "normal",
|
|
1152
|
+
},
|
|
1153
|
+
],
|
|
1154
|
+
style: "codepath",
|
|
1155
|
+
}),
|
|
1156
|
+
},
|
|
1157
|
+
});
|
|
1158
|
+
const handle = {
|
|
1159
|
+
batchId: response.id,
|
|
1160
|
+
runCount: response.runCount,
|
|
1161
|
+
publicAccessToken: response.publicAccessToken,
|
|
1162
|
+
};
|
|
1163
|
+
return handle;
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
// Stream path: convert to AsyncIterable and transform
|
|
1167
|
+
const asyncItems = normalizeToAsyncIterable(items);
|
|
1168
|
+
const transformedItems = transformSingleTaskBatchItemsStream(taskIdentifier, asyncItems, parsePayload, options, queue);
|
|
1169
|
+
// Execute streaming 2-phase batch
|
|
1170
|
+
const response = await tracer.startActiveSpan(name, async (span) => {
|
|
1171
|
+
const result = await executeBatchTwoPhaseStreaming(apiClient, transformedItems, {
|
|
1172
|
+
parentRunId: ctx?.run.id,
|
|
1173
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
1174
|
+
spanParentAsLink: true, // Fire-and-forget: child runs get separate trace IDs
|
|
1175
|
+
}, requestOptions);
|
|
1176
|
+
span.setAttribute("batchId", result.id);
|
|
1177
|
+
span.setAttribute("runCount", result.runCount);
|
|
1178
|
+
return result;
|
|
1179
|
+
}, {
|
|
1180
|
+
kind: SpanKind.PRODUCER,
|
|
1181
|
+
attributes: {
|
|
1182
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
1183
|
+
...accessoryAttributes({
|
|
1184
|
+
items: [
|
|
1185
|
+
{
|
|
1186
|
+
text: taskIdentifier,
|
|
1187
|
+
variant: "normal",
|
|
1188
|
+
},
|
|
1189
|
+
],
|
|
1190
|
+
style: "codepath",
|
|
1191
|
+
}),
|
|
1192
|
+
},
|
|
1193
|
+
});
|
|
1194
|
+
const handle = {
|
|
1195
|
+
batchId: response.id,
|
|
1196
|
+
runCount: response.runCount,
|
|
1197
|
+
publicAccessToken: response.publicAccessToken,
|
|
1198
|
+
};
|
|
1199
|
+
return handle;
|
|
1200
|
+
}
|
|
857
1201
|
}
|
|
858
1202
|
async function triggerAndWait_internal(name, id, payload, parsePayload, options, requestOptions) {
|
|
859
1203
|
const ctx = taskContext.ctx;
|
|
@@ -885,6 +1229,7 @@ async function triggerAndWait_internal(name, id, payload, parsePayload, options,
|
|
|
885
1229
|
machine: options?.machine,
|
|
886
1230
|
priority: options?.priority,
|
|
887
1231
|
region: options?.region,
|
|
1232
|
+
debounce: options?.debounce,
|
|
888
1233
|
},
|
|
889
1234
|
}, {}, requestOptions);
|
|
890
1235
|
span.setAttribute("runId", response.id);
|
|
@@ -915,72 +1260,117 @@ async function batchTriggerAndWait_internal(name, id, items, parsePayload, optio
|
|
|
915
1260
|
throw new Error("batchTriggerAndWait can only be used from inside a task.run()");
|
|
916
1261
|
}
|
|
917
1262
|
const apiClient = apiClientManager.clientOrThrow(requestOptions?.clientConfig);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
1263
|
+
// Check if items is an array or a stream
|
|
1264
|
+
if (Array.isArray(items)) {
|
|
1265
|
+
// Prepare items as BatchItemNDJSON
|
|
1266
|
+
const ndJsonItems = await Promise.all(items.map(async (item, index) => {
|
|
1267
|
+
const parsedPayload = parsePayload ? await parsePayload(item.payload) : item.payload;
|
|
1268
|
+
const payloadPacket = await stringifyIO(parsedPayload);
|
|
1269
|
+
const batchItemIdempotencyKey = await makeIdempotencyKey(flattenIdempotencyKey([options?.idempotencyKey, `${index}`]));
|
|
1270
|
+
return {
|
|
1271
|
+
index,
|
|
1272
|
+
task: id,
|
|
1273
|
+
payload: payloadPacket.data,
|
|
1274
|
+
options: {
|
|
1275
|
+
lockToVersion: taskContext.worker?.version,
|
|
1276
|
+
queue: item.options?.queue
|
|
1277
|
+
? { name: item.options.queue }
|
|
1278
|
+
: queue
|
|
1279
|
+
? { name: queue }
|
|
1280
|
+
: undefined,
|
|
1281
|
+
concurrencyKey: item.options?.concurrencyKey,
|
|
1282
|
+
test: taskContext.ctx?.run.isTest,
|
|
1283
|
+
payloadType: payloadPacket.dataType,
|
|
1284
|
+
delay: item.options?.delay,
|
|
1285
|
+
ttl: item.options?.ttl,
|
|
1286
|
+
tags: item.options?.tags,
|
|
1287
|
+
maxAttempts: item.options?.maxAttempts,
|
|
1288
|
+
metadata: item.options?.metadata,
|
|
1289
|
+
maxDuration: item.options?.maxDuration,
|
|
1290
|
+
idempotencyKey: (await makeIdempotencyKey(item.options?.idempotencyKey)) ?? batchItemIdempotencyKey,
|
|
1291
|
+
idempotencyKeyTTL: item.options?.idempotencyKeyTTL ?? options?.idempotencyKeyTTL,
|
|
1292
|
+
machine: item.options?.machine,
|
|
1293
|
+
priority: item.options?.priority,
|
|
1294
|
+
region: item.options?.region,
|
|
1295
|
+
},
|
|
1296
|
+
};
|
|
1297
|
+
}));
|
|
1298
|
+
return await tracer.startActiveSpan(name, async (span) => {
|
|
1299
|
+
// Execute 2-phase batch
|
|
1300
|
+
const response = await executeBatchTwoPhase(apiClient, ndJsonItems, {
|
|
1301
|
+
parentRunId: ctx.run.id,
|
|
1302
|
+
resumeParentOnCompletion: true,
|
|
1303
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
1304
|
+
spanParentAsLink: false, // Waiting: child runs share parent's trace ID
|
|
1305
|
+
}, requestOptions);
|
|
1306
|
+
span.setAttribute("batchId", response.id);
|
|
1307
|
+
span.setAttribute("runCount", response.runCount);
|
|
1308
|
+
const result = await runtime.waitForBatch({
|
|
1309
|
+
id: response.id,
|
|
1310
|
+
runCount: response.runCount,
|
|
1311
|
+
ctx,
|
|
1312
|
+
});
|
|
1313
|
+
const runs = await handleBatchTaskRunExecutionResult(result.items, id);
|
|
1314
|
+
return {
|
|
1315
|
+
id: result.id,
|
|
1316
|
+
runs,
|
|
1317
|
+
};
|
|
954
1318
|
}, {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1319
|
+
kind: SpanKind.PRODUCER,
|
|
1320
|
+
attributes: {
|
|
1321
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
1322
|
+
...accessoryAttributes({
|
|
1323
|
+
items: [
|
|
1324
|
+
{
|
|
1325
|
+
text: id,
|
|
1326
|
+
variant: "normal",
|
|
1327
|
+
},
|
|
1328
|
+
],
|
|
1329
|
+
style: "codepath",
|
|
1330
|
+
}),
|
|
1331
|
+
},
|
|
963
1332
|
});
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
// Stream path: convert to AsyncIterable and transform
|
|
1336
|
+
const asyncItems = normalizeToAsyncIterable(items);
|
|
1337
|
+
const transformedItems = transformSingleTaskBatchItemsStreamForWait(id, asyncItems, parsePayload, options, queue);
|
|
1338
|
+
return await tracer.startActiveSpan(name, async (span) => {
|
|
1339
|
+
// Execute streaming 2-phase batch
|
|
1340
|
+
const response = await executeBatchTwoPhaseStreaming(apiClient, transformedItems, {
|
|
1341
|
+
parentRunId: ctx.run.id,
|
|
1342
|
+
resumeParentOnCompletion: true,
|
|
1343
|
+
idempotencyKey: await makeIdempotencyKey(options?.idempotencyKey),
|
|
1344
|
+
spanParentAsLink: false, // Waiting: child runs share parent's trace ID
|
|
1345
|
+
}, requestOptions);
|
|
1346
|
+
span.setAttribute("batchId", response.id);
|
|
1347
|
+
span.setAttribute("runCount", response.runCount);
|
|
1348
|
+
const result = await runtime.waitForBatch({
|
|
1349
|
+
id: response.id,
|
|
1350
|
+
runCount: response.runCount,
|
|
1351
|
+
ctx,
|
|
1352
|
+
});
|
|
1353
|
+
const runs = await handleBatchTaskRunExecutionResult(result.items, id);
|
|
1354
|
+
return {
|
|
1355
|
+
id: result.id,
|
|
1356
|
+
runs,
|
|
1357
|
+
};
|
|
1358
|
+
}, {
|
|
1359
|
+
kind: SpanKind.PRODUCER,
|
|
1360
|
+
attributes: {
|
|
1361
|
+
[SemanticInternalAttributes.STYLE_ICON]: "trigger",
|
|
1362
|
+
...accessoryAttributes({
|
|
1363
|
+
items: [
|
|
1364
|
+
{
|
|
1365
|
+
text: id,
|
|
1366
|
+
variant: "normal",
|
|
1367
|
+
},
|
|
1368
|
+
],
|
|
1369
|
+
style: "codepath",
|
|
1370
|
+
}),
|
|
1371
|
+
},
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
984
1374
|
}
|
|
985
1375
|
async function handleBatchTaskRunExecutionResult(items, taskIdentifier) {
|
|
986
1376
|
const someObjectStoreOutputs = items.some((item) => item.ok && item.outputType === "application/store");
|