inngest 4.2.5 → 4.3.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 (103) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/api/api.cjs +1 -1
  3. package/api/api.js +1 -1
  4. package/components/Inngest.cjs +1 -1
  5. package/components/Inngest.d.cts +7 -1
  6. package/components/Inngest.d.cts.map +1 -1
  7. package/components/Inngest.d.ts +7 -1
  8. package/components/Inngest.d.ts.map +1 -1
  9. package/components/Inngest.js +1 -1
  10. package/components/InngestCommHandler.cjs +60 -28
  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 +60 -28
  15. package/components/InngestCommHandler.js.map +1 -1
  16. package/components/InngestStepTools.cjs +3 -7
  17. package/components/InngestStepTools.cjs.map +1 -1
  18. package/components/InngestStepTools.d.cts +10 -7
  19. package/components/InngestStepTools.d.cts.map +1 -1
  20. package/components/InngestStepTools.d.ts +10 -7
  21. package/components/InngestStepTools.d.ts.map +1 -1
  22. package/components/InngestStepTools.js +3 -7
  23. package/components/InngestStepTools.js.map +1 -1
  24. package/components/connect/strategies/core/connection.cjs +91 -11
  25. package/components/connect/strategies/core/connection.cjs.map +1 -1
  26. package/components/connect/strategies/core/connection.js +91 -11
  27. package/components/connect/strategies/core/connection.js.map +1 -1
  28. package/components/connect/strategies/core/heartbeat.cjs +2 -1
  29. package/components/connect/strategies/core/heartbeat.cjs.map +1 -1
  30. package/components/connect/strategies/core/heartbeat.js +2 -1
  31. package/components/connect/strategies/core/heartbeat.js.map +1 -1
  32. package/components/connect/strategies/core/requestProcessor.cjs +27 -4
  33. package/components/connect/strategies/core/requestProcessor.cjs.map +1 -1
  34. package/components/connect/strategies/core/requestProcessor.js +27 -4
  35. package/components/connect/strategies/core/requestProcessor.js.map +1 -1
  36. package/components/connect/strategies/core/types.cjs +21 -0
  37. package/components/connect/strategies/core/types.cjs.map +1 -0
  38. package/components/connect/strategies/core/types.js +20 -0
  39. package/components/connect/strategies/core/types.js.map +1 -0
  40. package/components/connect/types.cjs.map +1 -1
  41. package/components/connect/types.d.cts +18 -0
  42. package/components/connect/types.d.cts.map +1 -1
  43. package/components/connect/types.d.ts +18 -0
  44. package/components/connect/types.d.ts.map +1 -1
  45. package/components/connect/types.js.map +1 -1
  46. package/components/execution/engine.cjs +1 -1
  47. package/components/execution/engine.js +1 -1
  48. package/components/execution/otel/middleware.d.cts +12 -6
  49. package/components/execution/otel/middleware.d.cts.map +1 -1
  50. package/components/execution/otel/middleware.d.ts +12 -6
  51. package/components/execution/otel/middleware.d.ts.map +1 -1
  52. package/components/middleware/manager.cjs +1 -1
  53. package/components/middleware/manager.cjs.map +1 -1
  54. package/components/middleware/manager.js +2 -2
  55. package/components/middleware/manager.js.map +1 -1
  56. package/components/middleware/utils.cjs +4 -3
  57. package/components/middleware/utils.cjs.map +1 -1
  58. package/components/middleware/utils.js +4 -3
  59. package/components/middleware/utils.js.map +1 -1
  60. package/components/realtime/types.d.cts +4 -4
  61. package/components/realtime/types.d.cts.map +1 -1
  62. package/components/realtime/types.d.ts +4 -4
  63. package/components/realtime/types.d.ts.map +1 -1
  64. package/helpers/consts.cjs +1 -0
  65. package/helpers/consts.cjs.map +1 -1
  66. package/helpers/consts.d.cts +1 -0
  67. package/helpers/consts.d.cts.map +1 -1
  68. package/helpers/consts.d.ts +1 -0
  69. package/helpers/consts.d.ts.map +1 -1
  70. package/helpers/consts.js +1 -0
  71. package/helpers/consts.js.map +1 -1
  72. package/helpers/env.cjs +2 -1
  73. package/helpers/env.cjs.map +1 -1
  74. package/helpers/env.js +2 -1
  75. package/helpers/env.js.map +1 -1
  76. package/helpers/net.cjs +3 -2
  77. package/helpers/net.cjs.map +1 -1
  78. package/helpers/net.js +3 -2
  79. package/helpers/net.js.map +1 -1
  80. package/helpers/strings.cjs +15 -5
  81. package/helpers/strings.cjs.map +1 -1
  82. package/helpers/strings.d.cts.map +1 -1
  83. package/helpers/strings.d.ts.map +1 -1
  84. package/helpers/strings.js +15 -6
  85. package/helpers/strings.js.map +1 -1
  86. package/helpers/temporal.cjs +2 -0
  87. package/helpers/temporal.d.ts +3 -0
  88. package/helpers/temporal.d.ts.map +1 -1
  89. package/helpers/temporal.js +1 -1
  90. package/package.json +1 -1
  91. package/react.d.cts.map +1 -1
  92. package/types.cjs.map +1 -1
  93. package/types.d.cts +5 -4
  94. package/types.d.cts.map +1 -1
  95. package/types.d.ts +5 -4
  96. package/types.d.ts.map +1 -1
  97. package/types.js.map +1 -1
  98. package/version.cjs +1 -1
  99. package/version.cjs.map +1 -1
  100. package/version.d.cts +1 -1
  101. package/version.d.ts +1 -1
  102. package/version.js +1 -1
  103. package/version.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"InngestStepTools.js","names":["wrappedMatchOp: MatchOpFn<T>","opts: HashedOp[\"opts\"]","matchOpts: { timeout: string; if?: string }","msTimeStr: string","Temporal.isTemporalDuration","Temporal.getISOString","opts: {\n payload: MinimalEventPayload;\n function_id: string;\n timeout?: string;\n }","stepFetch","headers: Record<string, string>","step: GenericStepTools","group: GroupTools"],"sources":["../../src/components/InngestStepTools.ts"],"sourcesContent":["import { type AiAdapter, models } from \"@inngest/ai\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { z } from \"zod/v3\";\n\nimport type { InngestApi } from \"../api/api.ts\";\nimport type { Jsonify } from \"../helpers/jsonify.ts\";\nimport { timeStr } from \"../helpers/strings.ts\";\nimport * as Temporal from \"../helpers/temporal.ts\";\nimport type {\n ExclusiveKeys,\n ParametersExceptFirst,\n SendEventPayload,\n} from \"../helpers/types.ts\";\nimport {\n type ApplyAllMiddlewareTransforms,\n type Context,\n type EventPayload,\n type HashedOp,\n type InvocationResult,\n type InvokeTargetFunctionDefinition,\n type MinimalEventPayload,\n type OutgoingOp,\n type SendEventOutput,\n StepMode,\n StepOpCode,\n type StepOptions,\n type StepOptionsOrId,\n type TriggerEventFromFunction,\n} from \"../types.ts\";\nimport { getAsyncCtx, getAsyncCtxSync } from \"./execution/als.ts\";\nimport type { InngestExecution } from \"./execution/InngestExecution.ts\";\nimport { fetch as stepFetch } from \"./Fetch.ts\";\nimport {\n type ClientOptionsFromInngest,\n type GetFunctionOutputRaw,\n type GetStepTools,\n type Inngest,\n internalLoggerSymbol,\n} from \"./Inngest.ts\";\nimport { InngestFunction } from \"./InngestFunction.ts\";\nimport { InngestFunctionReference } from \"./InngestFunctionReference.ts\";\nimport type { GroupTools } from \"./InngestGroupTools.ts\";\nimport {\n type MetadataBuilder,\n type MetadataStepTool,\n metadataSymbol,\n UnscopedMetadataBuilder,\n} from \"./InngestMetadata.ts\";\nimport type { Middleware } from \"./middleware/index.ts\";\nimport { NonRetriableError } from \"./NonRetriableError.ts\";\nimport type { Realtime } from \"./realtime/types.ts\";\nimport type { EventType } from \"./triggers/triggers.ts\";\n\n/**\n * Middleware context for a step, created during step registration.\n *\n * Uses a \"deferred handler\" pattern: the `wrapStep` middleware chain starts\n * during discovery (so middleware can inject its own steps), but the real\n * handler isn't known until after the memoization lookup. `setActualHandler`\n * bridges the gap — the chain blocks on a deferred promise that is resolved\n * once `executeStep` determines the real result.\n */\nexport interface StepMiddlewareContext {\n /**\n * Sets the handler that the middleware pipeline will eventually call.\n * Called after memoization lookup to set either:\n * - A handler returning memoized data, OR\n * - A handler executing the step fresh\n */\n setActualHandler: (handler: () => Promise<unknown>) => void;\n\n /**\n * Step info after middleware transformations. The `options.id` may differ\n * from the original if middleware modified it via `transformStepInput`.\n */\n stepInfo: Middleware.StepInfo;\n\n /**\n * The middleware pipeline entry point. Call this to execute the step\n * through all middleware transformations.\n */\n wrappedHandler: () => Promise<unknown>;\n}\n\nexport interface FoundStep extends HashedOp {\n hashedId: string;\n fn?: (...args: unknown[]) => unknown;\n rawArgs: unknown[];\n\n /**\n * A boolean representing whether the step has been fulfilled, either\n * resolving or rejecting the `Promise` returned to userland code.\n *\n * Note that this is distinct from {@link hasStepState}, which instead tracks\n * whether the step has been given some state from the Executor. State from\n * the Executor could be data other than a resolution or rejection, such as\n * inputs.\n */\n fulfilled: boolean;\n\n /**\n * A boolean representing whether the step has been given some state from the\n * Executor. State from the Executor could be data other than a resolution or\n * rejection, such as inputs.\n *\n * This is distinct from {@link fulfilled}, which instead tracks whether the\n * step has been fulfilled, either resolving or rejecting the `Promise`\n * returned to userland code.\n */\n hasStepState: boolean;\n\n handled: boolean;\n\n /**\n * The promise that has been returned to userland code for this step.\n */\n promise: Promise<unknown>;\n\n /**\n * Returns a boolean representing whether or not the step was handled on this\n * invocation.\n */\n handle: () => boolean;\n\n // TODO This is used to track the input we want for this step. Might be\n // present in ctx from Executor.\n input?: unknown;\n\n /**\n * Middleware context for this step. Holds the `wrapStep` chain entry point\n * and the deferred handler setter used by `executeStep`.\n */\n middleware: StepMiddlewareContext;\n\n /**\n * For new steps where wrappedHandler is called during discovery,\n * this holds the resolve/reject to be called when the step's data is\n * memoized. Resolved with server-transformed data (post-wrapStepHandler),\n * which unblocks wrapStep's `next()`.\n *\n * Is undefined when any of the following is true:\n * - The step is fulfilled\n * - The step has no handler (`step.sleep`, `step.waitForSignal`, etc.)\n */\n memoizationDeferred?: {\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n };\n\n /**\n * For new steps where `wrappedHandler` is called during discovery, this holds\n * the promise for the wrapStep-transformed result. In checkpointing mode,\n * handle() reuses this promise to avoid a duplicate wrapStep call.\n */\n transformedResultPromise?: Promise<unknown>;\n\n /**\n * Optional metadata updates attached to this step, carried through from\n * the OutgoingOp so that checkpoint payloads include metadata.\n */\n metadata?: OutgoingOp[\"metadata\"];\n}\n\nexport type MatchOpFn<\n T extends (...args: unknown[]) => Promise<unknown> = (\n ...args: unknown[]\n ) => Promise<unknown>,\n> = (\n stepOptions: StepOptions,\n /**\n * Arguments passed by the user.\n */\n ...args: ParametersExceptFirst<T>\n) => Omit<HashedOp, \"data\" | \"error\">;\n\nexport type StepHandler = (info: {\n matchOp: MatchOpFn;\n opts?: StepToolOptions;\n args: [StepOptionsOrId, ...unknown[]];\n}) => Promise<unknown>;\n\nexport interface StepToolOptions<\n T extends (...args: unknown[]) => Promise<unknown> = (\n ...args: unknown[]\n ) => Promise<unknown>,\n> {\n /**\n * Optionally, we can also provide a function that will be called when\n * Inngest tells us to run this operation.\n *\n * If this function is defined, the first time the tool is used it will\n * report the desired operation (including options) to the Inngest. Inngest\n * will then call back to the function to tell it to run the step and then\n * retrieve data.\n *\n * We do this in order to allow functionality such as per-step retries; this\n * gives the SDK the opportunity to tell Inngest what it wants to do before\n * it does it.\n *\n * This function is passed the arguments passed by the user. It will be run\n * when we receive an operation matching this one that does not contain a\n * `data` property.\n */\n fn?: (...args: [Context.Any, ...Parameters<T>]) => unknown;\n}\n\nexport const getStepOptions = (options: StepOptionsOrId): StepOptions => {\n if (typeof options === \"string\") {\n return { id: options };\n }\n\n return options;\n};\n\n/**\n * Suffix used to namespace steps that are automatically indexed.\n */\nexport const STEP_INDEXING_SUFFIX = \":\";\n\n/**\n * Create a new set of step function tools ready to be used in a step function.\n * This function should be run and a fresh set of tools provided every time a\n * function is run.\n *\n * An op stack (function state) is passed in as well as some mutable properties\n * that the tools can use to submit a new op.\n */\n/**\n * Merge client-level and function-level middleware into a single array type\n * for use with ApplyAllMiddlewareTransforms etc.\n */\ntype MergedMiddleware<\n TClient extends Inngest.Any,\n TFnMiddleware extends Middleware.Class[] | undefined,\n> = [\n ...(ClientOptionsFromInngest<TClient>[\"middleware\"] extends Middleware.Class[]\n ? ClientOptionsFromInngest<TClient>[\"middleware\"]\n : []),\n ...(TFnMiddleware extends Middleware.Class[] ? TFnMiddleware : []),\n];\n\nexport const createStepTools = <\n TClient extends Inngest.Any,\n TFnMiddleware extends Middleware.Class[] | undefined = undefined,\n>(\n client: TClient,\n execution: InngestExecution,\n stepHandler: StepHandler,\n) => {\n /**\n * A local helper used to create tools that can be used to submit an op.\n *\n * When using this function, a generic type should be provided which is the\n * function signature exposed to the user.\n */\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n const createTool = <T extends (...args: any[]) => Promise<unknown>>(\n /**\n * A function that returns an ID for this op. This is used to ensure that\n * the op stack is correctly filled, submitted, and retrieved with the same\n * ID.\n *\n * It is passed the arguments passed by the user.\n *\n * Most simple tools will likely only need to define this.\n */\n matchOp: MatchOpFn<T>,\n opts?: StepToolOptions<T>,\n ): T => {\n const wrappedMatchOp: MatchOpFn<T> = (stepOptions, ...rest) => {\n const op = matchOp(stepOptions, ...rest);\n\n const alsCtx = getAsyncCtxSync()?.execution;\n\n if (alsCtx?.insideExperimentSelect) {\n throw new NonRetriableError(\n \"Step tools (step.run, step.sleep, etc.) cannot be called inside \" +\n \"an experiment select() callback. Move step calls into variant \" +\n \"callbacks instead.\",\n );\n }\n\n // Explicit option takes precedence, then check ALS context\n const parallelMode = stepOptions.parallelMode ?? alsCtx?.parallelMode;\n\n if (parallelMode) {\n op.opts = { ...op.opts, parallelMode };\n }\n\n // Propagate experiment context to variant sub-steps\n const experimentContext = alsCtx?.experimentContext;\n if (experimentContext) {\n op.opts = { ...op.opts, ...experimentContext };\n }\n\n // Track that a step tool was invoked inside a variant callback\n const tracker = alsCtx?.experimentStepTracker;\n if (tracker) {\n tracker.found = true;\n }\n\n return op;\n };\n\n return (async (...args: Parameters<T>): Promise<unknown> => {\n const parsedArgs = args as unknown as [StepOptionsOrId, ...unknown[]];\n return stepHandler({ args: parsedArgs, matchOp: wrappedMatchOp, opts });\n }) as T;\n };\n\n /**\n * Create a new step run tool that can be used to run a step function using\n * `step.run()` as a shim.\n */\n const createStepRun = (\n /**\n * The sub-type of this step tool, exposed via `opts.type` when the op is\n * reported.\n */\n type?: string,\n ) => {\n return createTool<\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n <TFn extends (...args: any[]) => unknown>(\n idOrOptions: StepOptionsOrId,\n\n /**\n * The function to run when this step is executed. Can be synchronous or\n * asynchronous.\n *\n * The return value of this function will be the return value of this\n * call to `run`, meaning you can return and reason about return data\n * for next steps.\n */\n fn: TFn,\n\n /**\n * Optional input to pass to the function. If this is specified, Inngest\n * will keep track of the input for this step and be able to display it\n * in the UI.\n */\n ...input: Parameters<TFn>\n ) => Promise<\n ApplyAllMiddlewareTransforms<\n MergedMiddleware<TClient, TFnMiddleware>,\n TFn extends (...args: Parameters<TFn>) => Promise<infer U>\n ? Awaited<U extends void ? null : U>\n : ReturnType<TFn> extends void\n ? null\n : ReturnType<TFn>\n >\n >\n >(\n ({ id, name }, _fn, ...input) => {\n const opts: HashedOp[\"opts\"] = {\n ...(input.length ? { input } : {}),\n ...(type ? { type } : {}),\n };\n\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n name: id,\n displayName: name ?? id,\n ...(Object.keys(opts).length ? { opts } : {}),\n userland: { id },\n };\n },\n {\n fn: (_, __, fn, ...input) => fn(...input),\n },\n );\n };\n\n /**\n * Creates a metadata builder wrapper for step.metadata(\"id\").\n * Uses MetadataBuilder for config accumulation, but wraps .update() in tools.run() for memoization.\n */\n const createStepMetadataWrapper = (\n memoizationId: string,\n builder?: UnscopedMetadataBuilder,\n ) => {\n if (!client[\"experimentalMetadataEnabled\"]) {\n throw new Error(\n 'step.metadata() is experimental. Enable it by adding metadataMiddleware() from \"inngest/experimental\" to your client middleware.',\n );\n }\n const withBuilder = (next: UnscopedMetadataBuilder) =>\n createStepMetadataWrapper(memoizationId, next);\n\n if (!builder) {\n builder = new UnscopedMetadataBuilder(client).run();\n }\n\n return {\n run: (runId?: string) => withBuilder(builder.run(runId)),\n step: (stepId: string, index?: number) =>\n withBuilder(builder.step(stepId, index)),\n attempt: (attemptIndex: number) =>\n withBuilder(builder.attempt(attemptIndex)),\n span: (spanId: string) => withBuilder(builder.span(spanId)),\n update: async (\n values: Record<string, unknown>,\n kind = \"default\",\n ): Promise<void> => {\n await tools.run(memoizationId, async () => {\n await builder.update(values, kind);\n });\n },\n\n do: async (\n fn: (builder: MetadataBuilder) => Promise<void>,\n ): Promise<void> => {\n await tools.run(memoizationId, async () => {\n await fn(builder);\n });\n },\n };\n };\n\n /**\n * Define the set of tools the user has access to for their step functions.\n *\n * Each key is the function name and is expected to run `createTool` and pass\n * a generic type for that function as it will appear in the user's code.\n */\n const tools = {\n /**\n * Send one or many events to Inngest. Should always be used in place of\n * `inngest.send()` to ensure that the event send is successfully retried\n * and not sent multiple times due to memoisation.\n *\n * @example\n * ```ts\n * await step.sendEvent(\"emit-user-creation\", {\n * name: \"app/user.created\",\n * data: { id: 123 },\n * });\n *\n * await step.sendEvent(\"emit-user-updates\", [\n * {\n * name: \"app/user.created\",\n * data: { id: 123 },\n * },\n * {\n * name: \"app/user.feed.created\",\n * data: { id: 123 },\n * },\n * ]);\n * ```\n *\n * Returns a promise that will resolve once the event has been sent.\n */\n sendEvent: createTool<\n (\n idOrOptions: StepOptionsOrId,\n payload: SendEventPayload,\n ) => Promise<SendEventOutput<ClientOptionsFromInngest<TClient>>>\n >(\n ({ id, name }) => {\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n name: \"sendEvent\",\n displayName: name ?? id,\n opts: {\n type: \"step.sendEvent\",\n },\n userland: { id },\n };\n },\n {\n fn: (_ctx, _idOrOptions, payload) => {\n const fn = execution[\"options\"][\"fn\"];\n return client[\"_send\"]({\n payload,\n headers: execution[\"options\"][\"headers\"],\n fnMiddleware: fn.opts.middleware ?? [],\n fn,\n });\n },\n },\n ),\n\n /**\n * EXPERIMENTAL: This API is not yet stable and may change in the future\n * without a major version bump.\n *\n * Wait for a particular signal to be received before continuing. When the\n * signal is received, its data will be returned.\n */\n waitForSignal: createTool<\n <TData>(\n idOrOptions: StepOptionsOrId,\n opts: WaitForSignalOpts,\n ) => Promise<{ signal: string; data: Jsonify<TData> } | null>\n >(({ id, name }, opts) => {\n // TODO Should support Temporal.DurationLike, Temporal.InstantLike,\n // Temporal.ZonedDateTimeLike\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.WaitForSignal,\n name: opts.signal,\n displayName: name ?? id,\n opts: {\n signal: opts.signal,\n timeout: timeStr(opts.timeout),\n conflict: opts.onConflict,\n },\n userland: { id },\n };\n }),\n\n /**\n * Step-level functionality related to realtime features.\n *\n * Unlike client-level realtime methods (`inngest.realtime.*`), these tools\n * will be their own durable steps when run. If you wish to use realtime\n * features outside of a step, make sure to use the client-level methods\n * instead.\n */\n realtime: {\n /**\n * Publish a realtime message as a durable step. Memoized and will not\n * re-fire on retry, unlike client-level `inngest.realtime.publish()`.\n *\n * Uses topic accessors: `step.realtime.publish(\"id\", chat.status, data)`\n */\n publish: createTool<\n <TData>(\n idOrOptions: StepOptionsOrId,\n topicRef: Realtime.TopicRef<TData>,\n data: TData,\n ) => Promise<TData>\n >(\n ({ id, name }) => {\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n displayName: name ?? id,\n opts: {\n type: \"step.realtime.publish\",\n },\n userland: { id },\n };\n },\n {\n fn: async (ctx, _idOrOptions, topicRef, data) => {\n const topicConfig = topicRef.config;\n if (topicConfig && \"schema\" in topicConfig && topicConfig.schema) {\n const result =\n await topicConfig.schema[\"~standard\"].validate(data);\n if (result.issues) {\n throw new Error(\n `Schema validation failed for topic \"${topicRef.topic}\"`,\n );\n }\n }\n\n const res = await client[\"inngestApi\"].publish(\n {\n topics: [topicRef.topic],\n channel: topicRef.channel,\n runId: ctx.runId,\n },\n data,\n );\n\n if (!res.ok) {\n throw new Error(\n `Failed to publish to realtime: ${res.error?.error || \"Unknown error\"}`,\n );\n }\n\n return data;\n },\n },\n ),\n },\n\n /**\n * Send a Signal to Inngest.\n */\n sendSignal: createTool<\n (\n idOrOptions: StepOptionsOrId,\n opts: SendSignalOpts,\n ) => Promise<InngestApi.SendSignalResponse>\n >(\n ({ id, name }, opts) => {\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n name: \"sendSignal\",\n displayName: name ?? id,\n opts: {\n type: \"step.sendSignal\",\n signal: opts.signal,\n },\n userland: { id },\n };\n },\n {\n fn: (_ctx, _idOrOptions, opts) => {\n return client[\"_sendSignal\"]({\n signal: opts.signal,\n data: opts.data,\n headers: execution[\"options\"][\"headers\"],\n });\n },\n },\n ),\n\n /**\n * Wait for a particular event to be received before continuing. When the\n * event is received, it will be returned.\n *\n * You can also provide options to control the particular event that is\n * received, for example to ensure that a user ID matches between two\n * events, or to only wait a maximum amount of time before giving up and\n * returning `null` instead of any event data.\n */\n waitForEvent: createTool<\n <\n TOpts extends {\n /**\n * The event to wait for.\n */\n event:\n | string\n // biome-ignore lint/suspicious/noExplicitAny: Allow any schema\n | EventType<string, any>;\n\n /**\n * The step function will wait for the event for a maximum of this\n * time, at which point the signal will be returned as `null` instead\n * of any signal data.\n *\n * The time to wait can be specified using a `number` of milliseconds,\n * an `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or\n * `\"2.5d\"`, or a `Date` object.\n *\n * {@link https://npm.im/ms}\n */\n timeout: number | string | Date;\n } & ExclusiveKeys<{ match?: string; if?: string }, \"match\", \"if\">,\n >(\n idOrOptions: StepOptionsOrId,\n opts: TOpts,\n ) => Promise<WaitForEventResult<TOpts>>\n >(\n (\n { id, name },\n\n /**\n * Options to control the event we're waiting for.\n */\n opts,\n ) => {\n const matchOpts: { timeout: string; if?: string } = {\n timeout: timeStr(typeof opts === \"string\" ? opts : opts.timeout),\n };\n\n if (typeof opts !== \"string\") {\n if (opts?.match) {\n matchOpts.if = `event.${opts.match} == async.${opts.match}`;\n } else if (opts?.if) {\n matchOpts.if = opts.if;\n }\n }\n\n // Extract event name from string or EventType object\n const eventName =\n typeof opts.event === \"string\" ? opts.event : opts.event.name;\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.WaitForEvent,\n name: eventName,\n opts: matchOpts,\n displayName: name ?? id,\n userland: { id },\n };\n },\n ),\n\n /**\n * Use this tool to run business logic. Each call to `run` will be retried\n * individually, meaning you can compose complex workflows that safely\n * retry dependent asynchronous actions.\n *\n * The function you pass to `run` will be called only when this \"step\" is to\n * be executed and can be synchronous or asynchronous.\n *\n * In either case, the return value of the function will be the return value\n * of the `run` tool, meaning you can return and reason about return data\n * for next steps.\n */\n run: createStepRun(),\n\n /**\n * AI tooling for running AI models and other AI-related tasks.\n */\n ai: {\n /**\n * Use this tool to have Inngest make your AI calls. Useful for agentic workflows.\n *\n * Input is also tracked for this tool, meaning you can pass input to the\n * function and it will be displayed and editable in the UI.\n */\n infer: createTool<\n <TAdapter extends AiAdapter>(\n idOrOptions: StepOptionsOrId,\n options: AiInferOpts<TAdapter>,\n ) => Promise<AiAdapter.Output<TAdapter>>\n >(({ id, name }, options) => {\n // eslint-disable-next-line\n const { model, body, ...rest } = options;\n\n const modelCopy = { ...model };\n\n // Allow the model to mutate options and body for this call\n options.model.onCall?.(modelCopy, options.body);\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.AiGateway,\n displayName: name ?? id,\n opts: {\n type: \"step.ai.infer\",\n url: modelCopy.url,\n headers: modelCopy.headers,\n auth_key: modelCopy.authKey,\n format: modelCopy.format,\n // eslint-disable-next-line\n body,\n // eslint-disable-next-line\n ...rest,\n },\n userland: { id },\n };\n }),\n\n /**\n * Use this tool to wrap AI models and other AI-related tasks. Each call\n * to `wrap` will be retried individually, meaning you can compose complex\n * workflows that safely retry dependent asynchronous actions.\n *\n * Input is also tracked for this tool, meaning you can pass input to the\n * function and it will be displayed and editable in the UI.\n */\n wrap: createStepRun(\"step.ai.wrap\"),\n\n /**\n * Models for AI inference and other AI-related tasks.\n */\n models: {\n ...models,\n },\n },\n\n /**\n * Wait a specified amount of time before continuing.\n *\n * The time to wait can be specified using a `number` of milliseconds or an\n * `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or `\"2.5d\"`.\n *\n * {@link https://npm.im/ms}\n *\n * To wait until a particular date, use `sleepUntil` instead.\n */\n sleep: createTool<\n (\n idOrOptions: StepOptionsOrId,\n\n /**\n * The amount of time to wait before continuing.\n */\n time: number | string | Temporal.DurationLike,\n ) => Promise<void>\n >(({ id, name }, time) => {\n /**\n * The presence of this operation in the returned stack indicates that the\n * sleep is over and we should continue execution.\n */\n const msTimeStr: string = timeStr(\n Temporal.isTemporalDuration(time)\n ? time.total({ unit: \"milliseconds\" })\n : (time as number | string),\n );\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.Sleep,\n name: msTimeStr,\n displayName: name ?? id,\n userland: { id },\n };\n }),\n\n /**\n * Wait until a particular date before continuing by passing a `Date`.\n *\n * To wait for a particular amount of time from now, always use `sleep`\n * instead.\n */\n sleepUntil: createTool<\n (\n idOrOptions: StepOptionsOrId,\n\n /**\n * The date to wait until before continuing.\n */\n time: Date | string | Temporal.InstantLike | Temporal.ZonedDateTimeLike,\n ) => Promise<void>\n >(({ id, name }, time) => {\n try {\n const iso = Temporal.getISOString(time);\n\n /**\n * The presence of this operation in the returned stack indicates that the\n * sleep is over and we should continue execution.\n */\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.Sleep,\n name: iso,\n displayName: name ?? id,\n userland: { id },\n };\n } catch (err) {\n /**\n * If we're here, it's because the date is invalid. We'll throw a custom\n * error here to standardise this response.\n */\n client[internalLoggerSymbol].warn(\n { err },\n \"Invalid `Date`, date string, `Temporal.Instant`, or `Temporal.ZonedDateTime` passed to sleepUntil\",\n );\n\n throw new Error(\n `Invalid \\`Date\\`, date string, \\`Temporal.Instant\\`, or \\`Temporal.ZonedDateTime\\` passed to sleepUntil: ${\n time\n }`,\n );\n }\n }),\n\n /**\n * Invoke a passed Inngest `function` with the given `data`. Returns the\n * result of the returned value of the function or `null` if the function\n * does not return a value.\n */\n invoke: createTool<\n <TFunction extends InvokeTargetFunctionDefinition>(\n idOrOptions: StepOptionsOrId,\n opts: InvocationOpts<TFunction>,\n ) => InvocationResult<\n ApplyAllMiddlewareTransforms<\n MergedMiddleware<TClient, TFnMiddleware>,\n GetFunctionOutputRaw<TFunction>,\n \"functionOutputTransform\"\n >\n >\n >(({ id, name }, invokeOpts) => {\n // Create a discriminated union to operate on based on the input types\n // available for this tool.\n const optsSchema = invokePayloadSchema.extend({\n timeout: z.union([z.number(), z.string(), z.date()]).optional(),\n });\n\n const parsedFnOpts = optsSchema\n .extend({\n _type: z.literal(\"fnInstance\").optional().default(\"fnInstance\"),\n function: z.instanceof(InngestFunction),\n })\n .or(\n optsSchema.extend({\n _type: z.literal(\"refInstance\").optional().default(\"refInstance\"),\n function: z.instanceof(InngestFunctionReference),\n }),\n )\n .safeParse(invokeOpts);\n\n if (!parsedFnOpts.success) {\n throw new Error(\n `Invalid invocation options passed to invoke; must include a function instance or referenceFunction().`,\n );\n }\n\n const { _type, function: fn, data, v, timeout } = parsedFnOpts.data;\n const payload = { data, v } satisfies MinimalEventPayload;\n const opts: {\n payload: MinimalEventPayload;\n function_id: string;\n timeout?: string;\n } = {\n payload,\n function_id: \"\",\n timeout: typeof timeout === \"undefined\" ? undefined : timeStr(timeout),\n };\n\n switch (_type) {\n case \"fnInstance\":\n opts.function_id = fn.id(fn[\"client\"].id);\n break;\n\n case \"refInstance\":\n opts.function_id = [fn.opts.appId || client.id, fn.opts.functionId]\n .filter(Boolean)\n .join(\"-\");\n break;\n }\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.InvokeFunction,\n displayName: name ?? id,\n opts,\n userland: { id },\n };\n }),\n\n /**\n * `step.fetch` is a Fetch-API-compatible function that can be used to make\n * any HTTP code durable if it's called within an Inngest function.\n *\n * It will gracefully fall back to the global `fetch` if called outside of\n * this context, and a custom fallback can be set using the `config` method.\n */\n fetch: stepFetch,\n };\n\n // NOTE: This should be moved into the above object definition under the key\n // \"metadata\" when metadata is made non-experimental.\n (tools as unknown as ExperimentalStepTools)[metadataSymbol] = (\n memoizationId: string,\n ): MetadataStepTool => createStepMetadataWrapper(memoizationId);\n\n // Attach a step.run variant with opts.type = \"group.experiment\" for use by\n // group.experiment(). The symbol keeps it off the public `step` surface.\n (tools as unknown as ExperimentStepTools)[experimentStepRunSymbol] =\n createStepRun(\"group.experiment\");\n\n // Add an uptyped gateway\n (tools as unknown as InternalStepTools)[gatewaySymbol] = createTool(\n ({ id, name }, input, init) => {\n const url = input instanceof Request ? input.url : input.toString();\n\n const headers: Record<string, string> = {};\n if (input instanceof Request) {\n input.headers.forEach((value, key) => {\n headers[key] = value;\n });\n } else if (init?.headers) {\n const h = new Headers(init.headers);\n h.forEach((value, key) => {\n headers[key] = value;\n });\n }\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.Gateway,\n displayName: name ?? id,\n opts: {\n url,\n method: init?.method ?? \"GET\",\n headers,\n body: init?.body,\n },\n userland: { id },\n };\n },\n );\n\n return tools;\n};\n\n/**\n * A generic set of step tools, without typing information about the client used\n * to create them.\n */\nexport type GenericStepTools = GetStepTools<Inngest.Any>;\n\nexport const gatewaySymbol = Symbol.for(\"inngest.step.gateway\");\n\nexport type InternalStepTools = GetStepTools<Inngest.Any> & {\n [gatewaySymbol]: (\n idOrOptions: StepOptionsOrId,\n ...args: Parameters<typeof fetch>\n ) => Promise<{\n status_code: number;\n headers: Record<string, string>;\n body: string;\n }>;\n};\n\nexport type ExperimentalStepTools = GetStepTools<Inngest.Any> & {\n [metadataSymbol]: (memoizationId: string) => MetadataStepTool;\n};\n\nexport const experimentStepRunSymbol = Symbol.for(\"inngest.group.experiment\");\n\nexport type ExperimentStepTools = GetStepTools<Inngest.Any> & {\n [experimentStepRunSymbol]: (\n idOrOptions: StepOptionsOrId,\n fn: () => unknown,\n ) => Promise<unknown>;\n};\n\n/**\n * A generic set of step tools that can be used without typing information about\n * the client used to create them.\n *\n * These tools use AsyncLocalStorage to track the context in which they are\n * used, and will throw an error if used outside of an Inngest context.\n *\n * The intention of these high-level tools is to allow usage of Inngest step\n * tools within API endpoints, though they can still be used within regular\n * Inngest functions as well.\n */\nexport const step: GenericStepTools = {\n // TODO Support `step.fetch` (this is already kinda half way deferred)\n fetch: null as unknown as GenericStepTools[\"fetch\"],\n ai: {\n infer: (...args) =>\n getDeferredStepTooling().then((tools) => tools.ai.infer(...args)),\n wrap: (...args) =>\n getDeferredStepTooling().then((tools) => tools.ai.wrap(...args)),\n models: {\n ...models,\n },\n },\n invoke: (...args) =>\n getDeferredStepTooling().then((tools) => tools.invoke(...args)),\n run: (...args) =>\n getDeferredStepTooling().then((tools) => tools.run(...args)),\n sendEvent: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sendEvent(...args)),\n sendSignal: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sendSignal(...args)),\n sleep: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sleep(...args)),\n sleepUntil: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sleepUntil(...args)),\n waitForEvent: (...args) =>\n getDeferredStepTooling().then((tools) => tools.waitForEvent(...args)),\n waitForSignal: (...args) =>\n getDeferredStepTooling().then((tools) => tools.waitForSignal(...args)),\n realtime: {\n publish: (...args) =>\n getDeferredStepTooling().then((tools) => tools.realtime.publish(...args)),\n },\n};\n\n/**\n * An internal function used to retrieve or create step tooling for the current\n * execution context.\n *\n * Note that this requires an existing context to create the step tooling;\n * something must declare the Inngest execution context before this can be used.\n */\nconst getDeferredStepTooling = async (): Promise<GenericStepTools> => {\n const ctx = await getAsyncCtx();\n if (!ctx) {\n throw new Error(\n \"`step` tools can only be used within Inngest function executions; no context was found\",\n );\n }\n\n if (!ctx.app) {\n throw new Error(\n \"`step` tools can only be used within Inngest function executions; no Inngest client was found in the execution context\",\n );\n }\n\n if (!ctx.execution) {\n throw new Error(\n \"`step` tools can only be used within Inngest function executions; no execution context was found\",\n );\n }\n\n // If we're here, we're in the context of a function execution already and\n // we can return the existing step tooling.\n return ctx.execution.ctx.step;\n};\n\nconst getDeferredGroupTooling = async (): Promise<GroupTools> => {\n const ctx = await getAsyncCtx();\n if (!ctx) {\n throw new Error(\n \"`group` tools can only be used within Inngest function executions; no context was found\",\n );\n }\n\n if (!ctx.execution) {\n throw new Error(\n \"`group` tools can only be used within Inngest function executions; no execution context was found\",\n );\n }\n\n return ctx.execution.ctx.group;\n};\n\n/**\n * A deferred proxy for `group` tools that delegates through ALS context.\n *\n * @public\n */\nexport const group: GroupTools = {\n parallel: (...args) =>\n getDeferredGroupTooling().then((tools) => tools.parallel(...args)),\n experiment: (...args) =>\n getDeferredGroupTooling().then((tools) => tools.experiment(...args)),\n};\n\n/**\n * The event payload portion of the options for `step.invoke()`. This does not\n * include non-payload options like `timeout` or the function to invoke.\n */\nexport const invokePayloadSchema = z.object({\n data: z.record(z.any()).optional(),\n v: z.string().optional(),\n});\n\ntype InvocationTargetOpts<TFunction extends InvokeTargetFunctionDefinition> = {\n function: TFunction;\n};\n\ntype InvocationOpts<TFunction extends InvokeTargetFunctionDefinition> =\n InvocationTargetOpts<TFunction> &\n Omit<TriggerEventFromFunction<TFunction>, \"id\"> & {\n /**\n * The step function will wait for the invocation to finish for a maximum\n * of this time, at which point the retured promise will be rejected\n * instead of resolved with the output of the invoked function.\n *\n * Note that the invoked function will continue to run even if this step\n * times out.\n *\n * The time to wait can be specified using a `number` of milliseconds, an\n * `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or `\"2.5d\"`,\n * or a `Date` object.\n *\n * {@link https://npm.im/ms}\n */\n timeout?: number | string | Date;\n };\n\n/**\n * A set of parameters given to a `sendSignal` call.\n */\ntype SendSignalOpts = {\n /**\n * The signal to send.\n */\n signal: string;\n\n /**\n * The data to send with the signal.\n */\n data?: unknown;\n};\n\n/**\n * A set of parameters given to a `waitForSignal` call.\n */\ntype WaitForSignalOpts = {\n /**\n * The signal to wait for.\n */\n signal: string;\n\n /**\n * The step function will wait for the signal for a maximum of this time, at\n * which point the signal will be returned as `null` instead of any signal\n * data.\n *\n * The time to wait can be specified using a `number` of milliseconds, an\n * `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or `\"2.5d\"`, or\n * a `Date` object.\n *\n * {@link https://npm.im/ms}\n */\n timeout: number | string | Date;\n\n /**\n * When this `step.waitForSignal()` call is made, choose whether an existing\n * wait for the same signal should be replaced, or whether this run should\n * fail.\n *\n * `\"replace\"` will replace any existing wait with this one, and the existing\n * wait will remain pending until it reaches its timeout.\n *\n * `\"fail\"` will cause this run to fail if there is already a wait for the\n * same signal.\n */\n onConflict: \"replace\" | \"fail\";\n};\n\n/**\n * Computes the return type for `waitForEvent` based on the options provided.\n *\n * Handles three cases:\n * 1. `event: EventType<TName, TSchema>` - extracts name and data from EventType\n * 2. `event: string` with `schema` - uses string as name and schema for data\n * 3. `event: string` without schema - uses string as name with untyped data\n */\ntype WaitForEventResult<TOpts> =\n // Case 1: event is an EventType with a schema\n TOpts extends {\n event: EventType<\n infer TName extends string,\n StandardSchemaV1<infer TData extends Record<string, unknown>>\n >;\n }\n ? { name: TName; data: TData; id: string; ts: number; v?: string } | null\n : // Case 2: event is an EventType without a schema\n TOpts extends {\n event: EventType<infer TName extends string, undefined>;\n }\n ? {\n name: TName;\n // biome-ignore lint/suspicious/noExplicitAny: fallback for untyped events\n data: Record<string, any>;\n id: string;\n ts: number;\n v?: string;\n } | null\n : // Case 3: event is a string with schema (spread EventType)\n TOpts extends {\n event: infer TName extends string;\n schema: StandardSchemaV1<\n infer TData extends Record<string, unknown>\n >;\n }\n ? {\n name: TName;\n data: TData;\n id: string;\n ts: number;\n v?: string;\n } | null\n : // Case 4: event is just a string\n TOpts extends { event: infer TName extends string }\n ? {\n name: TName;\n // biome-ignore lint/suspicious/noExplicitAny: fallback for untyped events\n data: Record<string, any>;\n id: string;\n ts: number;\n v?: string;\n } | null\n : EventPayload | null;\n\n/**\n * Options for `step.ai.infer()`.\n */\ntype AiInferOpts<TModel extends AiAdapter> = {\n /**\n * The model to use for the inference. Create a model by importing from\n * `\"inngest\"` or by using `step.ai.models.*`.\n *\n * @example Import `openai()`\n * ```ts\n * import { openai } from \"inngest\";\n *\n * const model = openai({ model: \"gpt-4\" });\n * ```\n *\n * @example Use a model from `step.ai.models`\n * ```ts\n * async ({ step }) => {\n * const model = step.ai.models.openai({ model: \"gpt-4\" });\n * }\n * ```\n */\n model: TModel;\n\n /**\n * The input to pass to the model.\n */\n body: AiAdapter.Input<TModel>;\n};\n"],"mappings":";;;;;;;;;;;;;;AA8MA,MAAa,kBAAkB,YAA0C;AACvE,KAAI,OAAO,YAAY,SACrB,QAAO,EAAE,IAAI,SAAS;AAGxB,QAAO;;;;;AAMT,MAAa,uBAAuB;AAwBpC,MAAa,mBAIX,QACA,WACA,gBACG;;;;;;;CAQH,MAAM,cAUJ,SACA,SACM;EACN,MAAMA,kBAAgC,aAAa,GAAG,SAAS;GAC7D,MAAM,KAAK,QAAQ,aAAa,GAAG,KAAK;GAExC,MAAM,SAAS,iBAAiB,EAAE;AAElC,OAAI,QAAQ,uBACV,OAAM,IAAI,kBACR,mJAGD;GAIH,MAAM,eAAe,YAAY,gBAAgB,QAAQ;AAEzD,OAAI,aACF,IAAG,OAAO;IAAE,GAAG,GAAG;IAAM;IAAc;GAIxC,MAAM,oBAAoB,QAAQ;AAClC,OAAI,kBACF,IAAG,OAAO;IAAE,GAAG,GAAG;IAAM,GAAG;IAAmB;GAIhD,MAAM,UAAU,QAAQ;AACxB,OAAI,QACF,SAAQ,QAAQ;AAGlB,UAAO;;AAGT,UAAQ,OAAO,GAAG,SAA0C;AAE1D,UAAO,YAAY;IADA;IACoB,SAAS;IAAgB;IAAM,CAAC;;;;;;;CAQ3E,MAAM,iBAKJ,SACG;AACH,SAAO,YAgCJ,EAAE,IAAI,QAAQ,KAAK,GAAG,UAAU;GAC/B,MAAMC,OAAyB;IAC7B,GAAI,MAAM,SAAS,EAAE,OAAO,GAAG,EAAE;IACjC,GAAI,OAAO,EAAE,MAAM,GAAG,EAAE;IACzB;AAED,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,GAAI,OAAO,KAAK,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,EAAE;IAC5C,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,KAAK,GAAG,IAAI,IAAI,GAAG,UAAU,GAAG,GAAG,MAAM,EAC1C,CACF;;;;;;CAOH,MAAM,6BACJ,eACA,YACG;AACH,MAAI,CAAC,OAAO,+BACV,OAAM,IAAI,MACR,qIACD;EAEH,MAAM,eAAe,SACnB,0BAA0B,eAAe,KAAK;AAEhD,MAAI,CAAC,QACH,WAAU,IAAI,wBAAwB,OAAO,CAAC,KAAK;AAGrD,SAAO;GACL,MAAM,UAAmB,YAAY,QAAQ,IAAI,MAAM,CAAC;GACxD,OAAO,QAAgB,UACrB,YAAY,QAAQ,KAAK,QAAQ,MAAM,CAAC;GAC1C,UAAU,iBACR,YAAY,QAAQ,QAAQ,aAAa,CAAC;GAC5C,OAAO,WAAmB,YAAY,QAAQ,KAAK,OAAO,CAAC;GAC3D,QAAQ,OACN,QACA,OAAO,cACW;AAClB,UAAM,MAAM,IAAI,eAAe,YAAY;AACzC,WAAM,QAAQ,OAAO,QAAQ,KAAK;MAClC;;GAGJ,IAAI,OACF,OACkB;AAClB,UAAM,MAAM,IAAI,eAAe,YAAY;AACzC,WAAM,GAAG,QAAQ;MACjB;;GAEL;;;;;;;;CASH,MAAM,QAAQ;EA2BZ,WAAW,YAMR,EAAE,IAAI,WAAW;AAChB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,MAAM,EACJ,MAAM,kBACP;IACD,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,KAAK,MAAM,cAAc,YAAY;GACnC,MAAM,KAAK,UAAU,WAAW;AAChC,UAAO,OAAO,SAAS;IACrB;IACA,SAAS,UAAU,WAAW;IAC9B,cAAc,GAAG,KAAK,cAAc,EAAE;IACtC;IACD,CAAC;KAEL,CACF;EASD,eAAe,YAKZ,EAAE,IAAI,QAAQ,SAAS;AAGxB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM,KAAK;IACX,aAAa,QAAQ;IACrB,MAAM;KACJ,QAAQ,KAAK;KACb,SAAS,QAAQ,KAAK,QAAQ;KAC9B,UAAU,KAAK;KAChB;IACD,UAAU,EAAE,IAAI;IACjB;IACD;EAUF,UAAU,EAOR,SAAS,YAON,EAAE,IAAI,WAAW;AAChB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,aAAa,QAAQ;IACrB,MAAM,EACJ,MAAM,yBACP;IACD,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,IAAI,OAAO,KAAK,cAAc,UAAU,SAAS;GAC/C,MAAM,cAAc,SAAS;AAC7B,OAAI,eAAe,YAAY,eAAe,YAAY,QAGxD;SADE,MAAM,YAAY,OAAO,aAAa,SAAS,KAAK,EAC3C,OACT,OAAM,IAAI,MACR,uCAAuC,SAAS,MAAM,GACvD;;GAIL,MAAM,MAAM,MAAM,OAAO,cAAc,QACrC;IACE,QAAQ,CAAC,SAAS,MAAM;IACxB,SAAS,SAAS;IAClB,OAAO,IAAI;IACZ,EACD,KACD;AAED,OAAI,CAAC,IAAI,GACP,OAAM,IAAI,MACR,kCAAkC,IAAI,OAAO,SAAS,kBACvD;AAGH,UAAO;KAEV,CACF,EACF;EAKD,YAAY,YAMT,EAAE,IAAI,QAAQ,SAAS;AACtB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,MAAM;KACJ,MAAM;KACN,QAAQ,KAAK;KACd;IACD,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,KAAK,MAAM,cAAc,SAAS;AAChC,UAAO,OAAO,eAAe;IAC3B,QAAQ,KAAK;IACb,MAAM,KAAK;IACX,SAAS,UAAU,WAAW;IAC/B,CAAC;KAEL,CACF;EAWD,cAAc,YA8BV,EAAE,IAAI,QAKN,SACG;GACH,MAAMC,YAA8C,EAClD,SAAS,QAAQ,OAAO,SAAS,WAAW,OAAO,KAAK,QAAQ,EACjE;AAED,OAAI,OAAO,SAAS,UAClB;QAAI,MAAM,MACR,WAAU,KAAK,SAAS,KAAK,MAAM,YAAY,KAAK;aAC3C,MAAM,GACf,WAAU,KAAK,KAAK;;GAKxB,MAAM,YACJ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAK,MAAM;AAE3D,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,MAAM;IACN,aAAa,QAAQ;IACrB,UAAU,EAAE,IAAI;IACjB;IAEJ;EAcD,KAAK,eAAe;EAKpB,IAAI;GAOF,OAAO,YAKJ,EAAE,IAAI,QAAQ,YAAY;IAE3B,MAAM,EAAE,OAAO,MAAM,GAAG,SAAS;IAEjC,MAAM,YAAY,EAAE,GAAG,OAAO;AAG9B,YAAQ,MAAM,SAAS,WAAW,QAAQ,KAAK;AAE/C,WAAO;KACL;KACA,MAAM,SAAS;KACf,IAAI,WAAW;KACf,aAAa,QAAQ;KACrB,MAAM;MACJ,MAAM;MACN,KAAK,UAAU;MACf,SAAS,UAAU;MACnB,UAAU,UAAU;MACpB,QAAQ,UAAU;MAElB;MAEA,GAAG;MACJ;KACD,UAAU,EAAE,IAAI;KACjB;KACD;GAUF,MAAM,cAAc,eAAe;GAKnC,QAAQ,EACN,GAAG,QACJ;GACF;EAYD,OAAO,YASJ,EAAE,IAAI,QAAQ,SAAS;;;;;GAKxB,MAAMC,YAAoB,QACxBC,mBAA4B,KAAK,GAC7B,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC,GACnC,KACN;AAED,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,UAAU,EAAE,IAAI;IACjB;IACD;EAQF,YAAY,YAST,EAAE,IAAI,QAAQ,SAAS;AACxB,OAAI;IACF,MAAM,MAAMC,aAAsB,KAAK;;;;;AAMvC,WAAO;KACL;KACA,MAAM,SAAS;KACf,IAAI,WAAW;KACf,MAAM;KACN,aAAa,QAAQ;KACrB,UAAU,EAAE,IAAI;KACjB;YACM,KAAK;;;;;AAKZ,WAAO,sBAAsB,KAC3B,EAAE,KAAK,EACP,oGACD;AAED,UAAM,IAAI,MACR,4GACE,OAEH;;IAEH;EAOF,QAAQ,YAWL,EAAE,IAAI,QAAQ,eAAe;GAG9B,MAAM,aAAa,oBAAoB,OAAO,EAC5C,SAAS,EAAE,MAAM;IAAC,EAAE,QAAQ;IAAE,EAAE,QAAQ;IAAE,EAAE,MAAM;IAAC,CAAC,CAAC,UAAU,EAChE,CAAC;GAEF,MAAM,eAAe,WAClB,OAAO;IACN,OAAO,EAAE,QAAQ,aAAa,CAAC,UAAU,CAAC,QAAQ,aAAa;IAC/D,UAAU,EAAE,WAAW,gBAAgB;IACxC,CAAC,CACD,GACC,WAAW,OAAO;IAChB,OAAO,EAAE,QAAQ,cAAc,CAAC,UAAU,CAAC,QAAQ,cAAc;IACjE,UAAU,EAAE,WAAW,yBAAyB;IACjD,CAAC,CACH,CACA,UAAU,WAAW;AAExB,OAAI,CAAC,aAAa,QAChB,OAAM,IAAI,MACR,wGACD;GAGH,MAAM,EAAE,OAAO,UAAU,IAAI,MAAM,GAAG,YAAY,aAAa;GAE/D,MAAMC,OAIF;IACF,SANc;KAAE;KAAM;KAAG;IAOzB,aAAa;IACb,SAAS,OAAO,YAAY,cAAc,SAAY,QAAQ,QAAQ;IACvE;AAED,WAAQ,OAAR;IACE,KAAK;AACH,UAAK,cAAc,GAAG,GAAG,GAAG,UAAU,GAAG;AACzC;IAEF,KAAK;AACH,UAAK,cAAc,CAAC,GAAG,KAAK,SAAS,OAAO,IAAI,GAAG,KAAK,WAAW,CAChE,OAAO,QAAQ,CACf,KAAK,IAAI;AACZ;;AAGJ,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,aAAa,QAAQ;IACrB;IACA,UAAU,EAAE,IAAI;IACjB;IACD;EASKC;EACR;AAID,CAAC,MAA2C,mBAC1C,kBACqB,0BAA0B,cAAc;AAI/D,CAAC,MAAyC,2BACxC,cAAc,mBAAmB;AAGnC,CAAC,MAAuC,iBAAiB,YACtD,EAAE,IAAI,QAAQ,OAAO,SAAS;EAC7B,MAAM,MAAM,iBAAiB,UAAU,MAAM,MAAM,MAAM,UAAU;EAEnE,MAAMC,UAAkC,EAAE;AAC1C,MAAI,iBAAiB,QACnB,OAAM,QAAQ,SAAS,OAAO,QAAQ;AACpC,WAAQ,OAAO;IACf;WACO,MAAM,QAEf,CADU,IAAI,QAAQ,KAAK,QAAQ,CACjC,SAAS,OAAO,QAAQ;AACxB,WAAQ,OAAO;IACf;AAGJ,SAAO;GACL;GACA,MAAM,SAAS;GACf,IAAI,WAAW;GACf,aAAa,QAAQ;GACrB,MAAM;IACJ;IACA,QAAQ,MAAM,UAAU;IACxB;IACA,MAAM,MAAM;IACb;GACD,UAAU,EAAE,IAAI;GACjB;GAEJ;AAED,QAAO;;AAST,MAAa,gBAAgB,OAAO,IAAI,uBAAuB;AAiB/D,MAAa,0BAA0B,OAAO,IAAI,2BAA2B;;;;;;;;;;;;AAoB7E,MAAaC,OAAyB;CAEpC,OAAO;CACP,IAAI;EACF,QAAQ,GAAG,SACT,wBAAwB,CAAC,MAAM,UAAU,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;EACnE,OAAO,GAAG,SACR,wBAAwB,CAAC,MAAM,UAAU,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;EAClE,QAAQ,EACN,GAAG,QACJ;EACF;CACD,SAAS,GAAG,SACV,wBAAwB,CAAC,MAAM,UAAU,MAAM,OAAO,GAAG,KAAK,CAAC;CACjE,MAAM,GAAG,SACP,wBAAwB,CAAC,MAAM,UAAU,MAAM,IAAI,GAAG,KAAK,CAAC;CAC9D,YAAY,GAAG,SACb,wBAAwB,CAAC,MAAM,UAAU,MAAM,UAAU,GAAG,KAAK,CAAC;CACpE,aAAa,GAAG,SACd,wBAAwB,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;CACrE,QAAQ,GAAG,SACT,wBAAwB,CAAC,MAAM,UAAU,MAAM,MAAM,GAAG,KAAK,CAAC;CAChE,aAAa,GAAG,SACd,wBAAwB,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;CACrE,eAAe,GAAG,SAChB,wBAAwB,CAAC,MAAM,UAAU,MAAM,aAAa,GAAG,KAAK,CAAC;CACvE,gBAAgB,GAAG,SACjB,wBAAwB,CAAC,MAAM,UAAU,MAAM,cAAc,GAAG,KAAK,CAAC;CACxE,UAAU,EACR,UAAU,GAAG,SACX,wBAAwB,CAAC,MAAM,UAAU,MAAM,SAAS,QAAQ,GAAG,KAAK,CAAC,EAC5E;CACF;;;;;;;;AASD,MAAM,yBAAyB,YAAuC;CACpE,MAAM,MAAM,MAAM,aAAa;AAC/B,KAAI,CAAC,IACH,OAAM,IAAI,MACR,yFACD;AAGH,KAAI,CAAC,IAAI,IACP,OAAM,IAAI,MACR,yHACD;AAGH,KAAI,CAAC,IAAI,UACP,OAAM,IAAI,MACR,mGACD;AAKH,QAAO,IAAI,UAAU,IAAI;;AAG3B,MAAM,0BAA0B,YAAiC;CAC/D,MAAM,MAAM,MAAM,aAAa;AAC/B,KAAI,CAAC,IACH,OAAM,IAAI,MACR,0FACD;AAGH,KAAI,CAAC,IAAI,UACP,OAAM,IAAI,MACR,oGACD;AAGH,QAAO,IAAI,UAAU,IAAI;;;;;;;AAQ3B,MAAaC,QAAoB;CAC/B,WAAW,GAAG,SACZ,yBAAyB,CAAC,MAAM,UAAU,MAAM,SAAS,GAAG,KAAK,CAAC;CACpE,aAAa,GAAG,SACd,yBAAyB,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;CACvE;;;;;AAMD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,UAAU;CAClC,GAAG,EAAE,QAAQ,CAAC,UAAU;CACzB,CAAC"}
1
+ {"version":3,"file":"InngestStepTools.js","names":["wrappedMatchOp: MatchOpFn<T>","opts: HashedOp[\"opts\"]","matchOpts: { timeout: string; if?: string }","msTimeStr: string","Temporal.getISOString","v","Temporal.isTemporalDuration","Temporal.isTemporalInstant","Temporal.isTemporalZonedDateTime","opts: {\n payload: MinimalEventPayload;\n function_id: string;\n timeout?: string;\n }","stepFetch","headers: Record<string, string>","step: GenericStepTools","group: GroupTools"],"sources":["../../src/components/InngestStepTools.ts"],"sourcesContent":["import { type AiAdapter, models } from \"@inngest/ai\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { z } from \"zod/v3\";\n\nimport type { InngestApi } from \"../api/api.ts\";\nimport type { Jsonify } from \"../helpers/jsonify.ts\";\nimport { timeStr } from \"../helpers/strings.ts\";\nimport * as Temporal from \"../helpers/temporal.ts\";\nimport type {\n ExclusiveKeys,\n ParametersExceptFirst,\n SendEventPayload,\n} from \"../helpers/types.ts\";\nimport {\n type ApplyAllMiddlewareTransforms,\n type Context,\n type EventPayload,\n type HashedOp,\n type InvocationResult,\n type InvokeTargetFunctionDefinition,\n type MinimalEventPayload,\n type OutgoingOp,\n type SendEventOutput,\n StepMode,\n StepOpCode,\n type StepOptions,\n type StepOptionsOrId,\n type TriggerEventFromFunction,\n} from \"../types.ts\";\nimport { getAsyncCtx, getAsyncCtxSync } from \"./execution/als.ts\";\nimport type { InngestExecution } from \"./execution/InngestExecution.ts\";\nimport { fetch as stepFetch } from \"./Fetch.ts\";\nimport {\n type ClientOptionsFromInngest,\n type GetFunctionOutputRaw,\n type GetStepTools,\n type Inngest,\n internalLoggerSymbol,\n} from \"./Inngest.ts\";\nimport { InngestFunction } from \"./InngestFunction.ts\";\nimport { InngestFunctionReference } from \"./InngestFunctionReference.ts\";\nimport type { GroupTools } from \"./InngestGroupTools.ts\";\nimport {\n type MetadataBuilder,\n type MetadataStepTool,\n metadataSymbol,\n UnscopedMetadataBuilder,\n} from \"./InngestMetadata.ts\";\nimport type { Middleware } from \"./middleware/index.ts\";\nimport { NonRetriableError } from \"./NonRetriableError.ts\";\nimport type { Realtime } from \"./realtime/types.ts\";\nimport type { EventType } from \"./triggers/triggers.ts\";\n\n/**\n * Middleware context for a step, created during step registration.\n *\n * Uses a \"deferred handler\" pattern: the `wrapStep` middleware chain starts\n * during discovery (so middleware can inject its own steps), but the real\n * handler isn't known until after the memoization lookup. `setActualHandler`\n * bridges the gap — the chain blocks on a deferred promise that is resolved\n * once `executeStep` determines the real result.\n */\nexport interface StepMiddlewareContext {\n /**\n * Sets the handler that the middleware pipeline will eventually call.\n * Called after memoization lookup to set either:\n * - A handler returning memoized data, OR\n * - A handler executing the step fresh\n */\n setActualHandler: (handler: () => Promise<unknown>) => void;\n\n /**\n * Step info after middleware transformations. The `options.id` may differ\n * from the original if middleware modified it via `transformStepInput`.\n */\n stepInfo: Middleware.StepInfo;\n\n /**\n * The middleware pipeline entry point. Call this to execute the step\n * through all middleware transformations.\n */\n wrappedHandler: () => Promise<unknown>;\n}\n\nexport interface FoundStep extends HashedOp {\n hashedId: string;\n fn?: (...args: unknown[]) => unknown;\n rawArgs: unknown[];\n\n /**\n * A boolean representing whether the step has been fulfilled, either\n * resolving or rejecting the `Promise` returned to userland code.\n *\n * Note that this is distinct from {@link hasStepState}, which instead tracks\n * whether the step has been given some state from the Executor. State from\n * the Executor could be data other than a resolution or rejection, such as\n * inputs.\n */\n fulfilled: boolean;\n\n /**\n * A boolean representing whether the step has been given some state from the\n * Executor. State from the Executor could be data other than a resolution or\n * rejection, such as inputs.\n *\n * This is distinct from {@link fulfilled}, which instead tracks whether the\n * step has been fulfilled, either resolving or rejecting the `Promise`\n * returned to userland code.\n */\n hasStepState: boolean;\n\n handled: boolean;\n\n /**\n * The promise that has been returned to userland code for this step.\n */\n promise: Promise<unknown>;\n\n /**\n * Returns a boolean representing whether or not the step was handled on this\n * invocation.\n */\n handle: () => boolean;\n\n // TODO This is used to track the input we want for this step. Might be\n // present in ctx from Executor.\n input?: unknown;\n\n /**\n * Middleware context for this step. Holds the `wrapStep` chain entry point\n * and the deferred handler setter used by `executeStep`.\n */\n middleware: StepMiddlewareContext;\n\n /**\n * For new steps where wrappedHandler is called during discovery,\n * this holds the resolve/reject to be called when the step's data is\n * memoized. Resolved with server-transformed data (post-wrapStepHandler),\n * which unblocks wrapStep's `next()`.\n *\n * Is undefined when any of the following is true:\n * - The step is fulfilled\n * - The step has no handler (`step.sleep`, `step.waitForSignal`, etc.)\n */\n memoizationDeferred?: {\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n };\n\n /**\n * For new steps where `wrappedHandler` is called during discovery, this holds\n * the promise for the wrapStep-transformed result. In checkpointing mode,\n * handle() reuses this promise to avoid a duplicate wrapStep call.\n */\n transformedResultPromise?: Promise<unknown>;\n\n /**\n * Optional metadata updates attached to this step, carried through from\n * the OutgoingOp so that checkpoint payloads include metadata.\n */\n metadata?: OutgoingOp[\"metadata\"];\n}\n\nexport type MatchOpFn<\n T extends (...args: unknown[]) => Promise<unknown> = (\n ...args: unknown[]\n ) => Promise<unknown>,\n> = (\n stepOptions: StepOptions,\n /**\n * Arguments passed by the user.\n */\n ...args: ParametersExceptFirst<T>\n) => Omit<HashedOp, \"data\" | \"error\">;\n\nexport type StepHandler = (info: {\n matchOp: MatchOpFn;\n opts?: StepToolOptions;\n args: [StepOptionsOrId, ...unknown[]];\n}) => Promise<unknown>;\n\nexport interface StepToolOptions<\n T extends (...args: unknown[]) => Promise<unknown> = (\n ...args: unknown[]\n ) => Promise<unknown>,\n> {\n /**\n * Optionally, we can also provide a function that will be called when\n * Inngest tells us to run this operation.\n *\n * If this function is defined, the first time the tool is used it will\n * report the desired operation (including options) to the Inngest. Inngest\n * will then call back to the function to tell it to run the step and then\n * retrieve data.\n *\n * We do this in order to allow functionality such as per-step retries; this\n * gives the SDK the opportunity to tell Inngest what it wants to do before\n * it does it.\n *\n * This function is passed the arguments passed by the user. It will be run\n * when we receive an operation matching this one that does not contain a\n * `data` property.\n */\n fn?: (...args: [Context.Any, ...Parameters<T>]) => unknown;\n}\n\nexport const getStepOptions = (options: StepOptionsOrId): StepOptions => {\n if (typeof options === \"string\") {\n return { id: options };\n }\n\n return options;\n};\n\n/**\n * Suffix used to namespace steps that are automatically indexed.\n */\nexport const STEP_INDEXING_SUFFIX = \":\";\n\n/**\n * Create a new set of step function tools ready to be used in a step function.\n * This function should be run and a fresh set of tools provided every time a\n * function is run.\n *\n * An op stack (function state) is passed in as well as some mutable properties\n * that the tools can use to submit a new op.\n */\n/**\n * Merge client-level and function-level middleware into a single array type\n * for use with ApplyAllMiddlewareTransforms etc.\n */\ntype MergedMiddleware<\n TClient extends Inngest.Any,\n TFnMiddleware extends Middleware.Class[] | undefined,\n> = [\n ...(ClientOptionsFromInngest<TClient>[\"middleware\"] extends Middleware.Class[]\n ? ClientOptionsFromInngest<TClient>[\"middleware\"]\n : []),\n ...(TFnMiddleware extends Middleware.Class[] ? TFnMiddleware : []),\n];\n\nexport const createStepTools = <\n TClient extends Inngest.Any,\n TFnMiddleware extends Middleware.Class[] | undefined = undefined,\n>(\n client: TClient,\n execution: InngestExecution,\n stepHandler: StepHandler,\n) => {\n /**\n * A local helper used to create tools that can be used to submit an op.\n *\n * When using this function, a generic type should be provided which is the\n * function signature exposed to the user.\n */\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n const createTool = <T extends (...args: any[]) => Promise<unknown>>(\n /**\n * A function that returns an ID for this op. This is used to ensure that\n * the op stack is correctly filled, submitted, and retrieved with the same\n * ID.\n *\n * It is passed the arguments passed by the user.\n *\n * Most simple tools will likely only need to define this.\n */\n matchOp: MatchOpFn<T>,\n opts?: StepToolOptions<T>,\n ): T => {\n const wrappedMatchOp: MatchOpFn<T> = (stepOptions, ...rest) => {\n const op = matchOp(stepOptions, ...rest);\n\n const alsCtx = getAsyncCtxSync()?.execution;\n\n if (alsCtx?.insideExperimentSelect) {\n throw new NonRetriableError(\n \"Step tools (step.run, step.sleep, etc.) cannot be called inside \" +\n \"an experiment select() callback. Move step calls into variant \" +\n \"callbacks instead.\",\n );\n }\n\n // Explicit option takes precedence, then check ALS context\n const parallelMode = stepOptions.parallelMode ?? alsCtx?.parallelMode;\n\n if (parallelMode) {\n op.opts = { ...op.opts, parallelMode };\n }\n\n // Propagate experiment context to variant sub-steps\n const experimentContext = alsCtx?.experimentContext;\n if (experimentContext) {\n op.opts = { ...op.opts, ...experimentContext };\n }\n\n // Track that a step tool was invoked inside a variant callback\n const tracker = alsCtx?.experimentStepTracker;\n if (tracker) {\n tracker.found = true;\n }\n\n return op;\n };\n\n return (async (...args: Parameters<T>): Promise<unknown> => {\n const parsedArgs = args as unknown as [StepOptionsOrId, ...unknown[]];\n return stepHandler({ args: parsedArgs, matchOp: wrappedMatchOp, opts });\n }) as T;\n };\n\n /**\n * Create a new step run tool that can be used to run a step function using\n * `step.run()` as a shim.\n */\n const createStepRun = (\n /**\n * The sub-type of this step tool, exposed via `opts.type` when the op is\n * reported.\n */\n type?: string,\n ) => {\n return createTool<\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n <TFn extends (...args: any[]) => unknown>(\n idOrOptions: StepOptionsOrId,\n\n /**\n * The function to run when this step is executed. Can be synchronous or\n * asynchronous.\n *\n * The return value of this function will be the return value of this\n * call to `run`, meaning you can return and reason about return data\n * for next steps.\n */\n fn: TFn,\n\n /**\n * Optional input to pass to the function. If this is specified, Inngest\n * will keep track of the input for this step and be able to display it\n * in the UI.\n */\n ...input: Parameters<TFn>\n ) => Promise<\n ApplyAllMiddlewareTransforms<\n MergedMiddleware<TClient, TFnMiddleware>,\n TFn extends (...args: Parameters<TFn>) => Promise<infer U>\n ? Awaited<U extends void ? null : U>\n : ReturnType<TFn> extends void\n ? null\n : ReturnType<TFn>\n >\n >\n >(\n ({ id, name }, _fn, ...input) => {\n const opts: HashedOp[\"opts\"] = {\n ...(input.length ? { input } : {}),\n ...(type ? { type } : {}),\n };\n\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n name: id,\n displayName: name ?? id,\n ...(Object.keys(opts).length ? { opts } : {}),\n userland: { id },\n };\n },\n {\n fn: (_, __, fn, ...input) => fn(...input),\n },\n );\n };\n\n /**\n * Creates a metadata builder wrapper for step.metadata(\"id\").\n * Uses MetadataBuilder for config accumulation, but wraps .update() in tools.run() for memoization.\n */\n const createStepMetadataWrapper = (\n memoizationId: string,\n builder?: UnscopedMetadataBuilder,\n ) => {\n if (!client[\"experimentalMetadataEnabled\"]) {\n throw new Error(\n 'step.metadata() is experimental. Enable it by adding metadataMiddleware() from \"inngest/experimental\" to your client middleware.',\n );\n }\n const withBuilder = (next: UnscopedMetadataBuilder) =>\n createStepMetadataWrapper(memoizationId, next);\n\n if (!builder) {\n builder = new UnscopedMetadataBuilder(client).run();\n }\n\n return {\n run: (runId?: string) => withBuilder(builder.run(runId)),\n step: (stepId: string, index?: number) =>\n withBuilder(builder.step(stepId, index)),\n attempt: (attemptIndex: number) =>\n withBuilder(builder.attempt(attemptIndex)),\n span: (spanId: string) => withBuilder(builder.span(spanId)),\n update: async (\n values: Record<string, unknown>,\n kind = \"default\",\n ): Promise<void> => {\n await tools.run(memoizationId, async () => {\n await builder.update(values, kind);\n });\n },\n\n do: async (\n fn: (builder: MetadataBuilder) => Promise<void>,\n ): Promise<void> => {\n await tools.run(memoizationId, async () => {\n await fn(builder);\n });\n },\n };\n };\n\n /**\n * Define the set of tools the user has access to for their step functions.\n *\n * Each key is the function name and is expected to run `createTool` and pass\n * a generic type for that function as it will appear in the user's code.\n */\n const tools = {\n /**\n * Send one or many events to Inngest. Should always be used in place of\n * `inngest.send()` to ensure that the event send is successfully retried\n * and not sent multiple times due to memoisation.\n *\n * @example\n * ```ts\n * await step.sendEvent(\"emit-user-creation\", {\n * name: \"app/user.created\",\n * data: { id: 123 },\n * });\n *\n * await step.sendEvent(\"emit-user-updates\", [\n * {\n * name: \"app/user.created\",\n * data: { id: 123 },\n * },\n * {\n * name: \"app/user.feed.created\",\n * data: { id: 123 },\n * },\n * ]);\n * ```\n *\n * Returns a promise that will resolve once the event has been sent.\n */\n sendEvent: createTool<\n (\n idOrOptions: StepOptionsOrId,\n payload: SendEventPayload,\n ) => Promise<SendEventOutput<ClientOptionsFromInngest<TClient>>>\n >(\n ({ id, name }) => {\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n name: \"sendEvent\",\n displayName: name ?? id,\n opts: {\n type: \"step.sendEvent\",\n },\n userland: { id },\n };\n },\n {\n fn: (_ctx, _idOrOptions, payload) => {\n const fn = execution[\"options\"][\"fn\"];\n return client[\"_send\"]({\n payload,\n headers: execution[\"options\"][\"headers\"],\n fnMiddleware: fn.opts.middleware ?? [],\n fn,\n });\n },\n },\n ),\n\n /**\n * EXPERIMENTAL: This API is not yet stable and may change in the future\n * without a major version bump.\n *\n * Wait for a particular signal to be received before continuing. When the\n * signal is received, its data will be returned.\n */\n waitForSignal: createTool<\n <TData>(\n idOrOptions: StepOptionsOrId,\n opts: WaitForSignalOpts,\n ) => Promise<{ signal: string; data: Jsonify<TData> } | null>\n >(({ id, name }, opts) => {\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.WaitForSignal,\n name: opts.signal,\n displayName: name ?? id,\n opts: {\n signal: opts.signal,\n timeout: timeStr(opts.timeout),\n conflict: opts.onConflict,\n },\n userland: { id },\n };\n }),\n\n /**\n * Step-level functionality related to realtime features.\n *\n * Unlike client-level realtime methods (`inngest.realtime.*`), these tools\n * will be their own durable steps when run. If you wish to use realtime\n * features outside of a step, make sure to use the client-level methods\n * instead.\n */\n realtime: {\n /**\n * Publish a realtime message as a durable step. Memoized and will not\n * re-fire on retry, unlike client-level `inngest.realtime.publish()`.\n *\n * Uses topic accessors: `step.realtime.publish(\"id\", chat.status, data)`\n */\n publish: createTool<\n <TData>(\n idOrOptions: StepOptionsOrId,\n topicRef: Realtime.TopicRef<TData>,\n data: TData,\n ) => Promise<TData>\n >(\n ({ id, name }) => {\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n displayName: name ?? id,\n opts: {\n type: \"step.realtime.publish\",\n },\n userland: { id },\n };\n },\n {\n fn: async (ctx, _idOrOptions, topicRef, data) => {\n const topicConfig = topicRef.config;\n if (topicConfig && \"schema\" in topicConfig && topicConfig.schema) {\n const result =\n await topicConfig.schema[\"~standard\"].validate(data);\n if (result.issues) {\n throw new Error(\n `Schema validation failed for topic \"${topicRef.topic}\"`,\n );\n }\n }\n\n const res = await client[\"inngestApi\"].publish(\n {\n topics: [topicRef.topic],\n channel: topicRef.channel,\n runId: ctx.runId,\n },\n data,\n );\n\n if (!res.ok) {\n throw new Error(\n `Failed to publish to realtime: ${res.error?.error || \"Unknown error\"}`,\n );\n }\n\n return data;\n },\n },\n ),\n },\n\n /**\n * Send a Signal to Inngest.\n */\n sendSignal: createTool<\n (\n idOrOptions: StepOptionsOrId,\n opts: SendSignalOpts,\n ) => Promise<InngestApi.SendSignalResponse>\n >(\n ({ id, name }, opts) => {\n return {\n id,\n mode: StepMode.Sync,\n op: StepOpCode.StepPlanned,\n name: \"sendSignal\",\n displayName: name ?? id,\n opts: {\n type: \"step.sendSignal\",\n signal: opts.signal,\n },\n userland: { id },\n };\n },\n {\n fn: (_ctx, _idOrOptions, opts) => {\n return client[\"_sendSignal\"]({\n signal: opts.signal,\n data: opts.data,\n headers: execution[\"options\"][\"headers\"],\n });\n },\n },\n ),\n\n /**\n * Wait for a particular event to be received before continuing. When the\n * event is received, it will be returned.\n *\n * You can also provide options to control the particular event that is\n * received, for example to ensure that a user ID matches between two\n * events, or to only wait a maximum amount of time before giving up and\n * returning `null` instead of any event data.\n */\n waitForEvent: createTool<\n <\n TOpts extends {\n /**\n * The event to wait for.\n */\n event:\n | string\n // biome-ignore lint/suspicious/noExplicitAny: Allow any schema\n | EventType<string, any>;\n\n /**\n * The step function will wait for the event for a maximum of this\n * time, at which point the signal will be returned as `null` instead\n * of any signal data.\n *\n * The time to wait can be specified using a `number` of milliseconds,\n * an `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or\n * `\"2.5d\"`, a `Date`, a `Temporal.Duration` (relative wait), or a\n * `Temporal.Instant` / `Temporal.ZonedDateTime` (absolute deadline).\n *\n * {@link https://npm.im/ms}\n */\n timeout:\n | number\n | string\n | Date\n | Temporal.DurationLike\n | Temporal.InstantLike\n | Temporal.ZonedDateTimeLike;\n } & ExclusiveKeys<{ match?: string; if?: string }, \"match\", \"if\">,\n >(\n idOrOptions: StepOptionsOrId,\n opts: TOpts,\n ) => Promise<WaitForEventResult<TOpts>>\n >(\n (\n { id, name },\n\n /**\n * Options to control the event we're waiting for.\n */\n opts,\n ) => {\n const matchOpts: { timeout: string; if?: string } = {\n timeout: timeStr(typeof opts === \"string\" ? opts : opts.timeout),\n };\n\n if (typeof opts !== \"string\") {\n if (opts?.match) {\n matchOpts.if = `event.${opts.match} == async.${opts.match}`;\n } else if (opts?.if) {\n matchOpts.if = opts.if;\n }\n }\n\n // Extract event name from string or EventType object\n const eventName =\n typeof opts.event === \"string\" ? opts.event : opts.event.name;\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.WaitForEvent,\n name: eventName,\n opts: matchOpts,\n displayName: name ?? id,\n userland: { id },\n };\n },\n ),\n\n /**\n * Use this tool to run business logic. Each call to `run` will be retried\n * individually, meaning you can compose complex workflows that safely\n * retry dependent asynchronous actions.\n *\n * The function you pass to `run` will be called only when this \"step\" is to\n * be executed and can be synchronous or asynchronous.\n *\n * In either case, the return value of the function will be the return value\n * of the `run` tool, meaning you can return and reason about return data\n * for next steps.\n */\n run: createStepRun(),\n\n /**\n * AI tooling for running AI models and other AI-related tasks.\n */\n ai: {\n /**\n * Use this tool to have Inngest make your AI calls. Useful for agentic workflows.\n *\n * Input is also tracked for this tool, meaning you can pass input to the\n * function and it will be displayed and editable in the UI.\n */\n infer: createTool<\n <TAdapter extends AiAdapter>(\n idOrOptions: StepOptionsOrId,\n options: AiInferOpts<TAdapter>,\n ) => Promise<AiAdapter.Output<TAdapter>>\n >(({ id, name }, options) => {\n // eslint-disable-next-line\n const { model, body, ...rest } = options;\n\n const modelCopy = { ...model };\n\n // Allow the model to mutate options and body for this call\n options.model.onCall?.(modelCopy, options.body);\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.AiGateway,\n displayName: name ?? id,\n opts: {\n type: \"step.ai.infer\",\n url: modelCopy.url,\n headers: modelCopy.headers,\n auth_key: modelCopy.authKey,\n format: modelCopy.format,\n // eslint-disable-next-line\n body,\n // eslint-disable-next-line\n ...rest,\n },\n userland: { id },\n };\n }),\n\n /**\n * Use this tool to wrap AI models and other AI-related tasks. Each call\n * to `wrap` will be retried individually, meaning you can compose complex\n * workflows that safely retry dependent asynchronous actions.\n *\n * Input is also tracked for this tool, meaning you can pass input to the\n * function and it will be displayed and editable in the UI.\n */\n wrap: createStepRun(\"step.ai.wrap\"),\n\n /**\n * Models for AI inference and other AI-related tasks.\n */\n models: {\n ...models,\n },\n },\n\n /**\n * Wait a specified amount of time before continuing.\n *\n * The time to wait can be specified using a `number` of milliseconds or an\n * `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or `\"2.5d\"`.\n *\n * {@link https://npm.im/ms}\n *\n * To wait until a particular date, use `sleepUntil` instead.\n */\n sleep: createTool<\n (\n idOrOptions: StepOptionsOrId,\n\n /**\n * The amount of time to wait before continuing.\n */\n time: number | string | Temporal.DurationLike,\n ) => Promise<void>\n >(({ id, name }, time) => {\n /**\n * The presence of this operation in the returned stack indicates that the\n * sleep is over and we should continue execution.\n */\n const msTimeStr: string = timeStr(time);\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.Sleep,\n name: msTimeStr,\n displayName: name ?? id,\n userland: { id },\n };\n }),\n\n /**\n * Wait until a particular date before continuing by passing a `Date`.\n *\n * To wait for a particular amount of time from now, always use `sleep`\n * instead.\n */\n sleepUntil: createTool<\n (\n idOrOptions: StepOptionsOrId,\n\n /**\n * The date to wait until before continuing.\n */\n time: Date | string | Temporal.InstantLike | Temporal.ZonedDateTimeLike,\n ) => Promise<void>\n >(({ id, name }, time) => {\n try {\n const iso = Temporal.getISOString(time);\n\n /**\n * The presence of this operation in the returned stack indicates that the\n * sleep is over and we should continue execution.\n */\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.Sleep,\n name: iso,\n displayName: name ?? id,\n userland: { id },\n };\n } catch (err) {\n /**\n * If we're here, it's because the date is invalid. We'll throw a custom\n * error here to standardise this response.\n */\n client[internalLoggerSymbol].warn(\n { err },\n \"Invalid `Date`, date string, `Temporal.Instant`, or `Temporal.ZonedDateTime` passed to sleepUntil\",\n );\n\n throw new Error(\n `Invalid \\`Date\\`, date string, \\`Temporal.Instant\\`, or \\`Temporal.ZonedDateTime\\` passed to sleepUntil: ${\n time\n }`,\n );\n }\n }),\n\n /**\n * Invoke a passed Inngest `function` with the given `data`. Returns the\n * result of the returned value of the function or `null` if the function\n * does not return a value.\n */\n invoke: createTool<\n <TFunction extends InvokeTargetFunctionDefinition>(\n idOrOptions: StepOptionsOrId,\n opts: InvocationOpts<TFunction>,\n ) => InvocationResult<\n ApplyAllMiddlewareTransforms<\n MergedMiddleware<TClient, TFnMiddleware>,\n GetFunctionOutputRaw<TFunction>,\n \"functionOutputTransform\"\n >\n >\n >(({ id, name }, invokeOpts) => {\n // Create a discriminated union to operate on based on the input types\n // available for this tool.\n const optsSchema = invokePayloadSchema.extend({\n timeout: z\n .custom<\n | number\n | string\n | Date\n | Temporal.DurationLike\n | Temporal.InstantLike\n | Temporal.ZonedDateTimeLike\n >(\n (v) =>\n typeof v === \"number\" ||\n typeof v === \"string\" ||\n v instanceof Date ||\n Temporal.isTemporalDuration(v) ||\n Temporal.isTemporalInstant(v) ||\n Temporal.isTemporalZonedDateTime(v),\n { message: \"Invalid timeout\" },\n )\n .optional(),\n });\n\n const parsedFnOpts = optsSchema\n .extend({\n _type: z.literal(\"fnInstance\").optional().default(\"fnInstance\"),\n function: z.instanceof(InngestFunction),\n })\n .or(\n optsSchema.extend({\n _type: z.literal(\"refInstance\").optional().default(\"refInstance\"),\n function: z.instanceof(InngestFunctionReference),\n }),\n )\n .safeParse(invokeOpts);\n\n if (!parsedFnOpts.success) {\n throw new Error(\n `Invalid invocation options passed to invoke; must include a function instance or referenceFunction().`,\n );\n }\n\n const { _type, function: fn, data, v, timeout } = parsedFnOpts.data;\n const payload = { data, v } satisfies MinimalEventPayload;\n const opts: {\n payload: MinimalEventPayload;\n function_id: string;\n timeout?: string;\n } = {\n payload,\n function_id: \"\",\n timeout: typeof timeout === \"undefined\" ? undefined : timeStr(timeout),\n };\n\n switch (_type) {\n case \"fnInstance\":\n opts.function_id = fn.id(fn[\"client\"].id);\n break;\n\n case \"refInstance\":\n opts.function_id = [fn.opts.appId || client.id, fn.opts.functionId]\n .filter(Boolean)\n .join(\"-\");\n break;\n }\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.InvokeFunction,\n displayName: name ?? id,\n opts,\n userland: { id },\n };\n }),\n\n /**\n * `step.fetch` is a Fetch-API-compatible function that can be used to make\n * any HTTP code durable if it's called within an Inngest function.\n *\n * It will gracefully fall back to the global `fetch` if called outside of\n * this context, and a custom fallback can be set using the `config` method.\n */\n fetch: stepFetch,\n };\n\n // NOTE: This should be moved into the above object definition under the key\n // \"metadata\" when metadata is made non-experimental.\n (tools as unknown as ExperimentalStepTools)[metadataSymbol] = (\n memoizationId: string,\n ): MetadataStepTool => createStepMetadataWrapper(memoizationId);\n\n // Attach a step.run variant with opts.type = \"group.experiment\" for use by\n // group.experiment(). The symbol keeps it off the public `step` surface.\n (tools as unknown as ExperimentStepTools)[experimentStepRunSymbol] =\n createStepRun(\"group.experiment\");\n\n // Add an uptyped gateway\n (tools as unknown as InternalStepTools)[gatewaySymbol] = createTool(\n ({ id, name }, input, init) => {\n const url = input instanceof Request ? input.url : input.toString();\n\n const headers: Record<string, string> = {};\n if (input instanceof Request) {\n input.headers.forEach((value, key) => {\n headers[key] = value;\n });\n } else if (init?.headers) {\n const h = new Headers(init.headers);\n h.forEach((value, key) => {\n headers[key] = value;\n });\n }\n\n return {\n id,\n mode: StepMode.Async,\n op: StepOpCode.Gateway,\n displayName: name ?? id,\n opts: {\n url,\n method: init?.method ?? \"GET\",\n headers,\n body: init?.body,\n },\n userland: { id },\n };\n },\n );\n\n return tools;\n};\n\n/**\n * A generic set of step tools, without typing information about the client used\n * to create them.\n */\nexport type GenericStepTools = GetStepTools<Inngest.Any>;\n\nexport const gatewaySymbol = Symbol.for(\"inngest.step.gateway\");\n\nexport type InternalStepTools = GetStepTools<Inngest.Any> & {\n [gatewaySymbol]: (\n idOrOptions: StepOptionsOrId,\n ...args: Parameters<typeof fetch>\n ) => Promise<{\n status_code: number;\n headers: Record<string, string>;\n body: string;\n }>;\n};\n\nexport type ExperimentalStepTools = GetStepTools<Inngest.Any> & {\n [metadataSymbol]: (memoizationId: string) => MetadataStepTool;\n};\n\nexport const experimentStepRunSymbol = Symbol.for(\"inngest.group.experiment\");\n\nexport type ExperimentStepTools = GetStepTools<Inngest.Any> & {\n [experimentStepRunSymbol]: (\n idOrOptions: StepOptionsOrId,\n fn: () => unknown,\n ) => Promise<unknown>;\n};\n\n/**\n * A generic set of step tools that can be used without typing information about\n * the client used to create them.\n *\n * These tools use AsyncLocalStorage to track the context in which they are\n * used, and will throw an error if used outside of an Inngest context.\n *\n * The intention of these high-level tools is to allow usage of Inngest step\n * tools within API endpoints, though they can still be used within regular\n * Inngest functions as well.\n */\nexport const step: GenericStepTools = {\n // TODO Support `step.fetch` (this is already kinda half way deferred)\n fetch: null as unknown as GenericStepTools[\"fetch\"],\n ai: {\n infer: (...args) =>\n getDeferredStepTooling().then((tools) => tools.ai.infer(...args)),\n wrap: (...args) =>\n getDeferredStepTooling().then((tools) => tools.ai.wrap(...args)),\n models: {\n ...models,\n },\n },\n invoke: (...args) =>\n getDeferredStepTooling().then((tools) => tools.invoke(...args)),\n run: (...args) =>\n getDeferredStepTooling().then((tools) => tools.run(...args)),\n sendEvent: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sendEvent(...args)),\n sendSignal: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sendSignal(...args)),\n sleep: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sleep(...args)),\n sleepUntil: (...args) =>\n getDeferredStepTooling().then((tools) => tools.sleepUntil(...args)),\n waitForEvent: (...args) =>\n getDeferredStepTooling().then((tools) => tools.waitForEvent(...args)),\n waitForSignal: (...args) =>\n getDeferredStepTooling().then((tools) => tools.waitForSignal(...args)),\n realtime: {\n publish: (...args) =>\n getDeferredStepTooling().then((tools) => tools.realtime.publish(...args)),\n },\n};\n\n/**\n * An internal function used to retrieve or create step tooling for the current\n * execution context.\n *\n * Note that this requires an existing context to create the step tooling;\n * something must declare the Inngest execution context before this can be used.\n */\nconst getDeferredStepTooling = async (): Promise<GenericStepTools> => {\n const ctx = await getAsyncCtx();\n if (!ctx) {\n throw new Error(\n \"`step` tools can only be used within Inngest function executions; no context was found\",\n );\n }\n\n if (!ctx.app) {\n throw new Error(\n \"`step` tools can only be used within Inngest function executions; no Inngest client was found in the execution context\",\n );\n }\n\n if (!ctx.execution) {\n throw new Error(\n \"`step` tools can only be used within Inngest function executions; no execution context was found\",\n );\n }\n\n // If we're here, we're in the context of a function execution already and\n // we can return the existing step tooling.\n return ctx.execution.ctx.step;\n};\n\nconst getDeferredGroupTooling = async (): Promise<GroupTools> => {\n const ctx = await getAsyncCtx();\n if (!ctx) {\n throw new Error(\n \"`group` tools can only be used within Inngest function executions; no context was found\",\n );\n }\n\n if (!ctx.execution) {\n throw new Error(\n \"`group` tools can only be used within Inngest function executions; no execution context was found\",\n );\n }\n\n return ctx.execution.ctx.group;\n};\n\n/**\n * A deferred proxy for `group` tools that delegates through ALS context.\n *\n * @public\n */\nexport const group: GroupTools = {\n parallel: (...args) =>\n getDeferredGroupTooling().then((tools) => tools.parallel(...args)),\n experiment: (...args) =>\n getDeferredGroupTooling().then((tools) => tools.experiment(...args)),\n};\n\n/**\n * The event payload portion of the options for `step.invoke()`. This does not\n * include non-payload options like `timeout` or the function to invoke.\n */\nexport const invokePayloadSchema = z.object({\n data: z.record(z.any()).optional(),\n v: z.string().optional(),\n});\n\ntype InvocationTargetOpts<TFunction extends InvokeTargetFunctionDefinition> = {\n function: TFunction;\n};\n\ntype InvocationOpts<TFunction extends InvokeTargetFunctionDefinition> =\n InvocationTargetOpts<TFunction> &\n Omit<TriggerEventFromFunction<TFunction>, \"id\"> & {\n /**\n * The step function will wait for the invocation to finish for a maximum\n * of this time, at which point the retured promise will be rejected\n * instead of resolved with the output of the invoked function.\n *\n * Note that the invoked function will continue to run even if this step\n * times out.\n *\n * The time to wait can be specified using a `number` of milliseconds, an\n * `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or `\"2.5d\"`,\n * a `Date`, a `Temporal.Duration` (relative wait), or a `Temporal.Instant`\n * / `Temporal.ZonedDateTime` (absolute deadline).\n *\n * {@link https://npm.im/ms}\n */\n timeout?:\n | number\n | string\n | Date\n | Temporal.DurationLike\n | Temporal.InstantLike\n | Temporal.ZonedDateTimeLike;\n };\n\n/**\n * A set of parameters given to a `sendSignal` call.\n */\ntype SendSignalOpts = {\n /**\n * The signal to send.\n */\n signal: string;\n\n /**\n * The data to send with the signal.\n */\n data?: unknown;\n};\n\n/**\n * A set of parameters given to a `waitForSignal` call.\n */\ntype WaitForSignalOpts = {\n /**\n * The signal to wait for.\n */\n signal: string;\n\n /**\n * The step function will wait for the signal for a maximum of this time, at\n * which point the signal will be returned as `null` instead of any signal\n * data.\n *\n * The time to wait can be specified using a `number` of milliseconds, an\n * `ms`-compatible time string like `\"1 hour\"`, `\"30 mins\"`, or `\"2.5d\"`, a\n * `Date`, a `Temporal.Duration` (relative wait), or a `Temporal.Instant` /\n * `Temporal.ZonedDateTime` (absolute deadline).\n *\n * {@link https://npm.im/ms}\n */\n timeout:\n | number\n | string\n | Date\n | Temporal.DurationLike\n | Temporal.InstantLike\n | Temporal.ZonedDateTimeLike;\n\n /**\n * When this `step.waitForSignal()` call is made, choose whether an existing\n * wait for the same signal should be replaced, or whether this run should\n * fail.\n *\n * `\"replace\"` will replace any existing wait with this one, and the existing\n * wait will remain pending until it reaches its timeout.\n *\n * `\"fail\"` will cause this run to fail if there is already a wait for the\n * same signal.\n */\n onConflict: \"replace\" | \"fail\";\n};\n\n/**\n * Computes the return type for `waitForEvent` based on the options provided.\n *\n * Handles three cases:\n * 1. `event: EventType<TName, TSchema>` - extracts name and data from EventType\n * 2. `event: string` with `schema` - uses string as name and schema for data\n * 3. `event: string` without schema - uses string as name with untyped data\n */\ntype WaitForEventResult<TOpts> =\n // Case 1: event is an EventType with a schema\n TOpts extends {\n event: EventType<\n infer TName extends string,\n StandardSchemaV1<infer TData extends Record<string, unknown>>\n >;\n }\n ? { name: TName; data: TData; id: string; ts: number; v?: string } | null\n : // Case 2: event is an EventType without a schema\n TOpts extends {\n event: EventType<infer TName extends string, undefined>;\n }\n ? {\n name: TName;\n // biome-ignore lint/suspicious/noExplicitAny: fallback for untyped events\n data: Record<string, any>;\n id: string;\n ts: number;\n v?: string;\n } | null\n : // Case 3: event is a string with schema (spread EventType)\n TOpts extends {\n event: infer TName extends string;\n schema: StandardSchemaV1<\n infer TData extends Record<string, unknown>\n >;\n }\n ? {\n name: TName;\n data: TData;\n id: string;\n ts: number;\n v?: string;\n } | null\n : // Case 4: event is just a string\n TOpts extends { event: infer TName extends string }\n ? {\n name: TName;\n // biome-ignore lint/suspicious/noExplicitAny: fallback for untyped events\n data: Record<string, any>;\n id: string;\n ts: number;\n v?: string;\n } | null\n : EventPayload | null;\n\n/**\n * Options for `step.ai.infer()`.\n */\ntype AiInferOpts<TModel extends AiAdapter> = {\n /**\n * The model to use for the inference. Create a model by importing from\n * `\"inngest\"` or by using `step.ai.models.*`.\n *\n * @example Import `openai()`\n * ```ts\n * import { openai } from \"inngest\";\n *\n * const model = openai({ model: \"gpt-4\" });\n * ```\n *\n * @example Use a model from `step.ai.models`\n * ```ts\n * async ({ step }) => {\n * const model = step.ai.models.openai({ model: \"gpt-4\" });\n * }\n * ```\n */\n model: TModel;\n\n /**\n * The input to pass to the model.\n */\n body: AiAdapter.Input<TModel>;\n};\n"],"mappings":";;;;;;;;;;;;;;AA8MA,MAAa,kBAAkB,YAA0C;AACvE,KAAI,OAAO,YAAY,SACrB,QAAO,EAAE,IAAI,SAAS;AAGxB,QAAO;;;;;AAMT,MAAa,uBAAuB;AAwBpC,MAAa,mBAIX,QACA,WACA,gBACG;;;;;;;CAQH,MAAM,cAUJ,SACA,SACM;EACN,MAAMA,kBAAgC,aAAa,GAAG,SAAS;GAC7D,MAAM,KAAK,QAAQ,aAAa,GAAG,KAAK;GAExC,MAAM,SAAS,iBAAiB,EAAE;AAElC,OAAI,QAAQ,uBACV,OAAM,IAAI,kBACR,mJAGD;GAIH,MAAM,eAAe,YAAY,gBAAgB,QAAQ;AAEzD,OAAI,aACF,IAAG,OAAO;IAAE,GAAG,GAAG;IAAM;IAAc;GAIxC,MAAM,oBAAoB,QAAQ;AAClC,OAAI,kBACF,IAAG,OAAO;IAAE,GAAG,GAAG;IAAM,GAAG;IAAmB;GAIhD,MAAM,UAAU,QAAQ;AACxB,OAAI,QACF,SAAQ,QAAQ;AAGlB,UAAO;;AAGT,UAAQ,OAAO,GAAG,SAA0C;AAE1D,UAAO,YAAY;IADA;IACoB,SAAS;IAAgB;IAAM,CAAC;;;;;;;CAQ3E,MAAM,iBAKJ,SACG;AACH,SAAO,YAgCJ,EAAE,IAAI,QAAQ,KAAK,GAAG,UAAU;GAC/B,MAAMC,OAAyB;IAC7B,GAAI,MAAM,SAAS,EAAE,OAAO,GAAG,EAAE;IACjC,GAAI,OAAO,EAAE,MAAM,GAAG,EAAE;IACzB;AAED,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,GAAI,OAAO,KAAK,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,EAAE;IAC5C,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,KAAK,GAAG,IAAI,IAAI,GAAG,UAAU,GAAG,GAAG,MAAM,EAC1C,CACF;;;;;;CAOH,MAAM,6BACJ,eACA,YACG;AACH,MAAI,CAAC,OAAO,+BACV,OAAM,IAAI,MACR,qIACD;EAEH,MAAM,eAAe,SACnB,0BAA0B,eAAe,KAAK;AAEhD,MAAI,CAAC,QACH,WAAU,IAAI,wBAAwB,OAAO,CAAC,KAAK;AAGrD,SAAO;GACL,MAAM,UAAmB,YAAY,QAAQ,IAAI,MAAM,CAAC;GACxD,OAAO,QAAgB,UACrB,YAAY,QAAQ,KAAK,QAAQ,MAAM,CAAC;GAC1C,UAAU,iBACR,YAAY,QAAQ,QAAQ,aAAa,CAAC;GAC5C,OAAO,WAAmB,YAAY,QAAQ,KAAK,OAAO,CAAC;GAC3D,QAAQ,OACN,QACA,OAAO,cACW;AAClB,UAAM,MAAM,IAAI,eAAe,YAAY;AACzC,WAAM,QAAQ,OAAO,QAAQ,KAAK;MAClC;;GAGJ,IAAI,OACF,OACkB;AAClB,UAAM,MAAM,IAAI,eAAe,YAAY;AACzC,WAAM,GAAG,QAAQ;MACjB;;GAEL;;;;;;;;CASH,MAAM,QAAQ;EA2BZ,WAAW,YAMR,EAAE,IAAI,WAAW;AAChB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,MAAM,EACJ,MAAM,kBACP;IACD,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,KAAK,MAAM,cAAc,YAAY;GACnC,MAAM,KAAK,UAAU,WAAW;AAChC,UAAO,OAAO,SAAS;IACrB;IACA,SAAS,UAAU,WAAW;IAC9B,cAAc,GAAG,KAAK,cAAc,EAAE;IACtC;IACD,CAAC;KAEL,CACF;EASD,eAAe,YAKZ,EAAE,IAAI,QAAQ,SAAS;AACxB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM,KAAK;IACX,aAAa,QAAQ;IACrB,MAAM;KACJ,QAAQ,KAAK;KACb,SAAS,QAAQ,KAAK,QAAQ;KAC9B,UAAU,KAAK;KAChB;IACD,UAAU,EAAE,IAAI;IACjB;IACD;EAUF,UAAU,EAOR,SAAS,YAON,EAAE,IAAI,WAAW;AAChB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,aAAa,QAAQ;IACrB,MAAM,EACJ,MAAM,yBACP;IACD,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,IAAI,OAAO,KAAK,cAAc,UAAU,SAAS;GAC/C,MAAM,cAAc,SAAS;AAC7B,OAAI,eAAe,YAAY,eAAe,YAAY,QAGxD;SADE,MAAM,YAAY,OAAO,aAAa,SAAS,KAAK,EAC3C,OACT,OAAM,IAAI,MACR,uCAAuC,SAAS,MAAM,GACvD;;GAIL,MAAM,MAAM,MAAM,OAAO,cAAc,QACrC;IACE,QAAQ,CAAC,SAAS,MAAM;IACxB,SAAS,SAAS;IAClB,OAAO,IAAI;IACZ,EACD,KACD;AAED,OAAI,CAAC,IAAI,GACP,OAAM,IAAI,MACR,kCAAkC,IAAI,OAAO,SAAS,kBACvD;AAGH,UAAO;KAEV,CACF,EACF;EAKD,YAAY,YAMT,EAAE,IAAI,QAAQ,SAAS;AACtB,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,MAAM;KACJ,MAAM;KACN,QAAQ,KAAK;KACd;IACD,UAAU,EAAE,IAAI;IACjB;KAEH,EACE,KAAK,MAAM,cAAc,SAAS;AAChC,UAAO,OAAO,eAAe;IAC3B,QAAQ,KAAK;IACb,MAAM,KAAK;IACX,SAAS,UAAU,WAAW;IAC/B,CAAC;KAEL,CACF;EAWD,cAAc,YAqCV,EAAE,IAAI,QAKN,SACG;GACH,MAAMC,YAA8C,EAClD,SAAS,QAAQ,OAAO,SAAS,WAAW,OAAO,KAAK,QAAQ,EACjE;AAED,OAAI,OAAO,SAAS,UAClB;QAAI,MAAM,MACR,WAAU,KAAK,SAAS,KAAK,MAAM,YAAY,KAAK;aAC3C,MAAM,GACf,WAAU,KAAK,KAAK;;GAKxB,MAAM,YACJ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAK,MAAM;AAE3D,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,MAAM;IACN,aAAa,QAAQ;IACrB,UAAU,EAAE,IAAI;IACjB;IAEJ;EAcD,KAAK,eAAe;EAKpB,IAAI;GAOF,OAAO,YAKJ,EAAE,IAAI,QAAQ,YAAY;IAE3B,MAAM,EAAE,OAAO,MAAM,GAAG,SAAS;IAEjC,MAAM,YAAY,EAAE,GAAG,OAAO;AAG9B,YAAQ,MAAM,SAAS,WAAW,QAAQ,KAAK;AAE/C,WAAO;KACL;KACA,MAAM,SAAS;KACf,IAAI,WAAW;KACf,aAAa,QAAQ;KACrB,MAAM;MACJ,MAAM;MACN,KAAK,UAAU;MACf,SAAS,UAAU;MACnB,UAAU,UAAU;MACpB,QAAQ,UAAU;MAElB;MAEA,GAAG;MACJ;KACD,UAAU,EAAE,IAAI;KACjB;KACD;GAUF,MAAM,cAAc,eAAe;GAKnC,QAAQ,EACN,GAAG,QACJ;GACF;EAYD,OAAO,YASJ,EAAE,IAAI,QAAQ,SAAS;;;;;GAKxB,MAAMC,YAAoB,QAAQ,KAAK;AAEvC,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,MAAM;IACN,aAAa,QAAQ;IACrB,UAAU,EAAE,IAAI;IACjB;IACD;EAQF,YAAY,YAST,EAAE,IAAI,QAAQ,SAAS;AACxB,OAAI;IACF,MAAM,MAAMC,aAAsB,KAAK;;;;;AAMvC,WAAO;KACL;KACA,MAAM,SAAS;KACf,IAAI,WAAW;KACf,MAAM;KACN,aAAa,QAAQ;KACrB,UAAU,EAAE,IAAI;KACjB;YACM,KAAK;;;;;AAKZ,WAAO,sBAAsB,KAC3B,EAAE,KAAK,EACP,oGACD;AAED,UAAM,IAAI,MACR,4GACE,OAEH;;IAEH;EAOF,QAAQ,YAWL,EAAE,IAAI,QAAQ,eAAe;GAG9B,MAAM,aAAa,oBAAoB,OAAO,EAC5C,SAAS,EACN,QAQE,QACC,OAAOC,QAAM,YACb,OAAOA,QAAM,YACbA,eAAa,QACbC,mBAA4BD,IAAE,IAC9BE,kBAA2BF,IAAE,IAC7BG,wBAAiCH,IAAE,EACrC,EAAE,SAAS,mBAAmB,CAC/B,CACA,UAAU,EACd,CAAC;GAEF,MAAM,eAAe,WAClB,OAAO;IACN,OAAO,EAAE,QAAQ,aAAa,CAAC,UAAU,CAAC,QAAQ,aAAa;IAC/D,UAAU,EAAE,WAAW,gBAAgB;IACxC,CAAC,CACD,GACC,WAAW,OAAO;IAChB,OAAO,EAAE,QAAQ,cAAc,CAAC,UAAU,CAAC,QAAQ,cAAc;IACjE,UAAU,EAAE,WAAW,yBAAyB;IACjD,CAAC,CACH,CACA,UAAU,WAAW;AAExB,OAAI,CAAC,aAAa,QAChB,OAAM,IAAI,MACR,wGACD;GAGH,MAAM,EAAE,OAAO,UAAU,IAAI,MAAM,GAAG,YAAY,aAAa;GAE/D,MAAMI,OAIF;IACF,SANc;KAAE;KAAM;KAAG;IAOzB,aAAa;IACb,SAAS,OAAO,YAAY,cAAc,SAAY,QAAQ,QAAQ;IACvE;AAED,WAAQ,OAAR;IACE,KAAK;AACH,UAAK,cAAc,GAAG,GAAG,GAAG,UAAU,GAAG;AACzC;IAEF,KAAK;AACH,UAAK,cAAc,CAAC,GAAG,KAAK,SAAS,OAAO,IAAI,GAAG,KAAK,WAAW,CAChE,OAAO,QAAQ,CACf,KAAK,IAAI;AACZ;;AAGJ,UAAO;IACL;IACA,MAAM,SAAS;IACf,IAAI,WAAW;IACf,aAAa,QAAQ;IACrB;IACA,UAAU,EAAE,IAAI;IACjB;IACD;EASKC;EACR;AAID,CAAC,MAA2C,mBAC1C,kBACqB,0BAA0B,cAAc;AAI/D,CAAC,MAAyC,2BACxC,cAAc,mBAAmB;AAGnC,CAAC,MAAuC,iBAAiB,YACtD,EAAE,IAAI,QAAQ,OAAO,SAAS;EAC7B,MAAM,MAAM,iBAAiB,UAAU,MAAM,MAAM,MAAM,UAAU;EAEnE,MAAMC,UAAkC,EAAE;AAC1C,MAAI,iBAAiB,QACnB,OAAM,QAAQ,SAAS,OAAO,QAAQ;AACpC,WAAQ,OAAO;IACf;WACO,MAAM,QAEf,CADU,IAAI,QAAQ,KAAK,QAAQ,CACjC,SAAS,OAAO,QAAQ;AACxB,WAAQ,OAAO;IACf;AAGJ,SAAO;GACL;GACA,MAAM,SAAS;GACf,IAAI,WAAW;GACf,aAAa,QAAQ;GACrB,MAAM;IACJ;IACA,QAAQ,MAAM,UAAU;IACxB;IACA,MAAM,MAAM;IACb;GACD,UAAU,EAAE,IAAI;GACjB;GAEJ;AAED,QAAO;;AAST,MAAa,gBAAgB,OAAO,IAAI,uBAAuB;AAiB/D,MAAa,0BAA0B,OAAO,IAAI,2BAA2B;;;;;;;;;;;;AAoB7E,MAAaC,OAAyB;CAEpC,OAAO;CACP,IAAI;EACF,QAAQ,GAAG,SACT,wBAAwB,CAAC,MAAM,UAAU,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;EACnE,OAAO,GAAG,SACR,wBAAwB,CAAC,MAAM,UAAU,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;EAClE,QAAQ,EACN,GAAG,QACJ;EACF;CACD,SAAS,GAAG,SACV,wBAAwB,CAAC,MAAM,UAAU,MAAM,OAAO,GAAG,KAAK,CAAC;CACjE,MAAM,GAAG,SACP,wBAAwB,CAAC,MAAM,UAAU,MAAM,IAAI,GAAG,KAAK,CAAC;CAC9D,YAAY,GAAG,SACb,wBAAwB,CAAC,MAAM,UAAU,MAAM,UAAU,GAAG,KAAK,CAAC;CACpE,aAAa,GAAG,SACd,wBAAwB,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;CACrE,QAAQ,GAAG,SACT,wBAAwB,CAAC,MAAM,UAAU,MAAM,MAAM,GAAG,KAAK,CAAC;CAChE,aAAa,GAAG,SACd,wBAAwB,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;CACrE,eAAe,GAAG,SAChB,wBAAwB,CAAC,MAAM,UAAU,MAAM,aAAa,GAAG,KAAK,CAAC;CACvE,gBAAgB,GAAG,SACjB,wBAAwB,CAAC,MAAM,UAAU,MAAM,cAAc,GAAG,KAAK,CAAC;CACxE,UAAU,EACR,UAAU,GAAG,SACX,wBAAwB,CAAC,MAAM,UAAU,MAAM,SAAS,QAAQ,GAAG,KAAK,CAAC,EAC5E;CACF;;;;;;;;AASD,MAAM,yBAAyB,YAAuC;CACpE,MAAM,MAAM,MAAM,aAAa;AAC/B,KAAI,CAAC,IACH,OAAM,IAAI,MACR,yFACD;AAGH,KAAI,CAAC,IAAI,IACP,OAAM,IAAI,MACR,yHACD;AAGH,KAAI,CAAC,IAAI,UACP,OAAM,IAAI,MACR,mGACD;AAKH,QAAO,IAAI,UAAU,IAAI;;AAG3B,MAAM,0BAA0B,YAAiC;CAC/D,MAAM,MAAM,MAAM,aAAa;AAC/B,KAAI,CAAC,IACH,OAAM,IAAI,MACR,0FACD;AAGH,KAAI,CAAC,IAAI,UACP,OAAM,IAAI,MACR,oGACD;AAGH,QAAO,IAAI,UAAU,IAAI;;;;;;;AAQ3B,MAAaC,QAAoB;CAC/B,WAAW,GAAG,SACZ,yBAAyB,CAAC,MAAM,UAAU,MAAM,SAAS,GAAG,KAAK,CAAC;CACpE,aAAa,GAAG,SACd,yBAAyB,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,CAAC;CACvE;;;;;AAMD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,UAAU;CAClC,GAAG,EAAE,QAAQ,CAAC,UAAU;CACzB,CAAC"}
@@ -6,6 +6,7 @@ const require_types = require('../../types.cjs');
6
6
  const require_url = require('../../../../helpers/url.cjs');
7
7
  const require_messages = require('../../messages.cjs');
8
8
  const require_handshake = require('./handshake.cjs');
9
+ const require_types$1 = require('./types.cjs');
9
10
  const require_heartbeat = require('./heartbeat.cjs');
10
11
  const require_requestProcessor = require('./requestProcessor.cjs');
11
12
  const require_statusReporter = require('./statusReporter.cjs');
@@ -35,7 +36,7 @@ let __jpwilliams_waitgroup = require("@jpwilliams/waitgroup");
35
36
  * - Manages a single heartbeat interval targeting the active connection
36
37
  * - Handles reconnection, drain, and shutdown as state changes
37
38
  */
38
- var ConnectionCore = class {
39
+ var ConnectionCore = class ConnectionCore {
39
40
  config;
40
41
  callbacks;
41
42
  _activeConnection;
@@ -51,8 +52,12 @@ var ConnectionCore = class {
51
52
  _lastMessageReceivedAt;
52
53
  excludeGateways = /* @__PURE__ */ new Set();
53
54
  wakeSignal;
55
+ pendingWakeReasons = [];
54
56
  hasConnectedBefore = false;
57
+ shutdownDumpInterval;
58
+ static SHUTDOWN_DUMP_INTERVAL_MS = 6e4;
55
59
  loopPromise;
60
+ closePromise;
56
61
  resolveFirstReady;
57
62
  rejectFirstReady;
58
63
  useSigningKey;
@@ -87,7 +92,7 @@ var ConnectionCore = class {
87
92
  return self.config.appIds;
88
93
  }
89
94
  };
90
- const wakeSignalRef = { wake: () => this.wake() };
95
+ const wakeSignalRef = { wake: (reason) => this.wake(reason) };
91
96
  const self = this;
92
97
  this.heartbeatManager = new require_heartbeat.HeartbeatManager(accessor, wakeSignalRef, callbacks.logger);
93
98
  this.heartbeatManager.onHeartbeatSent = () => {
@@ -143,14 +148,21 @@ var ConnectionCore = class {
143
148
  * connection closed).
144
149
  */
145
150
  async close() {
151
+ if (this.closePromise) return this.closePromise;
152
+ this.closePromise = this.closeOnce();
153
+ return this.closePromise;
154
+ }
155
+ async closeOnce() {
146
156
  const inFlightCount = Object.keys(this._inProgressRequests.requestLeases).length;
147
157
  this.callbacks.logger.info({ inFlightCount }, "Shutting down, waiting for in-flight requests");
148
158
  this._shutdownRequested = true;
159
+ this.dumpInFlightForShutdown("drain-start");
160
+ this.startShutdownInFlightDumpTimer();
149
161
  if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {
150
162
  this._activeConnection.ws.send(require_buffer.ensureUnsharedArrayBuffer(require_connect.ConnectMessage.encode(require_connect.ConnectMessage.create({ kind: require_connect.GatewayMessageType.WORKER_PAUSE })).finish()));
151
163
  this.callbacks.logger.info({ connectionId: this._activeConnection.id }, "Sent WORKER_PAUSE, draining");
152
164
  }
153
- this.wake();
165
+ this.wake(require_types$1.WAKE_REASON.ShutdownRequested);
154
166
  if (this.loopPromise) await this.loopPromise;
155
167
  this.callbacks.logger.info("Connection closed");
156
168
  }
@@ -169,9 +181,10 @@ var ConnectionCore = class {
169
181
  resolve
170
182
  };
171
183
  }
172
- wake() {
173
- this.wakeSignal.resolve();
174
- this.resetWakeSignal();
184
+ wake(reason = require_types$1.WAKE_REASON.Unknown) {
185
+ const shouldResolve = this.pendingWakeReasons.length === 0;
186
+ this.pendingWakeReasons.push(reason);
187
+ if (shouldResolve) this.wakeSignal.resolve();
175
188
  }
176
189
  switchAuthKey() {
177
190
  const switchToFallback = this.useSigningKey === this.config.hashedSigningKey;
@@ -181,8 +194,63 @@ var ConnectionCore = class {
181
194
  hasInFlightRequests() {
182
195
  return Object.keys(this._inProgressRequests.requestLeases).length > 0;
183
196
  }
197
+ /**
198
+ * Debug-level "still draining" dump emitted at drain start and periodically
199
+ * thereafter while in-flight requests are holding the shutdown. One summary
200
+ * line plus one line per request carrying `requestId`, `runId`, `stepId`,
201
+ * `functionSlug`, `ageMs`, and `sinceLastLeaseExtendMs`. Does not affect
202
+ * info/warn logs.
203
+ *
204
+ * `requestLeases` drives the reconcile-loop exit gate, so use it as the
205
+ * single source of truth for the in-flight set; `requestMeta` carries the
206
+ * enrichment fields and is kept in sync alongside the lease map.
207
+ */
208
+ dumpInFlightForShutdown(reason) {
209
+ const leaseIds = Object.keys(this._inProgressRequests.requestLeases);
210
+ if (leaseIds.length === 0) return;
211
+ const now = Date.now();
212
+ const ages = [];
213
+ for (const id of leaseIds) {
214
+ const m = this._inProgressRequests.requestMeta[id];
215
+ if (m?.leaseAcquiredAt) ages.push(now - m.leaseAcquiredAt);
216
+ }
217
+ this.callbacks.logger.debug({
218
+ reason,
219
+ inFlightCount: leaseIds.length,
220
+ oldestAgeMs: ages.length > 0 ? Math.max(...ages) : void 0
221
+ }, "Shutdown: still draining");
222
+ for (const id of leaseIds) {
223
+ const m = this._inProgressRequests.requestMeta[id];
224
+ if (!m) continue;
225
+ this.callbacks.logger.debug({
226
+ reason,
227
+ requestId: m.requestId,
228
+ runId: m.runId,
229
+ stepId: m.stepId,
230
+ functionSlug: m.functionSlug,
231
+ appId: m.appId,
232
+ ageMs: m.leaseAcquiredAt ? now - m.leaseAcquiredAt : void 0,
233
+ sinceLastLeaseExtendMs: m.leaseLastExtendedAt ? now - m.leaseLastExtendedAt : void 0
234
+ }, "Shutdown: still draining in-flight request");
235
+ }
236
+ }
237
+ startShutdownInFlightDumpTimer() {
238
+ if (this.shutdownDumpInterval) return;
239
+ this.shutdownDumpInterval = setInterval(() => {
240
+ if (!this._shutdownRequested) return;
241
+ this.dumpInFlightForShutdown("periodic");
242
+ this.wake(require_types$1.WAKE_REASON.ShutdownStillPending);
243
+ }, ConnectionCore.SHUTDOWN_DUMP_INTERVAL_MS);
244
+ }
245
+ stopShutdownInFlightDumpTimer() {
246
+ if (this.shutdownDumpInterval) {
247
+ clearInterval(this.shutdownDumpInterval);
248
+ this.shutdownDumpInterval = void 0;
249
+ }
250
+ }
184
251
  async reconcileLoop(initialAttempt) {
185
252
  let attempt = initialAttempt;
253
+ this.callbacks.logger.debug({ initialAttempt }, "Reconcile loop entered");
186
254
  while (true) {
187
255
  if (this._shutdownRequested && !this.hasInFlightRequests()) break;
188
256
  if (!this._activeConnection || this._activeConnection.dead) {
@@ -219,7 +287,10 @@ var ConnectionCore = class {
219
287
  if (this._shutdownRequested) {
220
288
  conn.ws.send(require_buffer.ensureUnsharedArrayBuffer(require_connect.ConnectMessage.encode(require_connect.ConnectMessage.create({ kind: require_connect.GatewayMessageType.WORKER_PAUSE })).finish()));
221
289
  this.callbacks.logger.info({ connectionId: conn.id }, "Sent WORKER_PAUSE on reconnect during shutdown");
222
- } else conn.ws.send(require_buffer.ensureUnsharedArrayBuffer(require_connect.ConnectMessage.encode(require_connect.ConnectMessage.create({ kind: require_connect.GatewayMessageType.WORKER_READY })).finish()));
290
+ } else {
291
+ conn.ws.send(require_buffer.ensureUnsharedArrayBuffer(require_connect.ConnectMessage.encode(require_connect.ConnectMessage.create({ kind: require_connect.GatewayMessageType.WORKER_READY })).finish()));
292
+ this.callbacks.logger.info({ connectionId: conn.id }, "Sent WORKER_READY");
293
+ }
223
294
  this.callbacks.onConnectionActive?.(this.useSigningKey);
224
295
  this.resolveFirstReady?.();
225
296
  this.resolveFirstReady = void 0;
@@ -252,15 +323,24 @@ var ConnectionCore = class {
252
323
  continue;
253
324
  }
254
325
  }
255
- await this.wakeSignal.promise;
326
+ if (this.pendingWakeReasons.length === 0) await this.wakeSignal.promise;
327
+ const reasons = this.pendingWakeReasons;
328
+ this.pendingWakeReasons = [];
329
+ this.resetWakeSignal();
256
330
  this.callbacks.logger.debug({
331
+ reasons,
257
332
  shutdownRequested: this._shutdownRequested,
258
333
  hasActiveConnection: !!this._activeConnection,
259
334
  activeConnectionDead: this._activeConnection?.dead
260
335
  }, "Reconcile loop woken");
261
336
  }
337
+ this.callbacks.logger.debug({
338
+ shutdownRequested: this._shutdownRequested,
339
+ inFlightCount: Object.keys(this._inProgressRequests.requestLeases).length
340
+ }, "Reconcile loop exiting");
262
341
  this.heartbeatManager.stop();
263
342
  this.statusReporter.stop();
343
+ this.stopShutdownInFlightDumpTimer();
264
344
  this._activeConnection?.close();
265
345
  this._activeConnection = void 0;
266
346
  this._drainingConnection?.close();
@@ -284,7 +364,7 @@ var ConnectionCore = class {
284
364
  conn.dead = true;
285
365
  this.excludeGateways.add(gatewayGroup);
286
366
  if (this._activeConnection?.id === connectionId) this._activeConnection = void 0;
287
- this.wake();
367
+ this.wake(require_types$1.WAKE_REASON.WsError);
288
368
  };
289
369
  ws.onclose = (ev) => {
290
370
  if (conn.dead) return;
@@ -299,7 +379,7 @@ var ConnectionCore = class {
299
379
  conn.dead = true;
300
380
  this.excludeGateways.add(gatewayGroup);
301
381
  if (this._activeConnection?.id === connectionId) this._activeConnection = void 0;
302
- this.wake();
382
+ this.wake(require_types$1.WAKE_REASON.WsClose);
303
383
  };
304
384
  ws.onmessage = async (event) => {
305
385
  this._lastMessageReceivedAt = Date.now();
@@ -313,7 +393,7 @@ var ConnectionCore = class {
313
393
  }, "Gateway draining, opening new connection");
314
394
  this._drainingConnection = this._activeConnection;
315
395
  this._activeConnection = void 0;
316
- this.wake();
396
+ this.wake(require_types$1.WAKE_REASON.GatewayClosing);
317
397
  return;
318
398
  }
319
399
  if (connectMessage.kind === require_connect.GatewayMessageType.GATEWAY_HEARTBEAT) {
@@ -1 +1 @@
1
- {"version":3,"file":"connection.cjs","names":["WaitGroup","resolve: () => void","HeartbeatManager","StatusReporter","RequestProcessor","ConnectionState","ensureUnsharedArrayBuffer","ConnectMessage","GatewayMessageType","resolveApiBaseUrl","establishConnection","ReconnectError","AuthError","ConnectionLimitError","waitWithCancel","expBackoff","parseConnectMessage","gatewayMessageTypeToJSON"],"sources":["../../../../../src/components/connect/strategies/core/connection.ts"],"sourcesContent":["/**\n * Shared connection core logic used by both SameThreadStrategy and\n * WorkerThreadStrategy.\n *\n * This module uses a **reconcile loop** that continuously ensures a live\n * WebSocket connection is open. Reconnection, drain, and shutdown are\n * expressed as state changes that wake the loop rather than recursive\n * calls or callback-driven control flow.\n *\n * Domain-specific logic is delegated to focused sub-modules:\n * - {@link HeartbeatManager} — periodic heartbeat pings\n * - {@link RequestProcessor} — executor requests, lease extensions, reply ACKs\n * - {@link establishConnection} — HTTP start + WebSocket handshake\n */\n\nimport { WaitGroup } from \"@jpwilliams/waitgroup\";\nimport { resolveApiBaseUrl } from \"../../../../helpers/url.ts\";\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport type { GatewayExecutorRequestData } from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport {\n ConnectMessage,\n GatewayMessageType,\n gatewayMessageTypeToJSON,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport { parseConnectMessage } from \"../../messages.ts\";\nimport {\n type ConnectDebugState,\n ConnectionState,\n type InFlightRequest,\n} from \"../../types.ts\";\nimport {\n AuthError,\n ConnectionLimitError,\n expBackoff,\n ReconnectError,\n waitWithCancel,\n} from \"../../util.ts\";\nimport { establishConnection } from \"./handshake.ts\";\nimport { HeartbeatManager } from \"./heartbeat.ts\";\nimport { RequestProcessor } from \"./requestProcessor.ts\";\nimport { StatusReporter } from \"./statusReporter.ts\";\nimport type { BaseConnectionConfig } from \"./types.ts\";\n\n/**\n * Connection object representing an active WebSocket connection.\n */\nexport interface Connection {\n id: string;\n ws: WebSocket;\n pendingHeartbeats: number;\n /** When true the connection is considered unusable and the reconcile loop\n * will establish a replacement. */\n dead: boolean;\n heartbeatIntervalMs: number;\n extendLeaseIntervalMs: number;\n statusIntervalMs: number;\n /** Timestamp (ms) when the connection was established. */\n connectedAt: number;\n /** Disable all handlers and close the underlying WebSocket. */\n close(): void;\n}\n\n/**\n * Configuration for the connection core.\n * Extends BaseConnectionConfig with connection-specific options.\n */\nexport interface ConnectionCoreConfig extends BaseConnectionConfig {\n instanceId?: string;\n maxWorkerConcurrency?: number;\n gatewayUrl?: string;\n appIds: string[];\n}\n\n/**\n * Callbacks for connection core events.\n */\nexport interface ConnectionCoreCallbacks {\n logger: Logger;\n onStateChange: (state: ConnectionState) => void;\n getState: () => ConnectionState;\n handleExecutionRequest: (\n request: GatewayExecutorRequestData,\n ) => Promise<Uint8Array>;\n onReplyAck?: (requestId: string) => void;\n onBufferResponse?: (requestId: string, responseBytes: Uint8Array) => void;\n onConnectionActive?: (signingKey: string | undefined) => void;\n}\n\n/**\n * Core connection manager that handles WebSocket connection lifecycle,\n * handshake, heartbeat, lease extension, and reconnection.\n *\n * Uses a reconcile loop that:\n * - Ensures a WebSocket connection is always open\n * - Manages a single heartbeat interval targeting the active connection\n * - Handles reconnection, drain, and shutdown as state changes\n */\nexport class ConnectionCore {\n private config: ConnectionCoreConfig;\n private callbacks: ConnectionCoreCallbacks;\n\n // Exposed via ConnectionAccessor for sub-modules\n private _activeConnection: Connection | undefined;\n private _drainingConnection: Connection | undefined;\n private _shutdownRequested = false;\n private _inProgressRequests: {\n wg: WaitGroup;\n requestLeases: Record<string, string>;\n requestMeta: Record<string, InFlightRequest>;\n } = {\n wg: new WaitGroup(),\n requestLeases: {},\n requestMeta: {},\n };\n\n private _lastHeartbeatSentAt: number | undefined;\n private _lastHeartbeatReceivedAt: number | undefined;\n private _lastMessageReceivedAt: number | undefined;\n\n private excludeGateways: Set<string> = new Set();\n\n // Wake signal for the reconcile loop\n private wakeSignal: { promise: Promise<void>; resolve: () => void };\n\n // Whether we've ever successfully connected (used to distinguish\n // CONNECTING from RECONNECTING state transitions).\n private hasConnectedBefore = false;\n\n // Loop promise — resolved when the reconcile loop exits\n private loopPromise: Promise<void> | undefined;\n\n // First-ready resolution — resolves start() when first connection is ready\n private resolveFirstReady: (() => void) | undefined;\n private rejectFirstReady: ((err: unknown) => void) | undefined;\n\n // Signing key management\n private useSigningKey: string | undefined;\n\n // Sub-modules\n private readonly heartbeatManager: HeartbeatManager;\n private readonly statusReporter: StatusReporter;\n private readonly requestProcessor: RequestProcessor;\n\n constructor(\n config: ConnectionCoreConfig,\n callbacks: ConnectionCoreCallbacks,\n ) {\n this.config = config;\n this.callbacks = callbacks;\n this.useSigningKey = config.hashedSigningKey;\n\n // Initialize the wake signal\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n\n // Build a ConnectionAccessor view for sub-modules\n const accessor = {\n get activeConnection() {\n return self._activeConnection;\n },\n get drainingConnection() {\n return self._drainingConnection;\n },\n get shutdownRequested() {\n return self._shutdownRequested;\n },\n get inProgressRequests() {\n return self._inProgressRequests;\n },\n get appIds() {\n return self.config.appIds;\n },\n };\n\n const wakeSignalRef = { wake: () => this.wake() };\n\n const self = this;\n\n this.heartbeatManager = new HeartbeatManager(\n accessor,\n wakeSignalRef,\n callbacks.logger,\n );\n this.heartbeatManager.onHeartbeatSent = () => {\n this._lastHeartbeatSentAt = Date.now();\n };\n\n this.statusReporter = new StatusReporter(accessor, callbacks.logger);\n\n this.requestProcessor = new RequestProcessor(\n accessor,\n wakeSignalRef,\n callbacks,\n callbacks.logger,\n );\n }\n\n get connectionId(): string | undefined {\n return this._activeConnection?.id;\n }\n\n /**\n * Wait for all in-progress requests to complete.\n */\n async waitForInProgress(): Promise<void> {\n await this._inProgressRequests.wg.wait();\n }\n\n /**\n * Return a snapshot of debug/health information for this connection.\n */\n getDebugState(): ConnectDebugState {\n return {\n state: this.callbacks.getState(),\n activeConnectionId: this._activeConnection?.id,\n drainingConnectionId: this._drainingConnection?.id,\n lastHeartbeatSentAt: this._lastHeartbeatSentAt,\n lastHeartbeatReceivedAt: this._lastHeartbeatReceivedAt,\n lastMessageReceivedAt: this._lastMessageReceivedAt,\n shutdownRequested: this._shutdownRequested,\n inFlightRequestCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n inFlightRequests: Object.values(this._inProgressRequests.requestMeta),\n };\n }\n\n /**\n * Start the reconcile loop. Resolves when the first connection is active.\n * The loop continues running in the background.\n */\n async start(attempt = 0): Promise<void> {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n const state = this.callbacks.getState();\n if (state === ConnectionState.CLOSED) {\n throw new Error(\"Connection already closed\");\n }\n\n const firstReadyPromise = new Promise<void>((resolve, reject) => {\n this.resolveFirstReady = resolve;\n this.rejectFirstReady = reject;\n });\n\n this.loopPromise = this.reconcileLoop(attempt);\n\n // If the loop ends before firstReady resolves, propagate any error\n this.loopPromise.catch((err) => {\n this.rejectFirstReady?.(err);\n });\n\n await firstReadyPromise;\n }\n\n /**\n * Request graceful shutdown. Resolves when fully closed (in-flight done,\n * connection closed).\n */\n async close(): Promise<void> {\n const inFlightCount = Object.keys(\n this._inProgressRequests.requestLeases,\n ).length;\n this.callbacks.logger.info(\n { inFlightCount },\n \"Shutting down, waiting for in-flight requests\",\n );\n this._shutdownRequested = true;\n\n if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {\n this._activeConnection.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: this._activeConnection.id },\n \"Sent WORKER_PAUSE, draining\",\n );\n }\n\n this.wake();\n\n if (this.loopPromise) {\n await this.loopPromise;\n }\n\n this.callbacks.logger.info(\"Connection closed\");\n }\n\n async getApiBaseUrl(): Promise<string> {\n return resolveApiBaseUrl({\n apiBaseUrl: this.config.apiBaseUrl,\n mode: this.config.mode,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Wake signal\n // ---------------------------------------------------------------------------\n\n private resetWakeSignal(): void {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n }\n\n private wake(): void {\n this.wakeSignal.resolve();\n this.resetWakeSignal();\n }\n\n // ---------------------------------------------------------------------------\n // Signing key management\n // ---------------------------------------------------------------------------\n\n private switchAuthKey(): void {\n const switchToFallback =\n this.useSigningKey === this.config.hashedSigningKey;\n if (switchToFallback) {\n this.callbacks.logger.debug(\"Switching to fallback signing key\");\n }\n this.useSigningKey = switchToFallback\n ? this.config.hashedFallbackKey\n : this.config.hashedSigningKey;\n }\n\n // ---------------------------------------------------------------------------\n // In-flight helpers\n // ---------------------------------------------------------------------------\n\n private hasInFlightRequests(): boolean {\n return Object.keys(this._inProgressRequests.requestLeases).length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Reconcile loop\n // ---------------------------------------------------------------------------\n\n private async reconcileLoop(initialAttempt: number): Promise<void> {\n let attempt = initialAttempt;\n\n while (true) {\n // Exit condition: shutdown requested + no in-flight requests\n if (this._shutdownRequested && !this.hasInFlightRequests()) {\n break;\n }\n\n // Ensure we have a live connection\n if (!this._activeConnection || this._activeConnection.dead) {\n this.callbacks.logger.debug(\n {\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n hasDrainingConnection: !!this._drainingConnection,\n drainingConnectionId: this._drainingConnection?.id,\n },\n \"No active connection\",\n );\n\n if (this.hasConnectedBefore) {\n this.callbacks.logger.info({ attempt }, \"Reconnecting\");\n } else {\n this.callbacks.logger.info(\"Connecting\");\n }\n\n this.callbacks.onStateChange(\n this.hasConnectedBefore\n ? ConnectionState.RECONNECTING\n : ConnectionState.CONNECTING,\n );\n\n try {\n const { conn, gatewayGroup } = await establishConnection(\n this.config,\n this.useSigningKey,\n attempt,\n this.excludeGateways,\n this.callbacks.logger,\n );\n\n // Attach post-handshake handlers\n this.attachHandlers(conn, gatewayGroup);\n\n // Clean up draining connection after new one is ready\n if (this._drainingConnection) {\n this.callbacks.logger.info(\n {\n oldConnectionId: this._drainingConnection.id,\n newConnectionId: conn.id,\n },\n \"Replaced draining connection\",\n );\n this._drainingConnection.close();\n this._drainingConnection = undefined;\n }\n\n this._activeConnection = conn;\n this.heartbeatManager.updateInterval(conn.heartbeatIntervalMs);\n this.statusReporter.updateInterval(conn.statusIntervalMs);\n attempt = 0;\n this.hasConnectedBefore = true;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup },\n \"Connection active\",\n );\n this.callbacks.onStateChange(ConnectionState.ACTIVE);\n\n if (this._shutdownRequested) {\n // Reconnected during shutdown to keep in-flight requests alive.\n // Send WORKER_PAUSE instead of WORKER_READY so no new work is routed.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Sent WORKER_PAUSE on reconnect during shutdown\",\n );\n } else {\n // Signal the gateway that we're ready to receive requests.\n // This must happen after ACTIVE so the gateway doesn't route\n // requests before handlers are fully attached.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_READY,\n }),\n ).finish(),\n ),\n );\n }\n\n // Flush any buffered responses via HTTP now that we're active.\n this.callbacks.onConnectionActive?.(this.useSigningKey);\n\n this.resolveFirstReady?.();\n this.resolveFirstReady = undefined;\n this.rejectFirstReady = undefined;\n } catch (err) {\n if (!(err instanceof ReconnectError)) throw err;\n\n attempt = err.attempt + 1;\n if (err instanceof AuthError) this.switchAuthKey();\n if (err instanceof ConnectionLimitError) {\n this.callbacks.logger.error(\"Max concurrent connections reached\");\n }\n\n // Gateway is draining, we should retry much faster\n if (err.message?.includes(\"connect_gateway_closing\")) {\n const jitter = 500 + Math.random() * 1000;\n this.callbacks.logger.info(\n { attempt, delay: Math.round(jitter), error: err.message },\n \"Gateway draining, retrying\",\n );\n const cancelled = await waitWithCancel(jitter, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n\n const delay = expBackoff(attempt);\n this.callbacks.logger.info(\n { attempt, delay },\n \"Reconnecting after failure\",\n );\n\n const cancelled = await waitWithCancel(delay, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n }\n\n // Wait for something to change\n await this.wakeSignal.promise;\n this.callbacks.logger.debug(\n {\n shutdownRequested: this._shutdownRequested,\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n },\n \"Reconcile loop woken\",\n );\n }\n\n // Teardown\n this.heartbeatManager.stop();\n this.statusReporter.stop();\n this._activeConnection?.close();\n this._activeConnection = undefined;\n this._drainingConnection?.close();\n this._drainingConnection = undefined;\n }\n\n // ---------------------------------------------------------------------------\n // Post-handshake handler attachment\n // ---------------------------------------------------------------------------\n\n /**\n * Wire up error, close, and message handlers on a newly-handshaked connection.\n */\n private attachHandlers(conn: Connection, gatewayGroup: string): void {\n const { ws } = conn;\n const connectionId = conn.id;\n\n // Error/close handlers: mark connection as dead and wake the loop\n ws.onerror = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n error: (ev as ErrorEvent)?.message,\n },\n \"Connection lost (error)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n ws.onclose = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n code: ev.code,\n reason: ev.reason,\n },\n \"Connection lost (close)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n // Message handler for post-handshake messages\n ws.onmessage = async (event) => {\n this._lastMessageReceivedAt = Date.now();\n\n const messageBytes = new Uint8Array(event.data as ArrayBuffer);\n const connectMessage = parseConnectMessage(messageBytes);\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup, uptimeMs },\n \"Gateway draining, opening new connection\",\n );\n // Move current connection to draining, clear active so the loop\n // establishes a replacement.\n this._drainingConnection = this._activeConnection;\n this._activeConnection = undefined;\n this.wake();\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_HEARTBEAT) {\n this._lastHeartbeatReceivedAt = Date.now();\n conn.pendingHeartbeats = 0;\n this.callbacks.logger.debug(\n { connectionId },\n \"Handled gateway heartbeat\",\n );\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_EXECUTOR_REQUEST) {\n await this.requestProcessor.handleExecutorRequest(connectMessage, conn);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.WORKER_REPLY_ACK) {\n this.requestProcessor.handleReplyAck(connectMessage, connectionId);\n return;\n }\n\n if (\n connectMessage.kind ===\n GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE_ACK\n ) {\n this.requestProcessor.handleExtendLeaseAck(\n connectMessage,\n connectionId,\n );\n return;\n }\n\n this.callbacks.logger.warn(\n {\n kind: gatewayMessageTypeToJSON(connectMessage.kind),\n rawKind: connectMessage.kind,\n state: this.callbacks.getState(),\n connectionId,\n },\n \"Unexpected message type\",\n );\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"connection.cjs","names":["WaitGroup","resolve: () => void","HeartbeatManager","StatusReporter","RequestProcessor","ConnectionState","ensureUnsharedArrayBuffer","ConnectMessage","GatewayMessageType","WAKE_REASON","resolveApiBaseUrl","ages: number[]","establishConnection","ReconnectError","AuthError","ConnectionLimitError","waitWithCancel","expBackoff","parseConnectMessage","gatewayMessageTypeToJSON"],"sources":["../../../../../src/components/connect/strategies/core/connection.ts"],"sourcesContent":["/**\n * Shared connection core logic used by both SameThreadStrategy and\n * WorkerThreadStrategy.\n *\n * This module uses a **reconcile loop** that continuously ensures a live\n * WebSocket connection is open. Reconnection, drain, and shutdown are\n * expressed as state changes that wake the loop rather than recursive\n * calls or callback-driven control flow.\n *\n * Domain-specific logic is delegated to focused sub-modules:\n * - {@link HeartbeatManager} — periodic heartbeat pings\n * - {@link RequestProcessor} — executor requests, lease extensions, reply ACKs\n * - {@link establishConnection} — HTTP start + WebSocket handshake\n */\n\nimport { WaitGroup } from \"@jpwilliams/waitgroup\";\nimport { resolveApiBaseUrl } from \"../../../../helpers/url.ts\";\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport type { GatewayExecutorRequestData } from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport {\n ConnectMessage,\n GatewayMessageType,\n gatewayMessageTypeToJSON,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport { parseConnectMessage } from \"../../messages.ts\";\nimport {\n type ConnectDebugState,\n ConnectionState,\n type InFlightRequest,\n} from \"../../types.ts\";\nimport {\n AuthError,\n ConnectionLimitError,\n expBackoff,\n ReconnectError,\n waitWithCancel,\n} from \"../../util.ts\";\nimport { establishConnection } from \"./handshake.ts\";\nimport { HeartbeatManager } from \"./heartbeat.ts\";\nimport { RequestProcessor } from \"./requestProcessor.ts\";\nimport { StatusReporter } from \"./statusReporter.ts\";\nimport type { BaseConnectionConfig } from \"./types.ts\";\nimport { WAKE_REASON, type WakeReason } from \"./types.ts\";\n\n/**\n * Connection object representing an active WebSocket connection.\n */\nexport interface Connection {\n id: string;\n ws: WebSocket;\n pendingHeartbeats: number;\n /** When true the connection is considered unusable and the reconcile loop\n * will establish a replacement. */\n dead: boolean;\n heartbeatIntervalMs: number;\n extendLeaseIntervalMs: number;\n statusIntervalMs: number;\n /** Timestamp (ms) when the connection was established. */\n connectedAt: number;\n /** Disable all handlers and close the underlying WebSocket. */\n close(): void;\n}\n\n/**\n * Configuration for the connection core.\n * Extends BaseConnectionConfig with connection-specific options.\n */\nexport interface ConnectionCoreConfig extends BaseConnectionConfig {\n instanceId?: string;\n maxWorkerConcurrency?: number;\n gatewayUrl?: string;\n appIds: string[];\n}\n\n/**\n * Callbacks for connection core events.\n */\nexport interface ConnectionCoreCallbacks {\n logger: Logger;\n onStateChange: (state: ConnectionState) => void;\n getState: () => ConnectionState;\n handleExecutionRequest: (\n request: GatewayExecutorRequestData,\n ) => Promise<Uint8Array>;\n onReplyAck?: (requestId: string) => void;\n onBufferResponse?: (requestId: string, responseBytes: Uint8Array) => void;\n onConnectionActive?: (signingKey: string | undefined) => void;\n}\n\n/**\n * Core connection manager that handles WebSocket connection lifecycle,\n * handshake, heartbeat, lease extension, and reconnection.\n *\n * Uses a reconcile loop that:\n * - Ensures a WebSocket connection is always open\n * - Manages a single heartbeat interval targeting the active connection\n * - Handles reconnection, drain, and shutdown as state changes\n */\nexport class ConnectionCore {\n private config: ConnectionCoreConfig;\n private callbacks: ConnectionCoreCallbacks;\n\n // Exposed via ConnectionAccessor for sub-modules\n private _activeConnection: Connection | undefined;\n private _drainingConnection: Connection | undefined;\n private _shutdownRequested = false;\n private _inProgressRequests: {\n wg: WaitGroup;\n requestLeases: Record<string, string>;\n requestMeta: Record<string, InFlightRequest>;\n } = {\n wg: new WaitGroup(),\n requestLeases: {},\n requestMeta: {},\n };\n\n private _lastHeartbeatSentAt: number | undefined;\n private _lastHeartbeatReceivedAt: number | undefined;\n private _lastMessageReceivedAt: number | undefined;\n\n private excludeGateways: Set<string> = new Set();\n\n // Wake signal for the reconcile loop\n private wakeSignal: { promise: Promise<void>; resolve: () => void };\n // Reasons accumulated since the last loop wake. Read + cleared by the loop.\n private pendingWakeReasons: WakeReason[] = [];\n\n // Whether we've ever successfully connected (used to distinguish\n // CONNECTING from RECONNECTING state transitions).\n private hasConnectedBefore = false;\n\n // Shutdown diagnostics: periodic \"still draining\" dump logged while the\n // drain is outstanding. Started in close(), cleared in teardown.\n private shutdownDumpInterval: ReturnType<typeof setInterval> | undefined;\n\n // Cadence for the periodic \"still draining\" debug dump.\n private static readonly SHUTDOWN_DUMP_INTERVAL_MS = 60_000;\n\n // Loop promise — resolved when the reconcile loop exits\n private loopPromise: Promise<void> | undefined;\n private closePromise: Promise<void> | undefined;\n\n // First-ready resolution — resolves start() when first connection is ready\n private resolveFirstReady: (() => void) | undefined;\n private rejectFirstReady: ((err: unknown) => void) | undefined;\n\n // Signing key management\n private useSigningKey: string | undefined;\n\n // Sub-modules\n private readonly heartbeatManager: HeartbeatManager;\n private readonly statusReporter: StatusReporter;\n private readonly requestProcessor: RequestProcessor;\n\n constructor(\n config: ConnectionCoreConfig,\n callbacks: ConnectionCoreCallbacks,\n ) {\n this.config = config;\n this.callbacks = callbacks;\n this.useSigningKey = config.hashedSigningKey;\n\n // Initialize the wake signal\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n\n // Build a ConnectionAccessor view for sub-modules\n const accessor = {\n get activeConnection() {\n return self._activeConnection;\n },\n get drainingConnection() {\n return self._drainingConnection;\n },\n get shutdownRequested() {\n return self._shutdownRequested;\n },\n get inProgressRequests() {\n return self._inProgressRequests;\n },\n get appIds() {\n return self.config.appIds;\n },\n };\n\n const wakeSignalRef = { wake: (reason?: WakeReason) => this.wake(reason) };\n\n const self = this;\n\n this.heartbeatManager = new HeartbeatManager(\n accessor,\n wakeSignalRef,\n callbacks.logger,\n );\n this.heartbeatManager.onHeartbeatSent = () => {\n this._lastHeartbeatSentAt = Date.now();\n };\n\n this.statusReporter = new StatusReporter(accessor, callbacks.logger);\n\n this.requestProcessor = new RequestProcessor(\n accessor,\n wakeSignalRef,\n callbacks,\n callbacks.logger,\n );\n }\n\n get connectionId(): string | undefined {\n return this._activeConnection?.id;\n }\n\n /**\n * Wait for all in-progress requests to complete.\n */\n async waitForInProgress(): Promise<void> {\n await this._inProgressRequests.wg.wait();\n }\n\n /**\n * Return a snapshot of debug/health information for this connection.\n */\n getDebugState(): ConnectDebugState {\n return {\n state: this.callbacks.getState(),\n activeConnectionId: this._activeConnection?.id,\n drainingConnectionId: this._drainingConnection?.id,\n lastHeartbeatSentAt: this._lastHeartbeatSentAt,\n lastHeartbeatReceivedAt: this._lastHeartbeatReceivedAt,\n lastMessageReceivedAt: this._lastMessageReceivedAt,\n shutdownRequested: this._shutdownRequested,\n inFlightRequestCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n inFlightRequests: Object.values(this._inProgressRequests.requestMeta),\n };\n }\n\n /**\n * Start the reconcile loop. Resolves when the first connection is active.\n * The loop continues running in the background.\n */\n async start(attempt = 0): Promise<void> {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n const state = this.callbacks.getState();\n if (state === ConnectionState.CLOSED) {\n throw new Error(\"Connection already closed\");\n }\n\n const firstReadyPromise = new Promise<void>((resolve, reject) => {\n this.resolveFirstReady = resolve;\n this.rejectFirstReady = reject;\n });\n\n this.loopPromise = this.reconcileLoop(attempt);\n\n // If the loop ends before firstReady resolves, propagate any error\n this.loopPromise.catch((err) => {\n this.rejectFirstReady?.(err);\n });\n\n await firstReadyPromise;\n }\n\n /**\n * Request graceful shutdown. Resolves when fully closed (in-flight done,\n * connection closed).\n */\n async close(): Promise<void> {\n if (this.closePromise) return this.closePromise;\n\n this.closePromise = this.closeOnce();\n return this.closePromise;\n }\n\n private async closeOnce(): Promise<void> {\n const inFlightCount = Object.keys(\n this._inProgressRequests.requestLeases,\n ).length;\n this.callbacks.logger.info(\n { inFlightCount },\n \"Shutting down, waiting for in-flight requests\",\n );\n // Flip the shutdown flag before starting any timers so the periodic\n // dump guard (`if (!this._shutdownRequested) return`) cannot observe a\n // stale `false` on its first tick.\n this._shutdownRequested = true;\n // Verbose per-request dump (debug-only) at drain start so operators can\n // immediately see which runs are holding the shutdown.\n this.dumpInFlightForShutdown(\"drain-start\");\n this.startShutdownInFlightDumpTimer();\n\n if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {\n this._activeConnection.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: this._activeConnection.id },\n \"Sent WORKER_PAUSE, draining\",\n );\n }\n\n this.wake(WAKE_REASON.ShutdownRequested);\n\n if (this.loopPromise) {\n await this.loopPromise;\n }\n\n this.callbacks.logger.info(\"Connection closed\");\n }\n\n async getApiBaseUrl(): Promise<string> {\n return resolveApiBaseUrl({\n apiBaseUrl: this.config.apiBaseUrl,\n mode: this.config.mode,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Wake signal\n // ---------------------------------------------------------------------------\n\n private resetWakeSignal(): void {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n }\n\n private wake(reason: WakeReason = WAKE_REASON.Unknown): void {\n // Only the first pending wake needs to resolve the parked loop; later\n // wakes are accumulated and consumed together on the next iteration.\n const shouldResolve = this.pendingWakeReasons.length === 0;\n this.pendingWakeReasons.push(reason);\n if (shouldResolve) {\n this.wakeSignal.resolve();\n }\n }\n\n // ---------------------------------------------------------------------------\n // Signing key management\n // ---------------------------------------------------------------------------\n\n private switchAuthKey(): void {\n const switchToFallback =\n this.useSigningKey === this.config.hashedSigningKey;\n if (switchToFallback) {\n this.callbacks.logger.debug(\"Switching to fallback signing key\");\n }\n this.useSigningKey = switchToFallback\n ? this.config.hashedFallbackKey\n : this.config.hashedSigningKey;\n }\n\n // ---------------------------------------------------------------------------\n // In-flight helpers\n // ---------------------------------------------------------------------------\n\n private hasInFlightRequests(): boolean {\n return Object.keys(this._inProgressRequests.requestLeases).length > 0;\n }\n\n /**\n * Debug-level \"still draining\" dump emitted at drain start and periodically\n * thereafter while in-flight requests are holding the shutdown. One summary\n * line plus one line per request carrying `requestId`, `runId`, `stepId`,\n * `functionSlug`, `ageMs`, and `sinceLastLeaseExtendMs`. Does not affect\n * info/warn logs.\n *\n * `requestLeases` drives the reconcile-loop exit gate, so use it as the\n * single source of truth for the in-flight set; `requestMeta` carries the\n * enrichment fields and is kept in sync alongside the lease map.\n */\n private dumpInFlightForShutdown(reason: string): void {\n const leaseIds = Object.keys(this._inProgressRequests.requestLeases);\n if (leaseIds.length === 0) return;\n const now = Date.now();\n const ages: number[] = [];\n for (const id of leaseIds) {\n const m = this._inProgressRequests.requestMeta[id];\n if (m?.leaseAcquiredAt) ages.push(now - m.leaseAcquiredAt);\n }\n\n this.callbacks.logger.debug(\n {\n reason,\n inFlightCount: leaseIds.length,\n oldestAgeMs: ages.length > 0 ? Math.max(...ages) : undefined,\n },\n \"Shutdown: still draining\",\n );\n\n for (const id of leaseIds) {\n const m = this._inProgressRequests.requestMeta[id];\n if (!m) continue;\n this.callbacks.logger.debug(\n {\n reason,\n requestId: m.requestId,\n runId: m.runId,\n stepId: m.stepId,\n functionSlug: m.functionSlug,\n appId: m.appId,\n ageMs: m.leaseAcquiredAt ? now - m.leaseAcquiredAt : undefined,\n sinceLastLeaseExtendMs: m.leaseLastExtendedAt\n ? now - m.leaseLastExtendedAt\n : undefined,\n },\n \"Shutdown: still draining in-flight request\",\n );\n }\n }\n\n private startShutdownInFlightDumpTimer(): void {\n if (this.shutdownDumpInterval) return;\n this.shutdownDumpInterval = setInterval(() => {\n if (!this._shutdownRequested) return;\n this.dumpInFlightForShutdown(\"periodic\");\n // Wake the loop so its \"Reconcile loop woken\" line emits a fresh\n // state snapshot alongside the in-flight dump. Loop will park again\n // immediately if nothing has changed.\n this.wake(WAKE_REASON.ShutdownStillPending);\n }, ConnectionCore.SHUTDOWN_DUMP_INTERVAL_MS);\n }\n\n private stopShutdownInFlightDumpTimer(): void {\n if (this.shutdownDumpInterval) {\n clearInterval(this.shutdownDumpInterval);\n this.shutdownDumpInterval = undefined;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Reconcile loop\n // ---------------------------------------------------------------------------\n\n private async reconcileLoop(initialAttempt: number): Promise<void> {\n let attempt = initialAttempt;\n\n this.callbacks.logger.debug({ initialAttempt }, \"Reconcile loop entered\");\n\n while (true) {\n // Exit condition: shutdown requested + no in-flight requests\n if (this._shutdownRequested && !this.hasInFlightRequests()) {\n break;\n }\n\n // Ensure we have a live connection\n if (!this._activeConnection || this._activeConnection.dead) {\n this.callbacks.logger.debug(\n {\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n hasDrainingConnection: !!this._drainingConnection,\n drainingConnectionId: this._drainingConnection?.id,\n },\n \"No active connection\",\n );\n\n if (this.hasConnectedBefore) {\n this.callbacks.logger.info({ attempt }, \"Reconnecting\");\n } else {\n this.callbacks.logger.info(\"Connecting\");\n }\n\n this.callbacks.onStateChange(\n this.hasConnectedBefore\n ? ConnectionState.RECONNECTING\n : ConnectionState.CONNECTING,\n );\n\n try {\n const { conn, gatewayGroup } = await establishConnection(\n this.config,\n this.useSigningKey,\n attempt,\n this.excludeGateways,\n this.callbacks.logger,\n );\n\n // Attach post-handshake handlers\n this.attachHandlers(conn, gatewayGroup);\n\n // Clean up draining connection after new one is ready\n if (this._drainingConnection) {\n this.callbacks.logger.info(\n {\n oldConnectionId: this._drainingConnection.id,\n newConnectionId: conn.id,\n },\n \"Replaced draining connection\",\n );\n this._drainingConnection.close();\n this._drainingConnection = undefined;\n }\n\n this._activeConnection = conn;\n this.heartbeatManager.updateInterval(conn.heartbeatIntervalMs);\n this.statusReporter.updateInterval(conn.statusIntervalMs);\n attempt = 0;\n this.hasConnectedBefore = true;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup },\n \"Connection active\",\n );\n this.callbacks.onStateChange(ConnectionState.ACTIVE);\n\n if (this._shutdownRequested) {\n // Reconnected during shutdown to keep in-flight requests alive.\n // Send WORKER_PAUSE instead of WORKER_READY so no new work is routed.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Sent WORKER_PAUSE on reconnect during shutdown\",\n );\n } else {\n // Signal the gateway that we're ready to receive requests.\n // This must happen after ACTIVE so the gateway doesn't route\n // requests before handlers are fully attached.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_READY,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Sent WORKER_READY\",\n );\n }\n\n // Flush any buffered responses via HTTP now that we're active.\n this.callbacks.onConnectionActive?.(this.useSigningKey);\n\n this.resolveFirstReady?.();\n this.resolveFirstReady = undefined;\n this.rejectFirstReady = undefined;\n } catch (err) {\n if (!(err instanceof ReconnectError)) throw err;\n\n attempt = err.attempt + 1;\n if (err instanceof AuthError) this.switchAuthKey();\n if (err instanceof ConnectionLimitError) {\n this.callbacks.logger.error(\"Max concurrent connections reached\");\n }\n\n // Gateway is draining, we should retry much faster\n if (err.message?.includes(\"connect_gateway_closing\")) {\n const jitter = 500 + Math.random() * 1000;\n this.callbacks.logger.info(\n { attempt, delay: Math.round(jitter), error: err.message },\n \"Gateway draining, retrying\",\n );\n const cancelled = await waitWithCancel(jitter, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n\n const delay = expBackoff(attempt);\n this.callbacks.logger.info(\n { attempt, delay },\n \"Reconnecting after failure\",\n );\n\n const cancelled = await waitWithCancel(delay, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n }\n\n // Wait for something to change. If a wake fired while this loop was\n // doing async work above, pendingWakeReasons is already populated; don't\n // wait on the replacement wakeSignal or the wake can be missed.\n if (this.pendingWakeReasons.length === 0) {\n await this.wakeSignal.promise;\n }\n const reasons = this.pendingWakeReasons;\n this.pendingWakeReasons = [];\n this.resetWakeSignal();\n this.callbacks.logger.debug(\n {\n reasons,\n shutdownRequested: this._shutdownRequested,\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n },\n \"Reconcile loop woken\",\n );\n }\n\n this.callbacks.logger.debug(\n {\n shutdownRequested: this._shutdownRequested,\n inFlightCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n },\n \"Reconcile loop exiting\",\n );\n\n // Teardown\n this.heartbeatManager.stop();\n this.statusReporter.stop();\n this.stopShutdownInFlightDumpTimer();\n this._activeConnection?.close();\n this._activeConnection = undefined;\n this._drainingConnection?.close();\n this._drainingConnection = undefined;\n }\n\n // ---------------------------------------------------------------------------\n // Post-handshake handler attachment\n // ---------------------------------------------------------------------------\n\n /**\n * Wire up error, close, and message handlers on a newly-handshaked connection.\n */\n private attachHandlers(conn: Connection, gatewayGroup: string): void {\n const { ws } = conn;\n const connectionId = conn.id;\n\n // Error/close handlers: mark connection as dead and wake the loop\n ws.onerror = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n error: (ev as ErrorEvent)?.message,\n },\n \"Connection lost (error)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake(WAKE_REASON.WsError);\n };\n\n ws.onclose = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n code: ev.code,\n reason: ev.reason,\n },\n \"Connection lost (close)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake(WAKE_REASON.WsClose);\n };\n\n // Message handler for post-handshake messages\n ws.onmessage = async (event) => {\n this._lastMessageReceivedAt = Date.now();\n\n const messageBytes = new Uint8Array(event.data as ArrayBuffer);\n const connectMessage = parseConnectMessage(messageBytes);\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup, uptimeMs },\n \"Gateway draining, opening new connection\",\n );\n // Move current connection to draining, clear active so the loop\n // establishes a replacement.\n this._drainingConnection = this._activeConnection;\n this._activeConnection = undefined;\n this.wake(WAKE_REASON.GatewayClosing);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_HEARTBEAT) {\n this._lastHeartbeatReceivedAt = Date.now();\n conn.pendingHeartbeats = 0;\n this.callbacks.logger.debug(\n { connectionId },\n \"Handled gateway heartbeat\",\n );\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_EXECUTOR_REQUEST) {\n await this.requestProcessor.handleExecutorRequest(connectMessage, conn);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.WORKER_REPLY_ACK) {\n this.requestProcessor.handleReplyAck(connectMessage, connectionId);\n return;\n }\n\n if (\n connectMessage.kind ===\n GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE_ACK\n ) {\n this.requestProcessor.handleExtendLeaseAck(\n connectMessage,\n connectionId,\n );\n return;\n }\n\n this.callbacks.logger.warn(\n {\n kind: gatewayMessageTypeToJSON(connectMessage.kind),\n rawKind: connectMessage.kind,\n state: this.callbacks.getState(),\n connectionId,\n },\n \"Unexpected message type\",\n );\n };\n }\n}\n"],"mappings":"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"}