inngest 4.1.2 → 4.2.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.
- package/CHANGELOG.md +6 -0
- package/api/api.cjs +30 -1
- package/api/api.cjs.map +1 -1
- package/api/api.d.cts +19 -0
- package/api/api.d.cts.map +1 -1
- package/api/api.d.ts +19 -0
- package/api/api.d.ts.map +1 -1
- package/api/api.js +30 -1
- package/api/api.js.map +1 -1
- package/components/InngestCommHandler.cjs +9 -1
- package/components/InngestCommHandler.cjs.map +1 -1
- package/components/InngestCommHandler.d.cts.map +1 -1
- package/components/InngestCommHandler.d.ts.map +1 -1
- package/components/InngestCommHandler.js +9 -1
- package/components/InngestCommHandler.js.map +1 -1
- package/components/StreamTools.cjs +241 -0
- package/components/StreamTools.cjs.map +1 -0
- package/components/StreamTools.d.cts +161 -0
- package/components/StreamTools.d.cts.map +1 -0
- package/components/StreamTools.d.ts +161 -0
- package/components/StreamTools.d.ts.map +1 -0
- package/components/StreamTools.js +240 -0
- package/components/StreamTools.js.map +1 -0
- package/components/createWebApiCommHandler.cjs +46 -0
- package/components/createWebApiCommHandler.cjs.map +1 -0
- package/components/createWebApiCommHandler.js +46 -0
- package/components/createWebApiCommHandler.js.map +1 -0
- package/components/execution/InngestExecution.cjs.map +1 -1
- package/components/execution/InngestExecution.d.cts +6 -0
- package/components/execution/InngestExecution.d.cts.map +1 -1
- package/components/execution/InngestExecution.d.ts +6 -0
- package/components/execution/InngestExecution.d.ts.map +1 -1
- package/components/execution/InngestExecution.js.map +1 -1
- package/components/execution/als.cjs.map +1 -1
- package/components/execution/als.d.cts +9 -1
- package/components/execution/als.d.cts.map +1 -1
- package/components/execution/als.d.ts +9 -1
- package/components/execution/als.d.ts.map +1 -1
- package/components/execution/als.js.map +1 -1
- package/components/execution/engine.cjs +334 -26
- package/components/execution/engine.cjs.map +1 -1
- package/components/execution/engine.d.cts +1 -0
- package/components/execution/engine.d.cts.map +1 -1
- package/components/execution/engine.d.ts +1 -0
- package/components/execution/engine.d.ts.map +1 -1
- package/components/execution/engine.js +334 -26
- package/components/execution/engine.js.map +1 -1
- package/components/execution/streaming.cjs +208 -0
- package/components/execution/streaming.cjs.map +1 -0
- package/components/execution/streaming.d.cts +12 -0
- package/components/execution/streaming.d.cts.map +1 -0
- package/components/execution/streaming.d.ts +12 -0
- package/components/execution/streaming.d.ts.map +1 -0
- package/components/execution/streaming.js +198 -0
- package/components/execution/streaming.js.map +1 -0
- package/edge.cjs +19 -32
- package/edge.cjs.map +1 -1
- package/edge.d.cts +1 -1
- package/edge.d.cts.map +1 -1
- package/edge.d.ts +1 -1
- package/edge.d.ts.map +1 -1
- package/edge.js +19 -32
- package/edge.js.map +1 -1
- package/experimental/durable-endpoints/client.cjs +114 -0
- package/experimental/durable-endpoints/client.cjs.map +1 -0
- package/experimental/durable-endpoints/client.d.cts +49 -0
- package/experimental/durable-endpoints/client.d.cts.map +1 -0
- package/experimental/durable-endpoints/client.d.ts +49 -0
- package/experimental/durable-endpoints/client.d.ts.map +1 -0
- package/experimental/durable-endpoints/client.js +114 -0
- package/experimental/durable-endpoints/client.js.map +1 -0
- package/experimental/durable-endpoints/index.cjs +3 -0
- package/experimental/durable-endpoints/index.d.cts +2 -0
- package/experimental/durable-endpoints/index.d.ts +2 -0
- package/experimental/durable-endpoints/index.js +3 -0
- package/helpers/promises.cjs.map +1 -1
- package/helpers/promises.js.map +1 -1
- package/node.cjs +97 -0
- package/node.cjs.map +1 -1
- package/node.d.cts +34 -2
- package/node.d.cts.map +1 -1
- package/node.d.ts +34 -2
- package/node.d.ts.map +1 -1
- package/node.js +95 -2
- package/node.js.map +1 -1
- package/package.json +17 -1
- package/react.d.cts.map +1 -1
- package/version.cjs +1 -1
- package/version.cjs.map +1 -1
- package/version.d.cts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
|
@@ -5,6 +5,7 @@ import { NonRetriableError } from "../NonRetriableError.js";
|
|
|
5
5
|
import { ErrCode, deserializeError, serializeError } from "../../helpers/errors.js";
|
|
6
6
|
import { StepMode, StepOpCode, jsonErrorSchema } from "../../types.js";
|
|
7
7
|
import { InngestExecution } from "./InngestExecution.js";
|
|
8
|
+
import { isRecord } from "../../helpers/types.js";
|
|
8
9
|
import { undefinedToNull } from "../../helpers/functions.js";
|
|
9
10
|
import { createDeferredPromise, createDeferredPromiseWithStack, createTimeoutPromise, goIntervalTiming, resolveAfterPending, resolveNextTick, retryWithBackoff, runAsPromise } from "../../helpers/promises.js";
|
|
10
11
|
import { getAsyncCtx, getAsyncLocalStorage } from "./als.js";
|
|
@@ -16,6 +17,8 @@ import { internalLoggerSymbol } from "../Inngest.js";
|
|
|
16
17
|
import { createGroupTools } from "../InngestGroupTools.js";
|
|
17
18
|
import { RetryAfterError } from "../RetryAfterError.js";
|
|
18
19
|
import { StepError } from "../StepError.js";
|
|
20
|
+
import { buildSseMetadataEvent, prependToStream } from "./streaming.js";
|
|
21
|
+
import { Stream } from "../StreamTools.js";
|
|
19
22
|
import { validateEvents } from "../triggers/utils.js";
|
|
20
23
|
import { clientProcessorMap } from "./otel/access.js";
|
|
21
24
|
import { z } from "zod/v3";
|
|
@@ -41,10 +44,43 @@ const CHECKPOINT_RETRY_OPTIONS = {
|
|
|
41
44
|
maxAttempts: 5,
|
|
42
45
|
baseDelay: 100
|
|
43
46
|
};
|
|
47
|
+
function errorMessage(error) {
|
|
48
|
+
if (error instanceof Error) return error.message;
|
|
49
|
+
if (isRecord(error) && typeof error.message === "string") return error.message;
|
|
50
|
+
return String(error);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Placeholder step ID used when completing a checkpointed run.
|
|
54
|
+
*/
|
|
55
|
+
const RUN_COMPLETE_STEP_ID = "complete";
|
|
44
56
|
const STEP_NOT_FOUND_MAX_FOUND_STEPS = 25;
|
|
45
57
|
const createExecutionEngine = (options) => {
|
|
46
58
|
return new InngestExecutionEngine(options);
|
|
47
59
|
};
|
|
60
|
+
function extractSseResponse(response, body) {
|
|
61
|
+
const headers = {};
|
|
62
|
+
response.headers.forEach((value, key) => {
|
|
63
|
+
headers[key] = value;
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
body,
|
|
67
|
+
statusCode: response.status,
|
|
68
|
+
headers
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function defaultSseResponse(data) {
|
|
72
|
+
return {
|
|
73
|
+
body: JSON.stringify(data),
|
|
74
|
+
statusCode: 200,
|
|
75
|
+
headers: { "content-type": "application/json" }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function jsonResponse(data, status = 200) {
|
|
79
|
+
return new Response(JSON.stringify(data), {
|
|
80
|
+
status,
|
|
81
|
+
headers: { "Content-Type": "application/json" }
|
|
82
|
+
});
|
|
83
|
+
}
|
|
48
84
|
var InngestExecutionEngine = class extends InngestExecution {
|
|
49
85
|
version = ExecutionVersion.V2;
|
|
50
86
|
state;
|
|
@@ -55,6 +91,29 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
55
91
|
userFnToRun;
|
|
56
92
|
middlewareManager;
|
|
57
93
|
/**
|
|
94
|
+
* Close the stream via {@link streamCloseSucceeded}, {@link streamCloseFailed},
|
|
95
|
+
* or {@link streamEnd} — never call `streamTools.close*`/`end` directly, as
|
|
96
|
+
* the wrappers ensure the redirect event is flushed first.
|
|
97
|
+
*/
|
|
98
|
+
streamTools;
|
|
99
|
+
/**
|
|
100
|
+
* Resolved when `stream.push()`/`pipe()` is first called in sync mode,
|
|
101
|
+
* allowing `_start()` to return the SSE Response to the HTTP layer while
|
|
102
|
+
* the core loop continues executing steps in the background.
|
|
103
|
+
*/
|
|
104
|
+
earlyStreamResponse;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the `inngest.redirect_info` SSE event has already been sent.
|
|
107
|
+
* Prevents duplicate redirect events.
|
|
108
|
+
*/
|
|
109
|
+
redirectSent = false;
|
|
110
|
+
/**
|
|
111
|
+
* Promise that resolves once the redirect event has been written (or the
|
|
112
|
+
* attempt completes). Stored so that `checkpointAndSwitchToAsync` can
|
|
113
|
+
* await it before closing the writer.
|
|
114
|
+
*/
|
|
115
|
+
redirectPromise = Promise.resolve();
|
|
116
|
+
/**
|
|
58
117
|
* If we're supposed to run a particular step via `requestedRunStep`, this
|
|
59
118
|
* will be a `Promise` that resolves after no steps have been found for
|
|
60
119
|
* `timeoutDuration` milliseconds.
|
|
@@ -89,6 +148,10 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
89
148
|
if (!this.options.createResponse) throw new Error("createResponse is required for sync step mode");
|
|
90
149
|
}
|
|
91
150
|
this.userFnToRun = this.getUserFnToRun();
|
|
151
|
+
this.streamTools = new Stream({
|
|
152
|
+
onActivated: () => this.handleStreamActivated(),
|
|
153
|
+
onWriteError: (err) => this.devDebug("stream write error (client may have disconnected):", err)
|
|
154
|
+
});
|
|
92
155
|
this.state = this.createExecutionState();
|
|
93
156
|
this.fnArg = this.createFnArg();
|
|
94
157
|
const mwInstances = this.options.middlewareInstances ?? (this.options.client.middleware || []).map((Cls) => {
|
|
@@ -113,7 +176,8 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
113
176
|
app: this.options.client,
|
|
114
177
|
execution: {
|
|
115
178
|
ctx: this.fnArg,
|
|
116
|
-
instance: this
|
|
179
|
+
instance: this,
|
|
180
|
+
stream: this.streamTools
|
|
117
181
|
}
|
|
118
182
|
}, async () => {
|
|
119
183
|
return tracer.startActiveSpan("inngest.execution", (span) => {
|
|
@@ -152,6 +216,21 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
152
216
|
* Starts execution of the user's function and the core loop.
|
|
153
217
|
*/
|
|
154
218
|
async _start() {
|
|
219
|
+
if (this.options.stepMode === StepMode.Sync && this.options.acceptsSse) this.earlyStreamResponse = createDeferredPromise();
|
|
220
|
+
const coreLoop = this.runCoreLoop();
|
|
221
|
+
if (this.earlyStreamResponse) {
|
|
222
|
+
coreLoop.catch((err) => {
|
|
223
|
+
this.options.client[internalLoggerSymbol].error({ err }, "Core loop rejected after early stream response was sent");
|
|
224
|
+
});
|
|
225
|
+
return Promise.race([this.earlyStreamResponse.promise, coreLoop]);
|
|
226
|
+
}
|
|
227
|
+
return coreLoop;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The core checkpoint loop: processes checkpoints until a handler returns
|
|
231
|
+
* a result.
|
|
232
|
+
*/
|
|
233
|
+
async runCoreLoop() {
|
|
155
234
|
try {
|
|
156
235
|
const allCheckpointHandler = this.getCheckpointHandler("");
|
|
157
236
|
await this.startExecution();
|
|
@@ -162,6 +241,12 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
162
241
|
if (result) return result;
|
|
163
242
|
}
|
|
164
243
|
} catch (error) {
|
|
244
|
+
if (this.earlyStreamResponse) {
|
|
245
|
+
await this.streamCloseFailed("Internal execution error");
|
|
246
|
+
const result = this.transformOutput({ error });
|
|
247
|
+
this.earlyStreamResponse.resolve(result);
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
165
250
|
return this.transformOutput({ error });
|
|
166
251
|
} finally {
|
|
167
252
|
this.state.loop.return();
|
|
@@ -184,8 +269,10 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
184
269
|
this.state.checkpointedRun = {
|
|
185
270
|
appId: res.data.app_id,
|
|
186
271
|
fnId: res.data.fn_id,
|
|
187
|
-
token: res.data.token
|
|
272
|
+
token: res.data.token,
|
|
273
|
+
realtimeToken: res.data.realtime_token
|
|
188
274
|
};
|
|
275
|
+
this.sendRedirectIfReady();
|
|
189
276
|
} else await retryWithBackoff(() => this.options.client["inngestApi"].checkpointSteps({
|
|
190
277
|
appId: this.state.checkpointedRun.appId,
|
|
191
278
|
fnId: this.state.checkpointedRun.fnId,
|
|
@@ -193,34 +280,191 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
193
280
|
steps
|
|
194
281
|
}), CHECKPOINT_RETRY_OPTIONS);
|
|
195
282
|
else if (this.options.stepMode === StepMode.AsyncCheckpointing) {
|
|
196
|
-
|
|
197
|
-
if (!
|
|
283
|
+
const { internalFnId, queueItemId } = this.options;
|
|
284
|
+
if (!queueItemId) throw new Error("Missing queueItemId for async checkpointing. This is a bug in the Inngest SDK.");
|
|
285
|
+
if (!internalFnId) throw new Error("Missing internalFnId for async checkpointing. This is a bug in the Inngest SDK.");
|
|
198
286
|
await retryWithBackoff(() => this.options.client["inngestApi"].checkpointStepsAsync({
|
|
199
287
|
runId: this.fnArg.runId,
|
|
200
|
-
fnId:
|
|
201
|
-
queueItemId
|
|
288
|
+
fnId: internalFnId,
|
|
289
|
+
queueItemId,
|
|
202
290
|
steps
|
|
203
291
|
}), CHECKPOINT_RETRY_OPTIONS);
|
|
204
292
|
} else throw new Error("Checkpointing is only supported in Sync and AsyncCheckpointing step modes. This is a bug in the Inngest SDK.");
|
|
205
293
|
}
|
|
206
|
-
async checkpointAndSwitchToAsync(steps) {
|
|
294
|
+
async checkpointAndSwitchToAsync(steps, stepError) {
|
|
207
295
|
await this.checkpoint(steps);
|
|
208
296
|
if (!this.state.checkpointedRun?.token) throw new Error("Failed to checkpoint and switch to async mode");
|
|
297
|
+
const token = this.state.checkpointedRun.token;
|
|
298
|
+
if (this.streamTools.activated) if (stepError && !this.retriability(stepError)) await this.streamCloseFailed(errorMessage(stepError));
|
|
299
|
+
else await this.streamEnd();
|
|
300
|
+
else if (this.options.acceptsSse) {
|
|
301
|
+
await this.streamEnd();
|
|
302
|
+
return {
|
|
303
|
+
type: "function-resolved",
|
|
304
|
+
ctx: this.fnArg,
|
|
305
|
+
ops: this.ops,
|
|
306
|
+
data: this.buildSyncSseResponse()
|
|
307
|
+
};
|
|
308
|
+
}
|
|
209
309
|
return {
|
|
210
310
|
type: "change-mode",
|
|
211
311
|
ctx: this.fnArg,
|
|
212
312
|
ops: this.ops,
|
|
213
313
|
to: StepMode.Async,
|
|
214
|
-
token
|
|
314
|
+
token
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Prepend the `inngest.metadata` SSE event to the stream's readable side.
|
|
319
|
+
* The returned stream can be used as a fetch body or Response body.
|
|
320
|
+
*
|
|
321
|
+
* NOTE: `this.streamTools.readable` can only be consumed once, so only one
|
|
322
|
+
* of `buildSyncSseResponse` or `postCheckpointStream` may be called per
|
|
323
|
+
* execution.
|
|
324
|
+
*/
|
|
325
|
+
buildMetadataPrefixedStream() {
|
|
326
|
+
const metadataEvent = buildSseMetadataEvent(this.fnArg.runId);
|
|
327
|
+
return prependToStream(new TextEncoder().encode(metadataEvent), this.streamTools.readable);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Build the initial SSE `Response` that marks the start of streaming to the
|
|
331
|
+
* client. Only used in sync mode. In async mode, the stream is POSTed to the
|
|
332
|
+
* Inngest Server via {@link postCheckpointStream} instead.
|
|
333
|
+
*
|
|
334
|
+
* The response body is the stream's readable side, prefixed with the
|
|
335
|
+
* `inngest.metadata` SSE event.
|
|
336
|
+
*/
|
|
337
|
+
buildSyncSseResponse() {
|
|
338
|
+
return new Response(this.buildMetadataPrefixedStream(), {
|
|
339
|
+
status: 200,
|
|
340
|
+
headers: {
|
|
341
|
+
"Content-Type": "text/event-stream",
|
|
342
|
+
"Cache-Control": "no-cache"
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Wraps a plain return value as an SSE Response.
|
|
348
|
+
*
|
|
349
|
+
* Used when the client sent `Accept: text/event-stream` but
|
|
350
|
+
* `stream.push()`/`pipe()` was NOT called during execution. The
|
|
351
|
+
* checkpointable data is the function's return value. The SSE events are just
|
|
352
|
+
* a delivery mechanism.
|
|
353
|
+
*/
|
|
354
|
+
async wrapResultAsSse(checkpoint, sseResponse) {
|
|
355
|
+
const resultData = checkpoint.data;
|
|
356
|
+
await this.streamCloseSucceeded(sseResponse);
|
|
357
|
+
const clientResponse = this.buildSyncSseResponse();
|
|
358
|
+
const streamingResult = {
|
|
359
|
+
...this.transformOutput({ data: resultData }),
|
|
360
|
+
data: clientResponse
|
|
215
361
|
};
|
|
362
|
+
this.checkpointReturnValue(resultData);
|
|
363
|
+
return streamingResult;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Called when `stream.push()`/`pipe()` is first invoked during sync
|
|
367
|
+
* execution. Resolves {@link earlyStreamResponse} so that `_start()` can
|
|
368
|
+
* return the SSE Response to the HTTP layer immediately, while the core
|
|
369
|
+
* checkpoint loop keeps running steps in the background.
|
|
370
|
+
*/
|
|
371
|
+
handleStreamActivated() {
|
|
372
|
+
if (this.earlyStreamResponse) {
|
|
373
|
+
this.earlyStreamResponse.resolve({
|
|
374
|
+
type: "function-resolved",
|
|
375
|
+
ctx: this.fnArg,
|
|
376
|
+
ops: this.ops,
|
|
377
|
+
data: this.buildSyncSseResponse()
|
|
378
|
+
});
|
|
379
|
+
this.sendRedirectIfReady();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (this.options.stepMode !== StepMode.Sync) this.postCheckpointStream();
|
|
383
|
+
this.sendRedirectIfReady();
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Sends the `inngest.redirect_info` SSE event when both conditions are met:
|
|
387
|
+
* 1. The client accepts SSE (so there's a stream to write the event to)
|
|
388
|
+
* 2. We have a realtime token (first checkpoint has completed)
|
|
389
|
+
*
|
|
390
|
+
* Called after the first checkpoint AND on stream activation, whichever
|
|
391
|
+
* comes second, so the redirect is sent as early as possible.
|
|
392
|
+
*/
|
|
393
|
+
sendRedirectIfReady() {
|
|
394
|
+
if (this.redirectSent) return;
|
|
395
|
+
if (!this.options.acceptsSse) return;
|
|
396
|
+
if (!this.state.checkpointedRun) return;
|
|
397
|
+
this.redirectSent = true;
|
|
398
|
+
const { realtimeToken } = this.state.checkpointedRun;
|
|
399
|
+
this.redirectPromise = (async () => {
|
|
400
|
+
try {
|
|
401
|
+
const redirect = await this.options.client["inngestApi"].getRealtimeStreamRedirect(realtimeToken);
|
|
402
|
+
this.streamTools.sendRedirectInfo({
|
|
403
|
+
runId: this.fnArg.runId,
|
|
404
|
+
url: redirect.url
|
|
405
|
+
});
|
|
406
|
+
} catch (err) {
|
|
407
|
+
this.options.client[internalLoggerSymbol].warn({ err }, "Failed to fetch realtime stream redirect URL");
|
|
408
|
+
}
|
|
409
|
+
})();
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Await the pending redirect-info fetch, then close the stream with a
|
|
413
|
+
* succeeded result. Awaiting first guarantees the redirect event is
|
|
414
|
+
* enqueued on the write chain before the close event.
|
|
415
|
+
*/
|
|
416
|
+
async streamCloseSucceeded(response) {
|
|
417
|
+
await this.redirectPromise;
|
|
418
|
+
this.streamTools.closeSucceeded(response);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Await the pending redirect-info fetch, then close the stream with a
|
|
422
|
+
* failed result.
|
|
423
|
+
*/
|
|
424
|
+
async streamCloseFailed(error) {
|
|
425
|
+
await this.redirectPromise;
|
|
426
|
+
this.streamTools.closeFailed(error);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Await the pending redirect-info fetch, then close the stream without
|
|
430
|
+
* a result event.
|
|
431
|
+
*/
|
|
432
|
+
async streamEnd() {
|
|
433
|
+
await this.redirectPromise;
|
|
434
|
+
this.streamTools.end();
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* POST stream data to the checkpoint stream ingest endpoint.
|
|
438
|
+
*
|
|
439
|
+
* Called eagerly from handleStreamActivated so chunks flow in
|
|
440
|
+
* real-time, or after completion if stream.push() was never called.
|
|
441
|
+
*/
|
|
442
|
+
postCheckpointStream() {
|
|
443
|
+
try {
|
|
444
|
+
this.options.client["inngestApi"].checkpointStream({
|
|
445
|
+
runId: this.fnArg.runId,
|
|
446
|
+
body: this.buildMetadataPrefixedStream()
|
|
447
|
+
}).catch((err) => {
|
|
448
|
+
this.devDebug("checkpoint stream POST error:", err);
|
|
449
|
+
});
|
|
450
|
+
} catch (err) {
|
|
451
|
+
this.devDebug("checkpoint stream POST error:", err);
|
|
452
|
+
}
|
|
216
453
|
}
|
|
217
454
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
455
|
+
* Checkpoints the return value of a function that was delivered via SSE.
|
|
456
|
+
* Runs in the background so it doesn't block the client stream.
|
|
220
457
|
*/
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
458
|
+
async checkpointReturnValue(data) {
|
|
459
|
+
try {
|
|
460
|
+
if (this.options.createResponse) await this.checkpoint([{
|
|
461
|
+
op: StepOpCode.RunComplete,
|
|
462
|
+
id: hashId(RUN_COMPLETE_STEP_ID),
|
|
463
|
+
data: await this.options.createResponse(jsonResponse(data))
|
|
464
|
+
}]);
|
|
465
|
+
} catch (err) {
|
|
466
|
+
this.devDebug("error during background checkpoint of SSE result, client stream unaffected:", err);
|
|
467
|
+
}
|
|
224
468
|
}
|
|
225
469
|
/**
|
|
226
470
|
* Creates a handler for every checkpoint type, defining what to do when we
|
|
@@ -311,19 +555,67 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
311
555
|
};
|
|
312
556
|
const syncHandlers = {
|
|
313
557
|
"": commonCheckpointHandler,
|
|
314
|
-
"function-resolved": async (checkpoint
|
|
315
|
-
const
|
|
558
|
+
"function-resolved": async (checkpoint) => {
|
|
559
|
+
const usingSseStream = !!this.earlyStreamResponse;
|
|
560
|
+
if (this.streamTools.activated) {
|
|
561
|
+
let resultData = checkpoint.data;
|
|
562
|
+
let sseResponse;
|
|
563
|
+
if (checkpoint.data instanceof Response) {
|
|
564
|
+
const body = await (usingSseStream ? checkpoint.data.text() : checkpoint.data.clone().text());
|
|
565
|
+
sseResponse = extractSseResponse(checkpoint.data, body);
|
|
566
|
+
resultData = body;
|
|
567
|
+
} else sseResponse = defaultSseResponse(resultData);
|
|
568
|
+
await this.streamCloseSucceeded(sseResponse);
|
|
569
|
+
if (usingSseStream) {
|
|
570
|
+
this.checkpointReturnValue(resultData);
|
|
571
|
+
return this.transformOutput({ data: resultData });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (this.options.acceptsSse) {
|
|
575
|
+
let sseResponse;
|
|
576
|
+
if (checkpoint.data instanceof Response) {
|
|
577
|
+
const body = await checkpoint.data.text();
|
|
578
|
+
sseResponse = extractSseResponse(checkpoint.data, body);
|
|
579
|
+
checkpoint = {
|
|
580
|
+
...checkpoint,
|
|
581
|
+
data: body
|
|
582
|
+
};
|
|
583
|
+
} else sseResponse = defaultSseResponse(checkpoint.data);
|
|
584
|
+
return this.wrapResultAsSse(checkpoint, sseResponse);
|
|
585
|
+
}
|
|
586
|
+
if (checkpoint.data instanceof Response) {
|
|
587
|
+
this.checkpointReturnValue(null);
|
|
588
|
+
return this.transformOutput({ data: checkpoint.data });
|
|
589
|
+
}
|
|
316
590
|
await this.checkpoint([{
|
|
317
591
|
op: StepOpCode.RunComplete,
|
|
318
|
-
id:
|
|
319
|
-
data: await this.options.createResponse(
|
|
592
|
+
id: hashId(RUN_COMPLETE_STEP_ID),
|
|
593
|
+
data: await this.options.createResponse(jsonResponse(checkpoint.data))
|
|
320
594
|
}]);
|
|
321
|
-
return
|
|
595
|
+
return this.transformOutput({ data: checkpoint.data });
|
|
322
596
|
},
|
|
323
597
|
"function-rejected": async (checkpoint) => {
|
|
324
|
-
|
|
598
|
+
const usingSseStream = !!this.earlyStreamResponse;
|
|
599
|
+
const isFinal = !this.retriability(checkpoint.error);
|
|
600
|
+
if (this.streamTools.activated && usingSseStream) {
|
|
601
|
+
(async () => {
|
|
602
|
+
try {
|
|
603
|
+
await this.checkpoint([{
|
|
604
|
+
id: hashId(RUN_COMPLETE_STEP_ID),
|
|
605
|
+
op: isFinal ? StepOpCode.StepFailed : StepOpCode.StepError,
|
|
606
|
+
error: checkpoint.error
|
|
607
|
+
}]);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
this.options.client[internalLoggerSymbol].warn({ err }, "Failed to checkpoint function error");
|
|
610
|
+
}
|
|
611
|
+
if (isFinal) await this.streamCloseFailed(errorMessage(checkpoint.error));
|
|
612
|
+
else await this.streamEnd();
|
|
613
|
+
})();
|
|
614
|
+
return this.transformOutput({ error: checkpoint.error });
|
|
615
|
+
}
|
|
616
|
+
if (isFinal) return this.transformOutput({ error: checkpoint.error });
|
|
325
617
|
return this.checkpointAndSwitchToAsync([{
|
|
326
|
-
id:
|
|
618
|
+
id: hashId(RUN_COMPLETE_STEP_ID),
|
|
327
619
|
op: StepOpCode.StepError,
|
|
328
620
|
error: checkpoint.error
|
|
329
621
|
}]);
|
|
@@ -345,7 +637,7 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
345
637
|
const result = await this.executeStep(steps[0]);
|
|
346
638
|
const transformed = await stepRanHandler(result);
|
|
347
639
|
if (transformed.type !== "step-ran") throw new Error("Unexpected checkpoint handler result type after running step in sync mode");
|
|
348
|
-
if (result.error) return this.checkpointAndSwitchToAsync([transformed.step]);
|
|
640
|
+
if (result.error) return this.checkpointAndSwitchToAsync([transformed.step], result.error);
|
|
349
641
|
const stepToResume = this.resumeStepWithResult(result);
|
|
350
642
|
delete this.state.executingStep;
|
|
351
643
|
const stepForCheckpoint = {
|
|
@@ -367,13 +659,25 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
367
659
|
const asyncHandlers = {
|
|
368
660
|
"": commonCheckpointHandler,
|
|
369
661
|
"function-resolved": async ({ data }) => {
|
|
662
|
+
let resultData = data;
|
|
663
|
+
let sseResponse;
|
|
664
|
+
if (data instanceof Response) {
|
|
665
|
+
const body = await data.text();
|
|
666
|
+
sseResponse = extractSseResponse(data, body);
|
|
667
|
+
resultData = body;
|
|
668
|
+
} else sseResponse = defaultSseResponse(resultData);
|
|
370
669
|
const newStepsResult = await maybeReturnNewSteps();
|
|
371
670
|
if (newStepsResult) return newStepsResult;
|
|
372
|
-
|
|
373
|
-
|
|
671
|
+
await this.streamCloseSucceeded(sseResponse);
|
|
672
|
+
if (!this.streamTools.activated) this.postCheckpointStream();
|
|
673
|
+
if (this.options.createResponse) data = await this.options.createResponse(jsonResponse(resultData));
|
|
674
|
+
return this.transformOutput({ data });
|
|
374
675
|
},
|
|
375
676
|
"function-rejected": async (checkpoint) => {
|
|
376
|
-
|
|
677
|
+
if (!this.retriability(checkpoint.error)) await this.streamCloseFailed(errorMessage(checkpoint.error));
|
|
678
|
+
else await this.streamEnd();
|
|
679
|
+
if (!this.streamTools.activated) this.postCheckpointStream();
|
|
680
|
+
return this.transformOutput({ error: checkpoint.error });
|
|
377
681
|
},
|
|
378
682
|
"steps-found": async ({ steps }) => {
|
|
379
683
|
const stepResult = await this.tryExecuteStep(steps);
|
|
@@ -405,7 +709,7 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
405
709
|
if (output?.type === "function-resolved") {
|
|
406
710
|
const steps = this.state.checkpointingStepBuffer.concat({
|
|
407
711
|
op: StepOpCode.RunComplete,
|
|
408
|
-
id:
|
|
712
|
+
id: hashId(RUN_COMPLETE_STEP_ID),
|
|
409
713
|
data: output.data
|
|
410
714
|
});
|
|
411
715
|
if (isNonEmpty(steps)) return {
|
|
@@ -465,6 +769,7 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
465
769
|
}
|
|
466
770
|
},
|
|
467
771
|
"checkpointing-runtime-reached": async () => {
|
|
772
|
+
this.options.client[internalLoggerSymbol].debug("Checkpointing runtime reached; sending discovery request");
|
|
468
773
|
return {
|
|
469
774
|
type: "steps-found",
|
|
470
775
|
ctx: this.fnArg,
|
|
@@ -548,7 +853,8 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
548
853
|
const store = await getAsyncCtx();
|
|
549
854
|
if (store?.execution) store.execution.executingStep = {
|
|
550
855
|
id,
|
|
551
|
-
name: displayName
|
|
856
|
+
name: displayName,
|
|
857
|
+
hashedId
|
|
552
858
|
};
|
|
553
859
|
this.devDebug(`executing step "${id}"`);
|
|
554
860
|
if (this.rootSpanId && this.options.checkpointingConfig) clientProcessorMap.get(this.options.client)?.declareStepExecution(this.rootSpanId, userland.id ?? "", userland.index ?? 0, hashedId, this.options.data?.attempt ?? 0);
|
|
@@ -574,6 +880,7 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
574
880
|
const metadata = this.state.metadata?.get(id);
|
|
575
881
|
const serverData = await resultPromise;
|
|
576
882
|
await this.middlewareManager.onStepComplete(stepInfo, serverData);
|
|
883
|
+
this.streamTools.commit(hashedId);
|
|
577
884
|
return {
|
|
578
885
|
...outgoingOp,
|
|
579
886
|
data: serverData,
|
|
@@ -648,6 +955,7 @@ var InngestExecutionEngine = class extends InngestExecution {
|
|
|
648
955
|
const isFinal = !this.retriability(error);
|
|
649
956
|
const metadata = this.state.metadata?.get(id);
|
|
650
957
|
await this.middlewareManager.onStepError(stepInfo, error instanceof Error ? error : new Error(String(error)), isFinal);
|
|
958
|
+
this.streamTools.rollback(outgoingOp.id);
|
|
651
959
|
const serialized = serializeError(error);
|
|
652
960
|
return {
|
|
653
961
|
...outgoingOp,
|