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.
Files changed (93) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/api/api.cjs +30 -1
  3. package/api/api.cjs.map +1 -1
  4. package/api/api.d.cts +19 -0
  5. package/api/api.d.cts.map +1 -1
  6. package/api/api.d.ts +19 -0
  7. package/api/api.d.ts.map +1 -1
  8. package/api/api.js +30 -1
  9. package/api/api.js.map +1 -1
  10. package/components/InngestCommHandler.cjs +9 -1
  11. package/components/InngestCommHandler.cjs.map +1 -1
  12. package/components/InngestCommHandler.d.cts.map +1 -1
  13. package/components/InngestCommHandler.d.ts.map +1 -1
  14. package/components/InngestCommHandler.js +9 -1
  15. package/components/InngestCommHandler.js.map +1 -1
  16. package/components/StreamTools.cjs +241 -0
  17. package/components/StreamTools.cjs.map +1 -0
  18. package/components/StreamTools.d.cts +161 -0
  19. package/components/StreamTools.d.cts.map +1 -0
  20. package/components/StreamTools.d.ts +161 -0
  21. package/components/StreamTools.d.ts.map +1 -0
  22. package/components/StreamTools.js +240 -0
  23. package/components/StreamTools.js.map +1 -0
  24. package/components/createWebApiCommHandler.cjs +46 -0
  25. package/components/createWebApiCommHandler.cjs.map +1 -0
  26. package/components/createWebApiCommHandler.js +46 -0
  27. package/components/createWebApiCommHandler.js.map +1 -0
  28. package/components/execution/InngestExecution.cjs.map +1 -1
  29. package/components/execution/InngestExecution.d.cts +6 -0
  30. package/components/execution/InngestExecution.d.cts.map +1 -1
  31. package/components/execution/InngestExecution.d.ts +6 -0
  32. package/components/execution/InngestExecution.d.ts.map +1 -1
  33. package/components/execution/InngestExecution.js.map +1 -1
  34. package/components/execution/als.cjs.map +1 -1
  35. package/components/execution/als.d.cts +9 -1
  36. package/components/execution/als.d.cts.map +1 -1
  37. package/components/execution/als.d.ts +9 -1
  38. package/components/execution/als.d.ts.map +1 -1
  39. package/components/execution/als.js.map +1 -1
  40. package/components/execution/engine.cjs +334 -26
  41. package/components/execution/engine.cjs.map +1 -1
  42. package/components/execution/engine.d.cts +1 -0
  43. package/components/execution/engine.d.cts.map +1 -1
  44. package/components/execution/engine.d.ts +1 -0
  45. package/components/execution/engine.d.ts.map +1 -1
  46. package/components/execution/engine.js +334 -26
  47. package/components/execution/engine.js.map +1 -1
  48. package/components/execution/streaming.cjs +208 -0
  49. package/components/execution/streaming.cjs.map +1 -0
  50. package/components/execution/streaming.d.cts +12 -0
  51. package/components/execution/streaming.d.cts.map +1 -0
  52. package/components/execution/streaming.d.ts +12 -0
  53. package/components/execution/streaming.d.ts.map +1 -0
  54. package/components/execution/streaming.js +198 -0
  55. package/components/execution/streaming.js.map +1 -0
  56. package/edge.cjs +19 -32
  57. package/edge.cjs.map +1 -1
  58. package/edge.d.cts +1 -1
  59. package/edge.d.cts.map +1 -1
  60. package/edge.d.ts +1 -1
  61. package/edge.d.ts.map +1 -1
  62. package/edge.js +19 -32
  63. package/edge.js.map +1 -1
  64. package/experimental/durable-endpoints/client.cjs +114 -0
  65. package/experimental/durable-endpoints/client.cjs.map +1 -0
  66. package/experimental/durable-endpoints/client.d.cts +49 -0
  67. package/experimental/durable-endpoints/client.d.cts.map +1 -0
  68. package/experimental/durable-endpoints/client.d.ts +49 -0
  69. package/experimental/durable-endpoints/client.d.ts.map +1 -0
  70. package/experimental/durable-endpoints/client.js +114 -0
  71. package/experimental/durable-endpoints/client.js.map +1 -0
  72. package/experimental/durable-endpoints/index.cjs +3 -0
  73. package/experimental/durable-endpoints/index.d.cts +2 -0
  74. package/experimental/durable-endpoints/index.d.ts +2 -0
  75. package/experimental/durable-endpoints/index.js +3 -0
  76. package/helpers/promises.cjs.map +1 -1
  77. package/helpers/promises.js.map +1 -1
  78. package/node.cjs +97 -0
  79. package/node.cjs.map +1 -1
  80. package/node.d.cts +34 -2
  81. package/node.d.cts.map +1 -1
  82. package/node.d.ts +34 -2
  83. package/node.d.ts.map +1 -1
  84. package/node.js +95 -2
  85. package/node.js.map +1 -1
  86. package/package.json +17 -1
  87. package/react.d.cts.map +1 -1
  88. package/version.cjs +1 -1
  89. package/version.cjs.map +1 -1
  90. package/version.d.cts +1 -1
  91. package/version.d.ts +1 -1
  92. package/version.js +1 -1
  93. 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
- if (!this.options.queueItemId) throw new Error("Missing queueItemId for async checkpointing. This is a bug in the Inngest SDK.");
197
- if (!this.options.internalFnId) throw new Error("Missing internalFnId for async checkpointing. This is a bug in the Inngest SDK.");
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: this.options.internalFnId,
201
- queueItemId: this.options.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: this.state.checkpointedRun?.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
- * Returns whether we're in the final attempt of execution, or `null` if we
219
- * can't determine this in the SDK.
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
- inFinalAttempt() {
222
- if (typeof this.fnArg.maxAttempts !== "number") return null;
223
- return this.fnArg.attempt + 1 >= this.fnArg.maxAttempts;
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, i) => {
315
- const transformedData = checkpoint.data;
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: _internals.hashId("complete"),
319
- data: await this.options.createResponse(transformedData)
592
+ id: hashId(RUN_COMPLETE_STEP_ID),
593
+ data: await this.options.createResponse(jsonResponse(checkpoint.data))
320
594
  }]);
321
- return await this.transformOutput({ data: checkpoint.data });
595
+ return this.transformOutput({ data: checkpoint.data });
322
596
  },
323
597
  "function-rejected": async (checkpoint) => {
324
- if (this.inFinalAttempt()) return await this.transformOutput({ error: checkpoint.error });
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: _internals.hashId("complete"),
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
- if (this.options.createResponse) data = await this.options.createResponse(data);
373
- return await this.transformOutput({ data });
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
- return await this.transformOutput({ error: checkpoint.error });
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: _internals.hashId("complete"),
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,