inngest 4.3.0 → 4.4.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 (134) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/api/schema.d.cts +2 -2
  3. package/api/schema.d.cts.map +1 -1
  4. package/api/schema.d.ts +2 -2
  5. package/api/schema.d.ts.map +1 -1
  6. package/components/DeferredFunction.cjs +66 -0
  7. package/components/DeferredFunction.cjs.map +1 -0
  8. package/components/DeferredFunction.d.cts +99 -0
  9. package/components/DeferredFunction.d.cts.map +1 -0
  10. package/components/DeferredFunction.d.ts +99 -0
  11. package/components/DeferredFunction.d.ts.map +1 -0
  12. package/components/DeferredFunction.js +65 -0
  13. package/components/DeferredFunction.js.map +1 -0
  14. package/components/Inngest.cjs +5 -2
  15. package/components/Inngest.cjs.map +1 -1
  16. package/components/Inngest.d.cts +4 -3
  17. package/components/Inngest.d.cts.map +1 -1
  18. package/components/Inngest.d.ts +4 -3
  19. package/components/Inngest.d.ts.map +1 -1
  20. package/components/Inngest.js +5 -2
  21. package/components/Inngest.js.map +1 -1
  22. package/components/InngestCommHandler.cjs +28 -23
  23. package/components/InngestCommHandler.cjs.map +1 -1
  24. package/components/InngestCommHandler.d.cts +9 -4
  25. package/components/InngestCommHandler.d.cts.map +1 -1
  26. package/components/InngestCommHandler.d.ts +9 -4
  27. package/components/InngestCommHandler.d.ts.map +1 -1
  28. package/components/InngestCommHandler.js +28 -23
  29. package/components/InngestCommHandler.js.map +1 -1
  30. package/components/InngestFunction.cjs +27 -19
  31. package/components/InngestFunction.cjs.map +1 -1
  32. package/components/InngestFunction.d.cts +6 -1
  33. package/components/InngestFunction.d.cts.map +1 -1
  34. package/components/InngestFunction.d.ts +6 -1
  35. package/components/InngestFunction.d.ts.map +1 -1
  36. package/components/InngestFunction.js +27 -19
  37. package/components/InngestFunction.js.map +1 -1
  38. package/components/InngestGroupTools.cjs +1 -1
  39. package/components/InngestGroupTools.cjs.map +1 -1
  40. package/components/InngestGroupTools.js +1 -1
  41. package/components/InngestGroupTools.js.map +1 -1
  42. package/components/connect/config.cjs +2 -0
  43. package/components/connect/config.cjs.map +1 -1
  44. package/components/connect/config.js +2 -0
  45. package/components/connect/config.js.map +1 -1
  46. package/components/execution/InngestExecution.cjs.map +1 -1
  47. package/components/execution/InngestExecution.d.cts +19 -2
  48. package/components/execution/InngestExecution.d.cts.map +1 -1
  49. package/components/execution/InngestExecution.d.ts +19 -2
  50. package/components/execution/InngestExecution.d.ts.map +1 -1
  51. package/components/execution/InngestExecution.js.map +1 -1
  52. package/components/execution/engine.cjs +232 -27
  53. package/components/execution/engine.cjs.map +1 -1
  54. package/components/execution/engine.d.cts +14 -0
  55. package/components/execution/engine.d.cts.map +1 -1
  56. package/components/execution/engine.d.ts +14 -0
  57. package/components/execution/engine.d.ts.map +1 -1
  58. package/components/execution/engine.js +232 -27
  59. package/components/execution/engine.js.map +1 -1
  60. package/components/execution/lazyOps.cjs +64 -0
  61. package/components/execution/lazyOps.cjs.map +1 -0
  62. package/components/execution/lazyOps.d.cts +42 -0
  63. package/components/execution/lazyOps.d.cts.map +1 -0
  64. package/components/execution/lazyOps.d.ts +42 -0
  65. package/components/execution/lazyOps.d.ts.map +1 -0
  66. package/components/execution/lazyOps.js +63 -0
  67. package/components/execution/lazyOps.js.map +1 -0
  68. package/components/execution/otel/middleware.d.cts +6 -3
  69. package/components/execution/otel/middleware.d.cts.map +1 -1
  70. package/components/execution/otel/middleware.d.ts +6 -3
  71. package/components/execution/otel/middleware.d.ts.map +1 -1
  72. package/components/middleware/middleware.cjs.map +1 -1
  73. package/components/middleware/middleware.d.cts +1 -1
  74. package/components/middleware/middleware.d.cts.map +1 -1
  75. package/components/middleware/middleware.d.ts +1 -1
  76. package/components/middleware/middleware.d.ts.map +1 -1
  77. package/components/middleware/middleware.js.map +1 -1
  78. package/components/middleware/utils.cjs +1 -0
  79. package/components/middleware/utils.cjs.map +1 -1
  80. package/components/middleware/utils.js +1 -0
  81. package/components/middleware/utils.js.map +1 -1
  82. package/components/realtime/types.d.cts +4 -4
  83. package/components/realtime/types.d.cts.map +1 -1
  84. package/components/realtime/types.d.ts +4 -4
  85. package/components/realtime/types.d.ts.map +1 -1
  86. package/components/triggers/typeHelpers.cjs.map +1 -1
  87. package/components/triggers/typeHelpers.d.cts +11 -0
  88. package/components/triggers/typeHelpers.d.cts.map +1 -1
  89. package/components/triggers/typeHelpers.d.ts +11 -0
  90. package/components/triggers/typeHelpers.d.ts.map +1 -1
  91. package/components/triggers/typeHelpers.js.map +1 -1
  92. package/experimental.cjs +3 -0
  93. package/experimental.d.cts +2 -1
  94. package/experimental.d.ts +2 -1
  95. package/experimental.js +2 -1
  96. package/helpers/consts.cjs +6 -0
  97. package/helpers/consts.cjs.map +1 -1
  98. package/helpers/consts.d.cts +6 -0
  99. package/helpers/consts.d.cts.map +1 -1
  100. package/helpers/consts.d.ts +6 -0
  101. package/helpers/consts.d.ts.map +1 -1
  102. package/helpers/consts.js +6 -0
  103. package/helpers/consts.js.map +1 -1
  104. package/helpers/functions.cjs +1 -0
  105. package/helpers/functions.cjs.map +1 -1
  106. package/helpers/functions.js +1 -0
  107. package/helpers/functions.js.map +1 -1
  108. package/helpers/marker.cjs +21 -0
  109. package/helpers/marker.cjs.map +1 -0
  110. package/helpers/marker.d.cts +12 -0
  111. package/helpers/marker.d.cts.map +1 -0
  112. package/helpers/marker.d.ts +12 -0
  113. package/helpers/marker.d.ts.map +1 -0
  114. package/helpers/marker.js +19 -0
  115. package/helpers/marker.js.map +1 -0
  116. package/package.json +2 -2
  117. package/proto/src/components/connect/protobuf/connect.cjs +11 -2
  118. package/proto/src/components/connect/protobuf/connect.cjs.map +1 -1
  119. package/proto/src/components/connect/protobuf/connect.js +11 -2
  120. package/proto/src/components/connect/protobuf/connect.js.map +1 -1
  121. package/types.cjs +2 -0
  122. package/types.cjs.map +1 -1
  123. package/types.d.cts +52 -18
  124. package/types.d.cts.map +1 -1
  125. package/types.d.ts +52 -18
  126. package/types.d.ts.map +1 -1
  127. package/types.js +2 -0
  128. package/types.js.map +1 -1
  129. package/version.cjs +1 -1
  130. package/version.cjs.map +1 -1
  131. package/version.d.cts +1 -1
  132. package/version.d.ts +1 -1
  133. package/version.js +1 -1
  134. package/version.js.map +1 -1
@@ -4,6 +4,7 @@ import { MetadataUpdate } from "../InngestMetadata.cjs";
4
4
  import { BasicFoundStep, InngestExecutionFactory, MemoizedOp } from "./InngestExecution.cjs";
5
5
  import { OutgoingOp } from "../../types.cjs";
6
6
  import { StepError } from "../StepError.cjs";
7
+ import { LazyOps } from "./lazyOps.cjs";
7
8
 
8
9
  //#region src/components/execution/engine.d.ts
9
10
  declare namespace engine_d_exports {
@@ -46,6 +47,13 @@ interface ExecutionState {
46
47
  * with state from the executor.
47
48
  */
48
49
  stepState: Record<string, MemoizedOp>;
50
+ /**
51
+ * Hashed defer step IDs the backend has already received. Used to skip
52
+ * re-emitting `DeferAdd` ops on replay. Defer ops aren't memoized like
53
+ * normal steps (they have no result), so this lives separately from
54
+ * `stepState`.
55
+ */
56
+ priorDefers: Record<string, unknown>;
49
57
  /**
50
58
  * The number of steps we expect to fulfil based on the state passed from the
51
59
  * Executor.
@@ -122,6 +130,12 @@ interface ExecutionState {
122
130
  * A buffer of steps that are currently queued to be checkpointed.
123
131
  */
124
132
  checkpointingStepBuffer: OutgoingOp[];
133
+ /**
134
+ * Buffer for opcode-only sync ops (e.g. `DeferAdd`) awaiting shipment
135
+ * on the next outbound wire message. See `ARCHITECTURE.md` and
136
+ * `lazyOps.ts` for the rationale.
137
+ */
138
+ lazyOps: LazyOps;
125
139
  /**
126
140
  * Metadata collected during execution to be sent with outgoing ops.
127
141
  */
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.cts","names":[],"sources":["../../../src/components/execution/engine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAwHa,uBAAuB;;;;UAq6EnB,WAAA;;YACU,cAAc;;;IAt6E5B,KAAA,EAAA,OAAA;EAq6EI,CAAA;EAAW,mBAAA,EAAA;IACD,IAAA,EAAA,OAAA;;kBAIjB,EAAA;IACM,IAAA,EADN,UACM;IAAc,UAAA,EAAd,cAAc,EAAA;IAOzB,eAAU,EAAA,MAAA;EAAA,CAAA;iCACD,EAAA,CAAA,CAAA;yCAA+B,EAAA,CAAA,CAAA;;KADxC,UAAA,GAC0D,QAAnC,MAAd,WAAc,GAAA,QAAA,CAAA;EACpB,IAAA,EADqC,CACrC;AAAW,CAAA,GADgC,WAChC,CAD4C,CAC5C,CAAA,CAAA,EA+BnB,CAAA,MA/BQ,WA+BS,CAAA;AAAc,UAAd,cAAA,CAAc;;;;;;eAwBV,CAAA,EAlBH,QAkBG,CAlBM,IAkBN,CAlBW,UAkBX,EAAA,IAAA,CAAA,CAAA;;;;;WAoCK,EAhDb,MAgDa,CAAA,MAAA,EAhDE,UAgDF,CAAA;;;;;gBA6Cb,EAAA,MAAA;EAAG;AACf;AAgBkC;;OA4BjC,EA9HO,GA8HP,CAAA,MAAA,EA9HmB,SA8HnB,CAAA;;;;;;UAOE,EAAA,OAAA;EAAyB;AAoC7B;;;MAjFiC,EA3EzB,cA2EyB,CA3EV,UA2EU,EAAA,IAAA,EAAA,IAAA,CAAA;;;;wBAtET;;;;;;;;;;;;;;;0BAkBE;;;;;;;;;;;;;;;8BAgBI;;;;;;;;;;;;;;;;;;;;;2BAwBH;;;;aAKd,YAAY,MAAM;;;;;UAiBrB,yBAAA;;;;;;;;;;;;;;;iBA2BD,sBAAA;;;;;;mBAMU;YACP,YAAY;IACpB;;;;cAoCS;eAjFO,eAAa"}
1
+ {"version":3,"file":"engine.d.cts","names":[],"sources":["../../../src/components/execution/engine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cA2Ha,uBAAuB;;;;UAmuFnB,WAAA;;YACU,cAAc;;;IApuF5B,KAAA,EAAA,OAAA;EAmuFI,CAAA;EAAW,mBAAA,EAAA;IACD,IAAA,EAAA,OAAA;;kBAIjB,EAAA;IACM,IAAA,EADN,UACM;IAAc,UAAA,EAAd,cAAc,EAAA;IAOzB,eAAU,EAAA,MAAA;EAAA,CAAA;iCACD,EAAA,CAAA,CAAA;yCAA+B,EAAA,CAAA,CAAA;;KADxC,UAAA,GAC0D,QAAnC,MAAd,WAAc,GAAA,QAAA,CAAA;EACpB,IAAA,EADqC,CACrC;AAAW,CAAA,GADgC,WAChC,CAD4C,CAC5C,CAAA,CAAA,EA+BnB,CAAA,MA/BQ,WA+BS,CAAA;AAAc,UAAd,cAAA,CAAc;;;;;;eAoBhB,CAAA,EAdG,QAcH,CAdY,IAcZ,CAdiB,UAcjB,EAAA,IAAA,CAAA,CAAA;;;;;WA8BS,EAtCX,MAsCW,CAAA,MAAA,EAtCI,UAsCJ,CAAA;;;;;;;aAsEX,EApGE,MAoGF,CAAA,MAAA,EAAA,OAAA,CAAA;EAAG;AACf;AAgBkC;;gBA4BjC,EAAA,MAAA;;;;;OAMU,EA3IH,GA2IG,CAAA,MAAA,EA3IS,SA2IT,CAAA;;;AAqCZ;;;UAjFiC,EAAA,OAAA;;;;;QAlFzB,eAAe;;;;wBAKC;;;;;;;;;;;;;;;0BAkBE;;;;;;;;;;;;;;;8BAgBI;;;;;;;;;;;;;;;;;;;;;2BAwBH;;;;;;WAOhB;;;;aAKE,YAAY,MAAM;;;;;UAiBrB,yBAAA;;;;;;;;;;;;;;;iBA2BD,sBAAA;;;;;;mBAMU;YACP,YAAY;IACpB;;;;cAoCS;eAjFO,eAAa"}
@@ -4,6 +4,7 @@ import { MetadataUpdate } from "../InngestMetadata.js";
4
4
  import { BasicFoundStep, InngestExecutionFactory, MemoizedOp } from "./InngestExecution.js";
5
5
  import { OutgoingOp } from "../../types.js";
6
6
  import { StepError } from "../StepError.js";
7
+ import { LazyOps } from "./lazyOps.js";
7
8
 
8
9
  //#region src/components/execution/engine.d.ts
9
10
  declare namespace engine_d_exports {
@@ -46,6 +47,13 @@ interface ExecutionState {
46
47
  * with state from the executor.
47
48
  */
48
49
  stepState: Record<string, MemoizedOp>;
50
+ /**
51
+ * Hashed defer step IDs the backend has already received. Used to skip
52
+ * re-emitting `DeferAdd` ops on replay. Defer ops aren't memoized like
53
+ * normal steps (they have no result), so this lives separately from
54
+ * `stepState`.
55
+ */
56
+ priorDefers: Record<string, unknown>;
49
57
  /**
50
58
  * The number of steps we expect to fulfil based on the state passed from the
51
59
  * Executor.
@@ -122,6 +130,12 @@ interface ExecutionState {
122
130
  * A buffer of steps that are currently queued to be checkpointed.
123
131
  */
124
132
  checkpointingStepBuffer: OutgoingOp[];
133
+ /**
134
+ * Buffer for opcode-only sync ops (e.g. `DeferAdd`) awaiting shipment
135
+ * on the next outbound wire message. See `ARCHITECTURE.md` and
136
+ * `lazyOps.ts` for the rationale.
137
+ */
138
+ lazyOps: LazyOps;
125
139
  /**
126
140
  * Metadata collected during execution to be sent with outgoing ops.
127
141
  */
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","names":[],"sources":["../../../src/components/execution/engine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAwHa,uBAAuB;;;;UAq6EnB,WAAA;;YACU,cAAc;;;IAt6E5B,KAAA,EAAA,OAAA;EAq6EI,CAAA;EAAW,mBAAA,EAAA;IACD,IAAA,EAAA,OAAA;;kBAIjB,EAAA;IACM,IAAA,EADN,UACM;IAAc,UAAA,EAAd,cAAc,EAAA;IAOzB,eAAU,EAAA,MAAA;EAAA,CAAA;iCACD,EAAA,CAAA,CAAA;yCAA+B,EAAA,CAAA,CAAA;;KADxC,UAAA,GAC0D,QAAnC,MAAd,WAAc,GAAA,QAAA,CAAA;EACpB,IAAA,EADqC,CACrC;AAAW,CAAA,GADgC,WAChC,CAD4C,CAC5C,CAAA,CAAA,EA+BnB,CAAA,MA/BQ,WA+BS,CAAA;AAAc,UAAd,cAAA,CAAc;;;;;;eAwBV,CAAA,EAlBH,QAkBG,CAlBM,IAkBN,CAlBW,UAkBX,EAAA,IAAA,CAAA,CAAA;;;;;WAoCK,EAhDb,MAgDa,CAAA,MAAA,EAhDE,UAgDF,CAAA;;;;;gBA6Cb,EAAA,MAAA;EAAG;AACf;AAgBkC;;OA4BjC,EA9HO,GA8HP,CAAA,MAAA,EA9HmB,SA8HnB,CAAA;;;;;;UAOE,EAAA,OAAA;EAAyB;AAoC7B;;;MAjFiC,EA3EzB,cA2EyB,CA3EV,UA2EU,EAAA,IAAA,EAAA,IAAA,CAAA;;;;wBAtET;;;;;;;;;;;;;;;0BAkBE;;;;;;;;;;;;;;;8BAgBI;;;;;;;;;;;;;;;;;;;;;2BAwBH;;;;aAKd,YAAY,MAAM;;;;;UAiBrB,yBAAA;;;;;;;;;;;;;;;iBA2BD,sBAAA;;;;;;mBAMU;YACP,YAAY;IACpB;;;;cAoCS;eAjFO,eAAa"}
1
+ {"version":3,"file":"engine.d.ts","names":[],"sources":["../../../src/components/execution/engine.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cA2Ha,uBAAuB;;;;UAmuFnB,WAAA;;YACU,cAAc;;;IApuF5B,KAAA,EAAA,OAAA;EAmuFI,CAAA;EAAW,mBAAA,EAAA;IACD,IAAA,EAAA,OAAA;;kBAIjB,EAAA;IACM,IAAA,EADN,UACM;IAAc,UAAA,EAAd,cAAc,EAAA;IAOzB,eAAU,EAAA,MAAA;EAAA,CAAA;iCACD,EAAA,CAAA,CAAA;yCAA+B,EAAA,CAAA,CAAA;;KADxC,UAAA,GAC0D,QAAnC,MAAd,WAAc,GAAA,QAAA,CAAA;EACpB,IAAA,EADqC,CACrC;AAAW,CAAA,GADgC,WAChC,CAD4C,CAC5C,CAAA,CAAA,EA+BnB,CAAA,MA/BQ,WA+BS,CAAA;AAAc,UAAd,cAAA,CAAc;;;;;;eAoBhB,CAAA,EAdG,QAcH,CAdY,IAcZ,CAdiB,UAcjB,EAAA,IAAA,CAAA,CAAA;;;;;WA8BS,EAtCX,MAsCW,CAAA,MAAA,EAtCI,UAsCJ,CAAA;;;;;;;aAsEX,EApGE,MAoGF,CAAA,MAAA,EAAA,OAAA,CAAA;EAAG;AACf;AAgBkC;;gBA4BjC,EAAA,MAAA;;;;;OAMU,EA3IH,GA2IG,CAAA,MAAA,EA3IS,SA2IT,CAAA;;;AAqCZ;;;UAjFiC,EAAA,OAAA;;;;;QAlFzB,eAAe;;;;wBAKC;;;;;;;;;;;;;;;0BAkBE;;;;;;;;;;;;;;;8BAgBI;;;;;;;;;;;;;;;;;;;;;2BAwBH;;;;;;WAOhB;;;;aAKE,YAAY,MAAM;;;;;UAiBrB,yBAAA;;;;;;;;;;;;;;;iBA2BD,sBAAA;;;;;;mBAMU;YACP,YAAY;IACpB;;;;cAoCS;eAjFO,eAAa"}
@@ -7,6 +7,7 @@ import { StepMode, StepOpCode, jsonErrorSchema } from "../../types.js";
7
7
  import { InngestExecution } from "./InngestExecution.js";
8
8
  import { isRecord } from "../../helpers/types.js";
9
9
  import { undefinedToNull } from "../../helpers/functions.js";
10
+ import { isDeferredFunction } from "../../helpers/marker.js";
10
11
  import { isTemporalDuration } from "../../helpers/temporal.js";
11
12
  import { createDeferredPromise, createDeferredPromiseWithStack, createTimeoutPromise, goIntervalTiming, resolveAfterPending, resolveNextTick, retryWithBackoff, runAsPromise } from "../../helpers/promises.js";
12
13
  import { getAsyncCtx, getAsyncLocalStorage } from "./als.js";
@@ -20,6 +21,7 @@ import { StepError } from "../StepError.js";
20
21
  import { buildSseMetadataEvent, prependToStream } from "./streaming.js";
21
22
  import { Stream } from "../StreamTools.js";
22
23
  import { validateEvents } from "../triggers/utils.js";
24
+ import { LazyOps, isLazyOp } from "./lazyOps.js";
23
25
  import { clientProcessorMap } from "./otel/access.js";
24
26
  import { z } from "zod/v3";
25
27
  import hashjs from "hash.js";
@@ -258,6 +260,8 @@ var InngestExecutionEngine = class extends InngestExecution {
258
260
  throw new Error("Core loop finished without returning a value");
259
261
  }
260
262
  async checkpoint(steps) {
263
+ const lazyOps = this.state.lazyOps.drain();
264
+ if (lazyOps.length > 0) steps = [...steps, ...lazyOps];
261
265
  if (this.options.stepMode === StepMode.Sync) if (!this.state.checkpointedRun) {
262
266
  const res = await retryWithBackoff(() => this.options.client["inngestApi"].checkpointNewRun({
263
267
  runId: this.fnArg.runId,
@@ -515,12 +519,13 @@ var InngestExecutionEngine = class extends InngestExecution {
515
519
  return transformResult;
516
520
  };
517
521
  const maybeReturnNewSteps = async () => {
518
- const newSteps = await this.filterNewSteps(Array.from(this.state.steps.values()));
519
- if (newSteps) return {
522
+ const allSteps = [...await this.filterNewSteps(Array.from(this.state.steps.values())) ?? [], ...this.state.lazyOps.drain()];
523
+ if (allSteps.length === 0) return;
524
+ return {
520
525
  type: "steps-found",
521
526
  ctx: this.fnArg,
522
527
  ops: this.ops,
523
- steps: newSteps
528
+ steps: allSteps
524
529
  };
525
530
  };
526
531
  const attemptCheckpointAndResume = async (stepResult, resume = true, force = false) => {
@@ -666,23 +671,36 @@ var InngestExecutionEngine = class extends InngestExecution {
666
671
  sseResponse = extractSseResponse(data, body);
667
672
  resultData = body;
668
673
  } else sseResponse = defaultSseResponse(resultData);
669
- const newStepsResult = await maybeReturnNewSteps();
670
- if (newStepsResult) return newStepsResult;
674
+ const newSteps = await this.filterNewSteps(Array.from(this.state.steps.values()));
675
+ if (newSteps?.length) return this.attachLazyOps({
676
+ type: "steps-found",
677
+ ctx: this.fnArg,
678
+ ops: this.ops,
679
+ steps: newSteps
680
+ });
671
681
  await this.streamCloseSucceeded(sseResponse);
672
682
  if (!this.streamTools.activated) this.postCheckpointStream();
673
683
  if (this.options.createResponse) data = await this.options.createResponse(jsonResponse(resultData));
674
- return this.transformOutput({ data });
684
+ return this.attachLazyOps(this.transformOutput({ data }));
675
685
  },
676
686
  "function-rejected": async (checkpoint) => {
677
687
  if (!this.retriability(checkpoint.error)) await this.streamCloseFailed(errorMessage(checkpoint.error));
678
688
  else await this.streamEnd();
679
689
  if (!this.streamTools.activated) this.postCheckpointStream();
680
- return this.transformOutput({ error: checkpoint.error });
690
+ return this.attachLazyOps(this.transformOutput({ error: checkpoint.error }));
681
691
  },
682
692
  "steps-found": async ({ steps }) => {
683
693
  const stepResult = await this.tryExecuteStep(steps);
684
- if (stepResult) return stepRanHandler(stepResult);
685
- return maybeReturnNewSteps();
694
+ if (!stepResult) return maybeReturnNewSteps();
695
+ if (this.state.lazyOps.length === 0) return stepRanHandler(stepResult);
696
+ const transformed = await stepRanHandler(stepResult);
697
+ if (transformed.type !== "step-ran") return transformed;
698
+ return this.attachLazyOps({
699
+ type: "steps-found",
700
+ ctx: transformed.ctx,
701
+ ops: transformed.ops,
702
+ steps: [transformed.step]
703
+ });
686
704
  },
687
705
  "step-not-found": ({ step }) => {
688
706
  const { foundSteps, totalFoundSteps } = this.getStepNotFoundDetails();
@@ -705,13 +723,18 @@ var InngestExecutionEngine = class extends InngestExecution {
705
723
  const asyncCheckpointingHandlers = {
706
724
  "": commonCheckpointHandler,
707
725
  "function-resolved": async (checkpoint, i) => {
726
+ const lazyOps = this.state.lazyOps.drain();
708
727
  const output = await asyncHandlers["function-resolved"](checkpoint, i);
709
728
  if (output?.type === "function-resolved") {
710
- const steps = this.state.checkpointingStepBuffer.concat({
711
- op: StepOpCode.RunComplete,
712
- id: hashId(RUN_COMPLETE_STEP_ID),
713
- data: output.data
714
- });
729
+ const steps = [
730
+ ...this.state.checkpointingStepBuffer,
731
+ ...lazyOps,
732
+ {
733
+ op: StepOpCode.RunComplete,
734
+ id: hashId(RUN_COMPLETE_STEP_ID),
735
+ data: output.data
736
+ }
737
+ ];
715
738
  if (isNonEmpty(steps)) return {
716
739
  type: "steps-found",
717
740
  ctx: output.ctx,
@@ -719,9 +742,14 @@ var InngestExecutionEngine = class extends InngestExecution {
719
742
  steps
720
743
  };
721
744
  }
745
+ if (output?.type === "steps-found" && lazyOps.length) return {
746
+ ...output,
747
+ steps: [...output.steps, ...lazyOps]
748
+ };
749
+ return output;
722
750
  },
723
751
  "function-rejected": async (checkpoint) => {
724
- if (this.state.checkpointingStepBuffer.length) {
752
+ if (this.state.checkpointingStepBuffer.length || this.state.lazyOps.length > 0) {
725
753
  const fallback = await attemptCheckpointAndResume(void 0, false, true);
726
754
  if (fallback) return fallback;
727
755
  }
@@ -966,7 +994,11 @@ var InngestExecutionEngine = class extends InngestExecution {
966
994
  * Validate event data against schemas defined in function triggers.
967
995
  */
968
996
  async validateEventSchemas() {
969
- if (this.options.isFailureHandler) return;
997
+ if (this.options.handlerKind === "failure") return;
998
+ if (this.options.handlerKind === "defer") {
999
+ await this.validateDeferEventSchema();
1000
+ return;
1001
+ }
970
1002
  const triggers = this.options.fn.opts.triggers;
971
1003
  if (!triggers || triggers.length === 0) return;
972
1004
  const fnArgEvents = this.fnArg.events;
@@ -977,6 +1009,17 @@ var InngestExecutionEngine = class extends InngestExecution {
977
1009
  })), triggers);
978
1010
  }
979
1011
  /**
1012
+ * Validate the deferred event's data against the defer function's own
1013
+ * schema (set via `createDefer`'s `opts.schema`).
1014
+ */
1015
+ async validateDeferEventSchema() {
1016
+ const fn = this.options.fn;
1017
+ if (!isDeferredFunction(fn) || !fn.schema) return;
1018
+ const eventData = this.fnArg.event?.data;
1019
+ const result = await fn.schema["~standard"].validate(eventData);
1020
+ if (result.issues) throw new NonRetriableError(`defer handler "${fn.id(this.options.client.id)}" schema validation failed: ${JSON.stringify(result.issues)}`);
1021
+ }
1022
+ /**
980
1023
  * Using middleware, transform output before returning.
981
1024
  */
982
1025
  transformOutput(dataOrError) {
@@ -999,6 +1042,64 @@ var InngestExecutionEngine = class extends InngestExecution {
999
1042
  data: undefinedToNull(data)
1000
1043
  };
1001
1044
  }
1045
+ /**
1046
+ * Drain buffered lazy ops (e.g. `DeferAdd` from `defer()`) and merge them
1047
+ * into `result` so they ship in the same outbound message. Lazy ops are
1048
+ * fire-and-forget and have no natural shipping moment, so each terminal code
1049
+ * path must ship them or they're silently dropped.
1050
+ */
1051
+ attachLazyOps(result, extras = []) {
1052
+ const lazyOps = this.state.lazyOps.drain();
1053
+ if (lazyOps.length === 0 && extras.length === 0) return result;
1054
+ switch (result.type) {
1055
+ case "function-resolved": {
1056
+ const steps = [
1057
+ ...extras,
1058
+ ...lazyOps,
1059
+ {
1060
+ op: StepOpCode.RunComplete,
1061
+ id: hashId(RUN_COMPLETE_STEP_ID),
1062
+ data: undefinedToNull(result.data)
1063
+ }
1064
+ ];
1065
+ return {
1066
+ type: "steps-found",
1067
+ ctx: result.ctx,
1068
+ ops: result.ops,
1069
+ steps
1070
+ };
1071
+ }
1072
+ case "function-rejected": {
1073
+ const isFinal = result.retriable === false;
1074
+ const steps = [
1075
+ ...extras,
1076
+ ...lazyOps,
1077
+ {
1078
+ op: isFinal ? StepOpCode.StepFailed : StepOpCode.StepError,
1079
+ id: hashId(RUN_COMPLETE_STEP_ID),
1080
+ error: result.error
1081
+ }
1082
+ ];
1083
+ return {
1084
+ type: "steps-found",
1085
+ ctx: result.ctx,
1086
+ ops: result.ops,
1087
+ steps
1088
+ };
1089
+ }
1090
+ case "steps-found": return {
1091
+ ...result,
1092
+ steps: [
1093
+ ...result.steps,
1094
+ ...extras,
1095
+ ...lazyOps
1096
+ ]
1097
+ };
1098
+ default:
1099
+ for (const op of lazyOps) this.state.lazyOps.push(op);
1100
+ return result;
1101
+ }
1102
+ }
1002
1103
  createExecutionState() {
1003
1104
  const d = createDeferredPromiseWithStack();
1004
1105
  let checkpointResolve = d.deferred.resolve;
@@ -1021,6 +1122,7 @@ var InngestExecutionEngine = class extends InngestExecution {
1021
1122
  const stepsToFulfill = Object.keys(this.options.stepState).length;
1022
1123
  return {
1023
1124
  stepState: this.options.stepState,
1125
+ priorDefers: this.options.priorDefers ?? {},
1024
1126
  stepsToFulfill,
1025
1127
  steps: /* @__PURE__ */ new Map(),
1026
1128
  loop,
@@ -1035,6 +1137,7 @@ var InngestExecutionEngine = class extends InngestExecution {
1035
1137
  return this.state.remainingStepsToBeSeen.size === 0;
1036
1138
  },
1037
1139
  checkpointingStepBuffer: [],
1140
+ lazyOps: new LazyOps(),
1038
1141
  metadata: /* @__PURE__ */ new Map()
1039
1142
  };
1040
1143
  }
@@ -1042,17 +1145,22 @@ var InngestExecutionEngine = class extends InngestExecution {
1042
1145
  return Object.fromEntries(this.state.steps);
1043
1146
  }
1044
1147
  createFnArg() {
1045
- const step = this.createStepTools();
1148
+ const { step, defer } = this.createStepTools();
1046
1149
  const experimentStepRun = step[experimentStepRunSymbol];
1047
1150
  let fnArg = {
1048
1151
  ...this.options.data,
1049
1152
  step,
1050
- group: createGroupTools({ experimentStepRun })
1153
+ group: createGroupTools({ experimentStepRun }),
1154
+ defer
1051
1155
  };
1156
+ if (this.options.handlerKind === "defer") {
1157
+ delete fnArg.event.data._inngest;
1158
+ for (const event of fnArg.events) delete event.data._inngest;
1159
+ }
1052
1160
  /**
1053
1161
  * Handle use of the `onFailure` option by deserializing the error.
1054
1162
  */
1055
- if (this.options.isFailureHandler) {
1163
+ if (this.options.handlerKind === "failure") {
1056
1164
  const eventData = z.object({ error: jsonErrorSchema }).parse(fnArg.event?.data);
1057
1165
  fnArg = {
1058
1166
  ...fnArg,
@@ -1181,6 +1289,30 @@ var InngestExecutionEngine = class extends InngestExecution {
1181
1289
  };
1182
1290
  const stepHandler = async ({ args, matchOp, opts }) => {
1183
1291
  const opId = matchOp(getStepOptions(args[0]), ...args.slice(1));
1292
+ if (isLazyOp(opts, opId)) {
1293
+ const hashedId$1 = _internals.hashId(opId.id);
1294
+ if (this.state.lazyOps.hasId(hashedId$1)) {
1295
+ this.options.client[internalLoggerSymbol].warn({
1296
+ runId: this.fnArg.runId,
1297
+ id: opId.userland?.id ?? opId.id
1298
+ }, "defer skipped: duplicate ID within run");
1299
+ return;
1300
+ }
1301
+ if (this.state.priorDefers[hashedId$1]) {
1302
+ this.state.lazyOps.markSeen(hashedId$1);
1303
+ return;
1304
+ }
1305
+ this.state.lazyOps.push({
1306
+ id: hashedId$1,
1307
+ op: opId.op,
1308
+ name: opId.name,
1309
+ displayName: opId.displayName ?? opId.id,
1310
+ opts: opId.opts,
1311
+ userland: opId.userland,
1312
+ data: null
1313
+ });
1314
+ return;
1315
+ }
1184
1316
  if (this.state.executingStep)
1185
1317
  /**
1186
1318
  * If a step is found after asynchronous actions during another step's
@@ -1302,7 +1434,75 @@ var InngestExecutionEngine = class extends InngestExecution {
1302
1434
  } else pushStepToReport(step);
1303
1435
  return promise;
1304
1436
  };
1305
- return createStepTools(this.options.client, this, stepHandler);
1437
+ return {
1438
+ step: createStepTools(this.options.client, this, stepHandler),
1439
+ defer: this.buildDefer(stepHandler)
1440
+ };
1441
+ }
1442
+ /**
1443
+ * Build the `defer(idOrOptions, { function, data })` method exposed on
1444
+ * every handler context. Validates `data` against the target function's
1445
+ * schema (if any) and emits a `DeferAdd` opcode that routes to the
1446
+ * target's companion function slug.
1447
+ *
1448
+ * `defer()` is fire-and-forget: a misuse should not derail the user's
1449
+ * handler. Validation failures (wrong function, async schema validator,
1450
+ * schema mismatch) are logged and the call is silently skipped.
1451
+ */
1452
+ buildDefer(stepHandler) {
1453
+ return (idOrOptions, { function: deferFn, data }) => {
1454
+ const log = this.options.client[internalLoggerSymbol];
1455
+ const runId = this.fnArg.runId;
1456
+ try {
1457
+ if (!isDeferredFunction(deferFn)) {
1458
+ log.error({ runId }, "defer skipped: function not created via createDefer");
1459
+ return;
1460
+ }
1461
+ const { schema } = deferFn;
1462
+ const deferFnSlug = deferFn.id(this.options.client.id);
1463
+ let input = data;
1464
+ if (schema) {
1465
+ const result = schema["~standard"].validate(data);
1466
+ if (result instanceof Promise) {
1467
+ log.error({ runId }, "defer() requires a synchronous schema validator. The defer call was skipped.");
1468
+ return;
1469
+ }
1470
+ if (result.issues) {
1471
+ log.error({
1472
+ runId,
1473
+ issues: result.issues
1474
+ }, "defer skipped: schema validation failed");
1475
+ return;
1476
+ }
1477
+ input = result.value ?? data;
1478
+ }
1479
+ stepHandler({
1480
+ args: [idOrOptions, input],
1481
+ matchOp: (stepOptions, inputArg) => ({
1482
+ id: stepOptions.id,
1483
+ mode: StepMode.Sync,
1484
+ op: StepOpCode.DeferAdd,
1485
+ name: stepOptions.name ?? stepOptions.id,
1486
+ displayName: stepOptions.name ?? stepOptions.id,
1487
+ opts: {
1488
+ fn_slug: deferFnSlug,
1489
+ input: inputArg
1490
+ },
1491
+ userland: { id: stepOptions.id }
1492
+ })
1493
+ }).catch((err) => {
1494
+ log.error({
1495
+ runId,
1496
+ err
1497
+ }, "defer skipped: unexpected error");
1498
+ });
1499
+ } catch (err) {
1500
+ log.error({
1501
+ runId,
1502
+ err
1503
+ }, "defer skipped: unexpected error");
1504
+ }
1505
+ };
1306
1506
  }
1307
1507
  /**
1308
1508
  * Applies middleware transformations to a step, resolves ID collisions,
@@ -1384,14 +1584,19 @@ var InngestExecutionEngine = class extends InngestExecution {
1384
1584
  return userlandStep;
1385
1585
  }
1386
1586
  getUserFnToRun() {
1387
- if (!this.options.isFailureHandler) return this.options.fn["fn"];
1388
- if (!this.options.fn["onFailureFn"])
1587
+ switch (this.options.handlerKind) {
1588
+ case "defer": return this.options.fn["fn"];
1589
+ case "failure":
1590
+ if (!this.options.fn["onFailureFn"])
1389
1591
  /**
1390
- * Somehow, we've ended up detecting that this is a failure handler but
1391
- * doesn't have an `onFailure` function. This should never happen.
1392
- */
1393
- throw new Error("Cannot find function `onFailure` handler");
1394
- return this.options.fn["onFailureFn"];
1592
+ * Somehow, we've ended up detecting that this is a failure handler
1593
+ * but doesn't have an `onFailure` function. This should never
1594
+ * happen.
1595
+ */
1596
+ throw new Error("Cannot find function `onFailure` handler");
1597
+ return this.options.fn["onFailureFn"];
1598
+ default: return this.options.fn["fn"];
1599
+ }
1395
1600
  }
1396
1601
  initializeTimer(state) {
1397
1602
  if (!this.options.requestedRunStep) return;