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
TypeScript
import { ExclusiveKeys, ParametersExceptFirst, SendEventPayload } from "../helpers/types.js";
import { StepFetch } from "./Fetch.js";
import { Jsonify } from "../helpers/jsonify.js";
import { DurationLike, InstantLike, ZonedDateTimeLike } from "../helpers/temporal.js";
import { GroupTools } from "./InngestGroupTools.js";
import { Middleware } from "./middleware/middleware.js";
import { Realtime } from "./realtime/types.js";
import { EventType } from "./triggers/triggers.js";
import { MetadataStepTool, metadataSymbol } from "./InngestMetadata.js";
import { InngestExecution } from "./execution/InngestExecution.js";
import { ApplyAllMiddlewareTransforms, Context, EventPayload, HashedOp, InvocationResult, InvokeTargetFunctionDefinition, SendEventOutput, StepOptions, StepOptionsOrId, TriggerEventFromFunction } from "../types.js";
import { ClientOptionsFromInngest, GetFunctionOutputRaw, GetStepTools, Inngest } from "./Inngest.js";
import { z } from "zod/v3";
import { AiAdapter, models } from "@inngest/ai";
import { StandardSchemaV1 } from "@standard-schema/spec";
//#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.ts.map