UNPKG

inngest

Version:

Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.

482 lines (480 loc) 19.3 kB
import { ExclusiveKeys, ParametersExceptFirst, SendEventPayload } from "../helpers/types.cjs"; import { StepFetch } from "./Fetch.cjs"; import { Jsonify } from "../helpers/jsonify.cjs"; import { DurationLike, InstantLike, ZonedDateTimeLike } from "../helpers/temporal.cjs"; import { GroupTools } from "./InngestGroupTools.cjs"; import { Middleware } from "./middleware/middleware.cjs"; import { Realtime } from "./realtime/types.cjs"; import { EventType } from "./triggers/triggers.cjs"; import { MetadataStepTool, metadataSymbol } from "./InngestMetadata.cjs"; import { InngestExecution } from "./execution/InngestExecution.cjs"; import { ApplyAllMiddlewareTransforms, Context, EventPayload, HashedOp, InvocationResult, InvokeTargetFunctionDefinition, SendEventOutput, StepOptions, StepOptionsOrId, TriggerEventFromFunction } from "../types.cjs"; import { ClientOptionsFromInngest, GetFunctionOutputRaw, GetStepTools, Inngest } from "./Inngest.cjs"; import { StandardSchemaV1 } from "@standard-schema/spec"; import { z } from "zod/v3"; import { AiAdapter, models } from "@inngest/ai"; //#region src/components/InngestStepTools.d.ts /** * Middleware context for a step, created during step registration. * * Uses a "deferred handler" pattern: the `wrapStep` middleware chain starts * during discovery (so middleware can inject its own steps), but the real * handler isn't known until after the memoization lookup. `setActualHandler` * bridges the gap — the chain blocks on a deferred promise that is resolved * once `executeStep` determines the real result. */ interface StepMiddlewareContext { /** * Sets the handler that the middleware pipeline will eventually call. * Called after memoization lookup to set either: * - A handler returning memoized data, OR * - A handler executing the step fresh */ setActualHandler: (handler: () => Promise<unknown>) => void; /** * Step info after middleware transformations. The `options.id` may differ * from the original if middleware modified it via `transformStepInput`. */ stepInfo: Middleware.StepInfo; /** * The middleware pipeline entry point. Call this to execute the step * through all middleware transformations. */ wrappedHandler: () => Promise<unknown>; } interface FoundStep extends HashedOp { hashedId: string; fn?: (...args: unknown[]) => unknown; rawArgs: unknown[]; /** * A boolean representing whether the step has been fulfilled, either * resolving or rejecting the `Promise` returned to userland code. * * Note that this is distinct from {@link hasStepState}, which instead tracks * whether the step has been given some state from the Executor. State from * the Executor could be data other than a resolution or rejection, such as * inputs. */ fulfilled: boolean; /** * A boolean representing whether the step has been given some state from the * Executor. State from the Executor could be data other than a resolution or * rejection, such as inputs. * * This is distinct from {@link fulfilled}, which instead tracks whether the * step has been fulfilled, either resolving or rejecting the `Promise` * returned to userland code. */ hasStepState: boolean; handled: boolean; /** * The promise that has been returned to userland code for this step. */ promise: Promise<unknown>; /** * Returns a boolean representing whether or not the step was handled on this * invocation. */ handle: () => boolean; input?: unknown; /** * Middleware context for this step. Holds the `wrapStep` chain entry point * and the deferred handler setter used by `executeStep`. */ middleware: StepMiddlewareContext; /** * For new steps where wrappedHandler is called during discovery, * this holds the resolve/reject to be called when the step's data is * memoized. Resolved with server-transformed data (post-wrapStepHandler), * which unblocks wrapStep's `next()`. * * Is undefined when any of the following is true: * - The step is fulfilled * - The step has no handler (`step.sleep`, `step.waitForSignal`, etc.) */ memoizationDeferred?: { resolve: (value: unknown) => void; reject: (error: unknown) => void; }; /** * For new steps where `wrappedHandler` is called during discovery, this holds * the promise for the wrapStep-transformed result. In checkpointing mode, * handle() reuses this promise to avoid a duplicate wrapStep call. */ transformedResultPromise?: Promise<unknown>; } type MatchOpFn<T extends (...args: unknown[]) => Promise<unknown> = (...args: unknown[]) => Promise<unknown>> = (stepOptions: StepOptions, /** * Arguments passed by the user. */ ...args: ParametersExceptFirst<T>) => Omit<HashedOp, "data" | "error">; type StepHandler = (info: { matchOp: MatchOpFn; opts?: StepToolOptions; args: [StepOptionsOrId, ...unknown[]]; }) => Promise<unknown>; interface StepToolOptions<T extends (...args: unknown[]) => Promise<unknown> = (...args: unknown[]) => Promise<unknown>> { /** * Optionally, we can also provide a function that will be called when * Inngest tells us to run this operation. * * If this function is defined, the first time the tool is used it will * report the desired operation (including options) to the Inngest. Inngest * will then call back to the function to tell it to run the step and then * retrieve data. * * We do this in order to allow functionality such as per-step retries; this * gives the SDK the opportunity to tell Inngest what it wants to do before * it does it. * * This function is passed the arguments passed by the user. It will be run * when we receive an operation matching this one that does not contain a * `data` property. */ fn?: (...args: [Context.Any, ...Parameters<T>]) => unknown; } /** * Create a new set of step function tools ready to be used in a step function. * This function should be run and a fresh set of tools provided every time a * function is run. * * An op stack (function state) is passed in as well as some mutable properties * that the tools can use to submit a new op. */ /** * Merge client-level and function-level middleware into a single array type * for use with ApplyAllMiddlewareTransforms etc. */ type MergedMiddleware<TClient extends Inngest.Any, TFnMiddleware extends Middleware.Class[] | undefined> = [...(ClientOptionsFromInngest<TClient>["middleware"] extends Middleware.Class[] ? ClientOptionsFromInngest<TClient>["middleware"] : []), ...(TFnMiddleware extends Middleware.Class[] ? TFnMiddleware : [])]; declare const createStepTools: <TClient extends Inngest.Any, TFnMiddleware extends Middleware.Class[] | undefined = undefined>(client: TClient, execution: InngestExecution, stepHandler: StepHandler) => { /** * Send one or many events to Inngest. Should always be used in place of * `inngest.send()` to ensure that the event send is successfully retried * and not sent multiple times due to memoisation. * * @example * ```ts * await step.sendEvent("emit-user-creation", { * name: "app/user.created", * data: { id: 123 }, * }); * * await step.sendEvent("emit-user-updates", [ * { * name: "app/user.created", * data: { id: 123 }, * }, * { * name: "app/user.feed.created", * data: { id: 123 }, * }, * ]); * ``` * * Returns a promise that will resolve once the event has been sent. */ sendEvent: (idOrOptions: StepOptionsOrId, payload: SendEventPayload) => Promise<SendEventOutput<ClientOptionsFromInngest<TClient>>>; /** * EXPERIMENTAL: This API is not yet stable and may change in the future * without a major version bump. * * Wait for a particular signal to be received before continuing. When the * signal is received, its data will be returned. */ waitForSignal: <TData>(idOrOptions: StepOptionsOrId, opts: WaitForSignalOpts) => Promise<{ signal: string; data: Jsonify<TData>; } | null>; /** * Step-level functionality related to realtime features. * * Unlike client-level realtime methods (`inngest.realtime.*`), these tools * will be their own durable steps when run. If you wish to use realtime * features outside of a step, make sure to use the client-level methods * instead. */ realtime: { /** * Publish a realtime message as a durable step. Memoized and will not * re-fire on retry, unlike client-level `inngest.realtime.publish()`. * * Uses topic accessors: `step.realtime.publish("id", chat.status, data)` */ publish: <TData>(idOrOptions: StepOptionsOrId, topicRef: Realtime.TopicRef<TData>, data: TData) => Promise<TData>; }; /** * Send a Signal to Inngest. */ sendSignal: (idOrOptions: StepOptionsOrId, opts: SendSignalOpts) => Promise<null>; /** * Wait for a particular event to be received before continuing. When the * event is received, it will be returned. * * You can also provide options to control the particular event that is * received, for example to ensure that a user ID matches between two * events, or to only wait a maximum amount of time before giving up and * returning `null` instead of any event data. */ waitForEvent: <TOpts extends { /** * The event to wait for. */ event: string | EventType<string, any>; /** * The step function will wait for the event for a maximum of this * time, at which point the signal will be returned as `null` instead * of any signal data. * * The time to wait can be specified using a `number` of milliseconds, * an `ms`-compatible time string like `"1 hour"`, `"30 mins"`, or * `"2.5d"`, or a `Date` object. * * {@link https://npm.im/ms} */ timeout: number | string | Date; } & ExclusiveKeys<{ match?: string; if?: string; }, "match", "if">>(idOrOptions: StepOptionsOrId, opts: TOpts) => Promise<WaitForEventResult<TOpts>>; /** * Use this tool to run business logic. Each call to `run` will be retried * individually, meaning you can compose complex workflows that safely * retry dependent asynchronous actions. * * The function you pass to `run` will be called only when this "step" is to * be executed and can be synchronous or asynchronous. * * In either case, the return value of the function will be the return value * of the `run` tool, meaning you can return and reason about return data * for next steps. */ run: <TFn extends (...args: any[]) => unknown>(idOrOptions: StepOptionsOrId, fn: TFn, ...input: Parameters<TFn>) => Promise<ApplyAllMiddlewareTransforms<MergedMiddleware<TClient, TFnMiddleware>, TFn extends ((...args: Parameters<TFn>) => Promise<infer U>) ? Awaited<U extends void ? null : U> : ReturnType<TFn> extends void ? null : ReturnType<TFn>>>; /** * AI tooling for running AI models and other AI-related tasks. */ ai: { /** * Use this tool to have Inngest make your AI calls. Useful for agentic workflows. * * Input is also tracked for this tool, meaning you can pass input to the * function and it will be displayed and editable in the UI. */ infer: <TAdapter extends AiAdapter>(idOrOptions: StepOptionsOrId, options: AiInferOpts<TAdapter>) => Promise<AiAdapter.Output<TAdapter>>; /** * Use this tool to wrap AI models and other AI-related tasks. Each call * to `wrap` will be retried individually, meaning you can compose complex * workflows that safely retry dependent asynchronous actions. * * Input is also tracked for this tool, meaning you can pass input to the * function and it will be displayed and editable in the UI. */ wrap: <TFn extends (...args: any[]) => unknown>(idOrOptions: StepOptionsOrId, fn: TFn, ...input: Parameters<TFn>) => Promise<ApplyAllMiddlewareTransforms<MergedMiddleware<TClient, TFnMiddleware>, TFn extends ((...args: Parameters<TFn>) => Promise<infer U>) ? Awaited<U extends void ? null : U> : ReturnType<TFn> extends void ? null : ReturnType<TFn>>>; /** * Models for AI inference and other AI-related tasks. */ models: { anthropic: AiAdapter.ModelCreator<[options: models.Anthropic.AiModelOptions], models.Anthropic.AiModel>; gemini: AiAdapter.ModelCreator<[options: models.Gemini.AiModelOptions], models.Gemini.AiModel>; openai: AiAdapter.ModelCreator<[options: models.OpenAi.AiModelOptions], models.OpenAi.AiModel>; deepseek: AiAdapter.ModelCreator<[options: models.DeepSeek.AiModelOptions], models.DeepSeek.AiModel>; grok: AiAdapter.ModelCreator<[options: models.Grok.AiModelOptions], models.Grok.AiModel>; }; }; /** * Wait a specified amount of time before continuing. * * The time to wait can be specified using a `number` of milliseconds or an * `ms`-compatible time string like `"1 hour"`, `"30 mins"`, or `"2.5d"`. * * {@link https://npm.im/ms} * * To wait until a particular date, use `sleepUntil` instead. */ sleep: (idOrOptions: StepOptionsOrId, time: number | string | DurationLike) => Promise<void>; /** * Wait until a particular date before continuing by passing a `Date`. * * To wait for a particular amount of time from now, always use `sleep` * instead. */ sleepUntil: (idOrOptions: StepOptionsOrId, time: Date | string | InstantLike | ZonedDateTimeLike) => Promise<void>; /** * Invoke a passed Inngest `function` with the given `data`. Returns the * result of the returned value of the function or `null` if the function * does not return a value. */ invoke: <TFunction extends InvokeTargetFunctionDefinition>(idOrOptions: StepOptionsOrId, opts: InvocationOpts<TFunction>) => InvocationResult<ApplyAllMiddlewareTransforms<MergedMiddleware<TClient, TFnMiddleware>, GetFunctionOutputRaw<TFunction>, "functionOutputTransform">>; /** * `step.fetch` is a Fetch-API-compatible function that can be used to make * any HTTP code durable if it's called within an Inngest function. * * It will gracefully fall back to the global `fetch` if called outside of * this context, and a custom fallback can be set using the `config` method. */ fetch: StepFetch; }; /** * A generic set of step tools, without typing information about the client used * to create them. */ type GenericStepTools = GetStepTools<Inngest.Any>; type ExperimentalStepTools = GetStepTools<Inngest.Any> & { [metadataSymbol]: (memoizationId: string) => MetadataStepTool; }; /** * A generic set of step tools that can be used without typing information about * the client used to create them. * * These tools use AsyncLocalStorage to track the context in which they are * used, and will throw an error if used outside of an Inngest context. * * The intention of these high-level tools is to allow usage of Inngest step * tools within API endpoints, though they can still be used within regular * Inngest functions as well. */ declare const step: GenericStepTools; /** * A deferred proxy for `group` tools that delegates through ALS context. * * @public */ declare const group: GroupTools; type InvocationTargetOpts<TFunction extends InvokeTargetFunctionDefinition> = { function: TFunction; }; type InvocationOpts<TFunction extends InvokeTargetFunctionDefinition> = InvocationTargetOpts<TFunction> & Omit<TriggerEventFromFunction<TFunction>, "id"> & { /** * The step function will wait for the invocation to finish for a maximum * of this time, at which point the retured promise will be rejected * instead of resolved with the output of the invoked function. * * Note that the invoked function will continue to run even if this step * times out. * * The time to wait can be specified using a `number` of milliseconds, an * `ms`-compatible time string like `"1 hour"`, `"30 mins"`, or `"2.5d"`, * or a `Date` object. * * {@link https://npm.im/ms} */ timeout?: number | string | Date; }; /** * A set of parameters given to a `sendSignal` call. */ type SendSignalOpts = { /** * The signal to send. */ signal: string; /** * The data to send with the signal. */ data?: unknown; }; /** * A set of parameters given to a `waitForSignal` call. */ type WaitForSignalOpts = { /** * The signal to wait for. */ signal: string; /** * The step function will wait for the signal for a maximum of this time, at * which point the signal will be returned as `null` instead of any signal * data. * * The time to wait can be specified using a `number` of milliseconds, an * `ms`-compatible time string like `"1 hour"`, `"30 mins"`, or `"2.5d"`, or * a `Date` object. * * {@link https://npm.im/ms} */ timeout: number | string | Date; /** * When this `step.waitForSignal()` call is made, choose whether an existing * wait for the same signal should be replaced, or whether this run should * fail. * * `"replace"` will replace any existing wait with this one, and the existing * wait will remain pending until it reaches its timeout. * * `"fail"` will cause this run to fail if there is already a wait for the * same signal. */ onConflict: "replace" | "fail"; }; /** * Computes the return type for `waitForEvent` based on the options provided. * * Handles three cases: * 1. `event: EventType<TName, TSchema>` - extracts name and data from EventType * 2. `event: string` with `schema` - uses string as name and schema for data * 3. `event: string` without schema - uses string as name with untyped data */ type WaitForEventResult<TOpts> = TOpts extends { event: EventType<infer TName extends string, StandardSchemaV1<infer TData extends Record<string, unknown>>>; } ? { name: TName; data: TData; id: string; ts: number; v?: string; } | null : TOpts extends { event: EventType<infer TName extends string, undefined>; } ? { name: TName; data: Record<string, any>; id: string; ts: number; v?: string; } | null : TOpts extends { event: infer TName extends string; schema: StandardSchemaV1<infer TData extends Record<string, unknown>>; } ? { name: TName; data: TData; id: string; ts: number; v?: string; } | null : TOpts extends { event: infer TName extends string; } ? { name: TName; data: Record<string, any>; id: string; ts: number; v?: string; } | null : EventPayload | null; /** * Options for `step.ai.infer()`. */ type AiInferOpts<TModel extends AiAdapter> = { /** * The model to use for the inference. Create a model by importing from * `"inngest"` or by using `step.ai.models.*`. * * @example Import `openai()` * ```ts * import { openai } from "inngest"; * * const model = openai({ model: "gpt-4" }); * ``` * * @example Use a model from `step.ai.models` * ```ts * async ({ step }) => { * const model = step.ai.models.openai({ model: "gpt-4" }); * } * ``` */ model: TModel; /** * The input to pass to the model. */ body: AiAdapter.Input<TModel>; }; //#endregion export { ExperimentalStepTools, FoundStep, createStepTools, group, step }; //# sourceMappingURL=InngestStepTools.d.cts.map