Skip to content

Commit 151a50a

Browse files
committed
Changed how batching works, includes breaking changes in CLI
1 parent 641edd2 commit 151a50a

File tree

20 files changed

+610
-464
lines changed

20 files changed

+610
-464
lines changed

apps/webapp/app/routes/api.v1.tasks.batch.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ import { AuthenticatedEnvironment, getOneTimeUseToken } from "~/services/apiAuth
99
import { logger } from "~/services/logger.server";
1010
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
1111
import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server";
12-
import { determineEngineVersion } from "~/v3/engineVersion.server";
1312
import { ServiceValidationError } from "~/v3/services/baseService.server";
1413
import {
1514
BatchProcessingStrategy,
1615
BatchTriggerV2Service,
1716
} from "~/v3/services/batchTriggerV2.server";
18-
import { BatchTriggerV3Service } from "~/v3/services/batchTriggerV3.server";
1917
import { OutOfEntitlementError } from "~/v3/services/triggerTask.server";
2018
import { HeadersSchema } from "./api.v1.tasks.$taskId.trigger";
2119

@@ -88,15 +86,7 @@ const { action, loader } = createActionApiRoute(
8886
resolveIdempotencyKeyTTL(idempotencyKeyTTL) ??
8987
new Date(Date.now() + 24 * 60 * 60 * 1000 * 30);
9088

91-
const version = await determineEngineVersion({
92-
environment: authentication.environment,
93-
version: engineVersion ?? undefined,
94-
});
95-
96-
const service =
97-
version === "V1"
98-
? new BatchTriggerV2Service(batchProcessingStrategy ?? undefined)
99-
: new BatchTriggerV3Service(batchProcessingStrategy ?? undefined);
89+
const service = new BatchTriggerV2Service(batchProcessingStrategy ?? undefined);
10090

10191
try {
10292
const batch = await service.call(authentication.environment, body, {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import {
3+
BatchTriggerTaskV2RequestBody,
4+
BatchTriggerTaskV2Response,
5+
BatchTriggerTaskV3RequestBody,
6+
BatchTriggerTaskV3Response,
7+
generateJWT,
8+
} from "@trigger.dev/core/v3";
9+
import { env } from "~/env.server";
10+
import { AuthenticatedEnvironment, getOneTimeUseToken } from "~/services/apiAuth.server";
11+
import { logger } from "~/services/logger.server";
12+
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
13+
import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server";
14+
import { ServiceValidationError } from "~/v3/services/baseService.server";
15+
import {
16+
BatchProcessingStrategy,
17+
BatchTriggerV3Service,
18+
} from "~/v3/services/batchTriggerV3.server";
19+
import { OutOfEntitlementError } from "~/v3/services/triggerTask.server";
20+
import { HeadersSchema } from "./api.v1.tasks.$taskId.trigger";
21+
22+
const { action, loader } = createActionApiRoute(
23+
{
24+
headers: HeadersSchema.extend({
25+
"batch-processing-strategy": BatchProcessingStrategy.nullish(),
26+
}),
27+
body: BatchTriggerTaskV3RequestBody,
28+
allowJWT: true,
29+
maxContentLength: env.BATCH_TASK_PAYLOAD_MAXIMUM_SIZE,
30+
authorization: {
31+
action: "batchTrigger",
32+
resource: (_, __, ___, body) => ({
33+
tasks: Array.from(new Set(body.items.map((i) => i.task))),
34+
}),
35+
superScopes: ["write:tasks", "admin"],
36+
},
37+
corsStrategy: "all",
38+
},
39+
async ({ body, headers, params, authentication }) => {
40+
if (!body.items.length) {
41+
return json({ error: "Batch cannot be triggered with no items" }, { status: 400 });
42+
}
43+
44+
// Check the there are fewer than MAX_BATCH_V2_TRIGGER_ITEMS items
45+
if (body.items.length > env.MAX_BATCH_V2_TRIGGER_ITEMS) {
46+
return json(
47+
{
48+
error: `Batch size of ${body.items.length} is too large. Maximum allowed batch size is ${env.MAX_BATCH_V2_TRIGGER_ITEMS}.`,
49+
},
50+
{ status: 400 }
51+
);
52+
}
53+
54+
const {
55+
"idempotency-key": idempotencyKey,
56+
"idempotency-key-ttl": idempotencyKeyTTL,
57+
"trigger-version": triggerVersion,
58+
"x-trigger-span-parent-as-link": spanParentAsLink,
59+
"x-trigger-worker": isFromWorker,
60+
"x-trigger-client": triggerClient,
61+
"x-trigger-engine-version": engineVersion,
62+
"batch-processing-strategy": batchProcessingStrategy,
63+
traceparent,
64+
tracestate,
65+
} = headers;
66+
67+
const oneTimeUseToken = await getOneTimeUseToken(authentication);
68+
69+
logger.debug("Batch trigger request", {
70+
idempotencyKey,
71+
idempotencyKeyTTL,
72+
triggerVersion,
73+
spanParentAsLink,
74+
isFromWorker,
75+
triggerClient,
76+
traceparent,
77+
tracestate,
78+
batchProcessingStrategy,
79+
});
80+
81+
const traceContext =
82+
traceparent && isFromWorker // If the request is from a worker, we should pass the trace context
83+
? { traceparent, tracestate }
84+
: undefined;
85+
86+
// By default, the idempotency key expires in 30 days
87+
const idempotencyKeyExpiresAt =
88+
resolveIdempotencyKeyTTL(idempotencyKeyTTL) ??
89+
new Date(Date.now() + 24 * 60 * 60 * 1000 * 30);
90+
91+
const service = new BatchTriggerV3Service(batchProcessingStrategy ?? undefined);
92+
93+
try {
94+
const batch = await service.call(authentication.environment, body, {
95+
idempotencyKey: idempotencyKey ?? undefined,
96+
idempotencyKeyExpiresAt,
97+
triggerVersion: triggerVersion ?? undefined,
98+
traceContext,
99+
spanParentAsLink: spanParentAsLink === 1,
100+
oneTimeUseToken,
101+
});
102+
103+
const $responseHeaders = await responseHeaders(
104+
batch,
105+
authentication.environment,
106+
triggerClient
107+
);
108+
109+
return json(batch, { status: 202, headers: $responseHeaders });
110+
} catch (error) {
111+
logger.error("Batch trigger error", {
112+
error: {
113+
message: (error as Error).message,
114+
stack: (error as Error).stack,
115+
},
116+
});
117+
118+
if (error instanceof ServiceValidationError) {
119+
return json({ error: error.message }, { status: 422 });
120+
} else if (error instanceof OutOfEntitlementError) {
121+
return json({ error: error.message }, { status: 422 });
122+
} else if (error instanceof Error) {
123+
return json(
124+
{ error: error.message },
125+
{ status: 500, headers: { "x-should-retry": "false" } }
126+
);
127+
}
128+
129+
return json({ error: "Something went wrong" }, { status: 500 });
130+
}
131+
}
132+
);
133+
134+
async function responseHeaders(
135+
batch: BatchTriggerTaskV3Response,
136+
environment: AuthenticatedEnvironment,
137+
triggerClient?: string | null
138+
): Promise<Record<string, string>> {
139+
const claimsHeader = JSON.stringify({
140+
sub: environment.id,
141+
pub: true,
142+
});
143+
144+
if (triggerClient === "browser") {
145+
const claims = {
146+
sub: environment.id,
147+
pub: true,
148+
scopes: [`read:batch:${batch.id}`],
149+
};
150+
151+
const jwt = await generateJWT({
152+
secretKey: environment.apiKey,
153+
payload: claims,
154+
expirationTime: "1h",
155+
});
156+
157+
return {
158+
"x-trigger-jwt-claims": claimsHeader,
159+
"x-trigger-jwt": jwt,
160+
};
161+
}
162+
163+
return {
164+
"x-trigger-jwt-claims": claimsHeader,
165+
};
166+
}
167+
168+
export { action, loader };

0 commit comments

Comments
 (0)