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