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.
585 lines (583 loc) • 21.7 kB
TypeScript
import { ExecutionVersion } from "../helpers/consts.js";
import { MaybePromise } from "../helpers/types.js";
import { ServerTiming } from "../helpers/ServerTiming.js";
import { Middleware } from "./middleware/middleware.js";
import { ExecutionResult, InngestExecutionOptions } from "./execution/InngestExecution.js";
import { InngestFunction } from "./InngestFunction.js";
import { AsyncResponseValue, AuthenticatedIntrospection, FunctionConfig, InBandRegisterRequest, RegisterOptions, RegisterRequest, UnauthenticatedIntrospection } from "../types.js";
import { Inngest } from "./Inngest.js";
import { Env } from "../helpers/env.js";
//#region src/components/InngestCommHandler.d.ts
/**
* A set of options that can be passed to a serve handler, intended to be used
* by internal and custom serve handlers to provide a consistent interface.
*
* @public
*/
interface ServeHandlerOptions extends RegisterOptions {
/**
* The `Inngest` instance used to declare all functions.
*/
client: Inngest.Like;
/**
* An array of the functions to serve and register with Inngest.
*/
functions: readonly InngestFunction.Like[];
}
/**
* Parameters passed to the asyncRedirectUrl function.
*/
interface AsyncRedirectUrlParams {
/**
* The unique identifier for this run.
*/
runId: string;
/**
* The token used to authenticate the request to fetch run output.
*/
token: string;
}
interface SyncHandlerOptions extends RegisterOptions {
/**
* The `Inngest` instance used to declare all functions.
*/
client: Inngest.Like;
/**
* The type of response you wish to return to an API endpoint when using steps
* within it and we must transition to {@link StepMode.Async}.
*
* In most cases, this defaults to {@link AsyncResponseType.Redirect}.
*/
asyncResponse?: AsyncResponseValue;
/**
* Custom URL to redirect to when switching from sync to async mode.
*
* Can be:
* - A string path (e.g., "/api/inngest/poll") - resolved relative to request origin
* - A function that receives `{ runId, token }` and returns a full URL
*
* When a string path is provided, `runId` and `token` query parameters are
* automatically appended.
*
* @example
* ```ts
* // String path - resolved relative to request origin
* asyncRedirectUrl: "/api/inngest/poll"
*
* // Function - full control over URL construction
* asyncRedirectUrl: ({ runId, token }) =>
* `https://my-app.com/poll?run=${runId}&t=${token}`
* ```
*/
asyncRedirectUrl?: string | ((params: AsyncRedirectUrlParams) => string | Promise<string>);
/**
* If defined, this sets the function ID that represents this endpoint.
* Without this set, it defaults to using the detected method and path of the
* request, for example: `GET /api/my-endpoint`.
*/
functionId?: string;
/**
* Specifies the maximum number of retries for all steps.
*
* Can be a number from `0` to `20`. Defaults to `3`.
*/
retries?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
}
type SyncAdapterOptions = Omit<SyncHandlerOptions, "client">;
interface InngestCommHandlerOptions<Input extends any[] = any[], Output = any, StreamOutput = any> extends RegisterOptions {
/**
* The name of the framework this handler is designed for. Should be
* lowercase, alphanumeric characters inclusive of `-` and `/`.
*
* This should never be defined by the user; a {@link ServeHandler} should
* abstract this.
*/
frameworkName: string;
/**
* The name of this serve handler, e.g. `"My App"`. It's recommended that this
* value represents the overarching app/service that this set of functions is
* being served from.
*
* This can also be an `Inngest` client, in which case the name given when
* instantiating the client is used. This is useful if you're sending and
* receiving events from the same service, as you can reuse a single
* definition of Inngest.
*/
client: Inngest.Like;
/**
* An array of the functions to serve and register with Inngest.
*/
functions?: readonly InngestFunction.Like[];
/**
* The `handler` is the function that will be called with your framework's
* request arguments and returns a set of functions that the SDK will use to
* access various parts of the request, such as the body, headers, and query
* string parameters.
*
* It also defines how to transform a response from the SDK into a response
* that your framework can understand, ensuring headers, status codes, and
* body are all set correctly.
*
* @example
* ```ts
* function handler (req: Request, res: Response) {
* return {
* method: () => req.method,
* body: () => req.json(),
* headers: (key) => req.headers.get(key),
* url: () => req.url,
* transformResponse: ({ body, headers, status }) => {
* return new Response(body, { status, headers });
* },
* };
* };
* ```
*
* See any existing handler for a full example.
*/
handler: Handler<Input, Output, StreamOutput>;
skipSignatureValidation?: boolean;
/**
* Options for when this comm handler executes a synchronous (API) function.
*/
syncOptions?: SyncHandlerOptions;
}
/**
* `InngestCommHandler` is a class for handling incoming requests from Inngest (or
* Inngest's tooling such as the dev server or CLI) and taking appropriate
* action for any served functions.
*
* All handlers (Next.js, RedwoodJS, Remix, Deno Fresh, etc.) are created using
* this class; the exposed `serve` function will - most commonly - create an
* instance of `InngestCommHandler` and then return `instance.createHandler()`.
*
* See individual parameter details for more information, or see the
* source code for an existing handler, e.g.
* {@link https://github.com/inngest/inngest-js/blob/main/src/next.ts}
*
* @example
* ```
* // my-custom-handler.ts
* import {
* InngestCommHandler,
* type ServeHandlerOptions,
* } from "./components/InngestCommHandler";
*
* export const serve = (options: ServeHandlerOptions) => {
* const handler = new InngestCommHandler({
* frameworkName: "my-custom-handler",
* ...options,
* handler: (req: Request) => {
* return {
* body: () => req.json(),
* headers: (key) => req.headers.get(key),
* method: () => req.method,
* url: () => new URL(req.url, `https://${req.headers.get("host") || ""}`),
* transformResponse: ({ body, status, headers }) => {
* return new Response(body, { status, headers });
* },
* };
* },
* });
*
* return handler.createHandler();
* };
* ```
*
* @public
*/
declare class InngestCommHandler<Input extends any[] = any[], Output = any, StreamOutput = any> {
/**
* The handler specified during instantiation of the class.
*/
readonly handler: Handler;
/**
* The URL of the Inngest function registration endpoint.
*/
private readonly inngestRegisterUrl;
/**
* The name of the framework this handler is designed for. Should be
* lowercase, alphanumeric characters inclusive of `-` and `/`.
*/
protected readonly frameworkName: string;
/**
* The origin used to access the Inngest serve endpoint, e.g.:
*
* "https://myapp.com" or "https://myapp.com:1234"
*
* By default, the library will try to infer this using request details such
* as the "Host" header and request path, but sometimes this isn't possible
* (e.g. when running in a more controlled environments such as AWS Lambda or
* when dealing with proxies/redirects).
*
* Provide the custom origin here to ensure that the path is reported
* correctly when registering functions with Inngest.
*
* To also provide a custom path, use `servePath`.
*/
private readonly _serveOrigin;
/**
* The path to the Inngest serve endpoint. e.g.:
*
* "/some/long/path/to/inngest/endpoint"
*
* By default, the library will try to infer this using request details such
* as the "Host" header and request path, but sometimes this isn't possible
* (e.g. when running in a more controlled environments such as AWS Lambda or
* when dealing with proxies/redirects).
*
* Provide the custom path (excluding the hostname) here to ensure that the
* path is reported correctly when registering functions with Inngest.
*
* To also provide a custom hostname, use `serveOrigin`.
*/
private readonly _servePath;
protected readonly streaming: RegisterOptions["streaming"];
/**
* A private collection of just Inngest functions, as they have been passed
* when instantiating the class.
*/
private readonly rawFns;
private readonly client;
/**
* A private collection of functions that are being served. This map is used
* to find and register functions when interacting with Inngest Cloud.
*/
private readonly fns;
private env;
private allowExpiredSignatures;
private readonly _options;
private readonly skipSignatureValidation;
constructor(options: InngestCommHandlerOptions<Input, Output, StreamOutput>);
/**
* The origin used to access the Inngest serve endpoint, e.g.:
*
* "https://myapp.com"
*
* By default, the library will try to infer this using request details such
* as the "Host" header and request path, but sometimes this isn't possible
* (e.g. when running in a more controlled environments such as AWS Lambda or
* when dealing with proxies/redirects).
*
* Provide the custom origin here to ensure that the path is reported
* correctly when registering functions with Inngest.
*
* To also provide a custom path, use `servePath`.
*/
protected get serveOrigin(): string | undefined;
/**
* The path to the Inngest serve endpoint. e.g.:
*
* "/some/long/path/to/inngest/endpoint"
*
* By default, the library will try to infer this using request details such
* as the "Host" header and request path, but sometimes this isn't possible
* (e.g. when running in a more controlled environments such as AWS Lambda or
* when dealing with proxies/redirects).
*
* Provide the custom path (excluding the hostname) here to ensure that the
* path is reported correctly when registering functions with Inngest.
*
* To also provide a custom hostname, use `serveOrigin`.
*
* This is a getter to encourage checking the environment for the serve path
* each time it's accessed, as it may change during execution.
*/
protected get servePath(): string | undefined;
private get hashedEventKey();
private get hashedSigningKey();
private get hashedSigningKeyFallback();
/**
* Returns a `boolean` representing whether this handler will stream responses
* or not. Takes into account the user's preference and the platform's
* capabilities.
*/
private shouldStream;
private isInngestReq;
/**
* Start handling a request, setting up environments, modes, and returning
* some helpers.
*/
private initRequest;
/**
* `createSyncHandler` should be used to return a type-equivalent version of
* the `handler` specified during instantiation.
*/
createSyncHandler<THandler extends (...args: Input) => Promise<Awaited<Output>>>(): (handler: THandler) => THandler;
/**
* `createHandler` should be used to return a type-equivalent version of the
* `handler` specified during instantiation.
*
* @example
* ```
* // my-custom-handler.ts
* import {
* InngestCommHandler,
* type ServeHandlerOptions,
* } from "./components/InngestCommHandler";
*
* export const serve = (options: ServeHandlerOptions) => {
* const handler = new InngestCommHandler({
* frameworkName: "my-custom-handler",
* ...options,
* handler: (req: Request) => {
* return {
* body: () => req.json(),
* headers: (key) => req.headers.get(key),
* method: () => req.method,
* url: () => new URL(req.url, `https://${req.headers.get("host") || ""}`),
* transformResponse: ({ body, status, headers }) => {
* return new Response(body, { status, headers });
* },
* };
* },
* });
*
* return handler.createHandler();
* };
* ```
*/
createHandler<THandler extends (...args: Input) => Promise<Awaited<Output>>>(): THandler;
/**
* Given a set of actions that let us access the incoming request, create an
* event that repesents a run starting from an HTTP request.
*/
private createHttpEvent;
private handleSyncRequest;
private handleAsyncRequest;
private getActions;
private wrapHandler;
/**
* Given a set of functions to check if an action is available from the
* instance's handler, enact any action that is found.
*
* This method can fetch varying payloads of data, but ultimately is the place
* where _decisions_ are made regarding functionality.
*
* For example, if we find that we should be viewing the UI, this function
* will decide whether the UI should be visible based on the payload it has
* found (e.g. env vars, options, etc).
*/
private handleAction;
protected runStep({
actions,
functionId,
stepId,
data,
timer,
reqArgs,
headers,
fn,
forceExecution,
headerReqVersion,
requestInfo,
mwInstances
}: {
actions: HandlerResponseWithErrors;
functionId: string;
stepId: string | null;
data: unknown;
timer: ServerTiming;
reqArgs: unknown[];
headers: Record<string, string>;
fn: {
fn: InngestFunction.Any;
onFailure: boolean;
};
forceExecution: boolean;
headerReqVersion?: ExecutionVersion;
requestInfo?: InngestExecutionOptions["requestInfo"];
mwInstances?: Middleware.BaseMiddleware[];
}): {
version: ExecutionVersion;
result: Promise<ExecutionResult>;
};
protected configs(url: URL): FunctionConfig[];
/**
* Return an Inngest serve endpoint URL given a potential `path` and `host`.
*
* Will automatically use the `serveOrigin` and `servePath` if they have been
* set when registering.
*/
protected reqUrl(url: URL): URL;
protected registerBody({
url,
deployId
}: {
url: URL;
/**
* Non-optional to ensure we always consider if we have a deploy ID
* available to us to use.
*/
deployId: string | undefined | null;
}): RegisterRequest;
protected inBandRegisterBody({
actions,
deployId,
env,
signatureValidation,
url
}: {
actions: HandlerResponseWithErrors;
/**
* Non-optional to ensure we always consider if we have a deploy ID
* available to us to use.
*/
deployId: string | undefined | null;
env: string | null;
signatureValidation: ReturnType<InngestCommHandler["validateSignature"]>;
url: URL;
}): Promise<InBandRegisterRequest>;
protected introspectionBody({
actions,
env,
signatureValidation,
url
}: {
actions: HandlerResponseWithErrors;
env: string | null;
signatureValidation: ReturnType<InngestCommHandler["validateSignature"]>;
url: URL;
}): Promise<UnauthenticatedIntrospection | AuthenticatedIntrospection>;
protected register(url: URL, deployId: string | undefined | null, getHeaders: () => Promise<Record<string, string>>): Promise<{
status: number;
message: string;
modified: boolean;
}>;
/**
* Check that the current mode has the configuration it requires.
* Returns `true` if valid, `false` if not.
*/
checkModeConfiguration(): boolean;
/**
* Validate the signature of a request and return the signing key used to
* validate it.
*/
protected validateSignature(sig: string | undefined, body: unknown): Promise<{
success: true;
keyUsed: string;
} | {
success: false;
err: Error;
}>;
protected getResponseSignature(key: string, body: string): Promise<string>;
}
/**
* The broad definition of a handler passed when instantiating an
* {@link InngestCommHandler} instance.
*/
type Handler<Input extends any[] = any[], Output = any, StreamOutput = any> = (...args: Input) => HandlerResponse<Output, StreamOutput>;
type HandlerResponse<Output = any, StreamOutput = any> = {
body: () => MaybePromise<any>;
env?: () => MaybePromise<Env | undefined>;
headers: (key: string) => MaybePromise<string | null | undefined>;
method: () => MaybePromise<string>;
queryString?: (key: string, url: URL) => MaybePromise<string | null | undefined>;
url: () => MaybePromise<URL>;
/**
* The `transformResponse` function receives the output of the Inngest SDK and
* can decide how to package up that information to appropriately return the
* information to Inngest.
*
* Mostly, this is taking the given parameters and returning a new `Response`.
*
* The function is passed an {@link ActionResponse}, an object containing a
* `status` code, a `headers` object, and a stringified `body`. This ensures
* you can appropriately handle the response, including use of any required
* parameters such as `res` in Express-/Connect-like frameworks.
*/
transformResponse: (res: ActionResponse<string>) => Output;
/**
* The `transformStreamingResponse` function, if defined, declares that this
* handler supports streaming responses back to Inngest. This is useful for
* functions that are expected to take a long time, as edge streaming can
* often circumvent restrictive request timeouts and other limitations.
*
* If your handler does not support streaming, do not define this function.
*
* It receives the output of the Inngest SDK and can decide how to package
* up that information to appropriately return the information in a stream
* to Inngest.
*
* Mostly, this is taking the given parameters and returning a new `Response`.
*
* The function is passed an {@link ActionResponse}, an object containing a
* `status` code, a `headers` object, and `body`, a `ReadableStream`. This
* ensures you can appropriately handle the response, including use of any
* required parameters such as `res` in Express-/Connect-like frameworks.
*/
transformStreamingResponse?: (res: ActionResponse<ReadableStream>) => StreamOutput;
/**
* TODO Needed to give folks a chance to wrap arguments if they need to in
* order to extract the request body so that it can be sent back to Inngest
* during either sync or async calls.
*
* This is because usually they do not interact directly with e.g. the
* `Response` object, but with sync mode they do, so we need to provide hooks
* to let us access the body.
*/
experimentalTransformSyncRequest?: (...args: unknown[]) => MaybePromise<unknown>;
/**
* TODO Needed to give folks a chance to transform the response from their own
* code to an Inngestish response. This is only needed so that sync mode can
* checkpoint the response if we've gone through the entire run with no
* interruptions.
*
* Because of its location when being specified, we have scoped access to the
* `reqArgs` (e.g. `req` and `res`), so we don't need to pass them here.
*/
experimentalTransformSyncResponse?: (data: unknown) => MaybePromise<Omit<ActionResponse, "version">>;
};
/**
* The response from the Inngest SDK before it is transformed in to a
* framework-compatible response by an {@link InngestCommHandler} instance.
*/
interface ActionResponse<TBody extends string | ReadableStream = string> {
/**
* The HTTP status code to return.
*/
status: number;
/**
* The headers to return in the response.
*/
headers: Record<string, string>;
/**
* A stringified body to return.
*/
body: TBody;
/**
* The version of the execution engine that was used to run this action.
*
* If the action didn't use the execution engine (for example, a GET request
* as a health check) or would have but errored before reaching it, this will
* be `undefined`.
*
* If the version should be entirely omitted from the response (for example,
* when sending preliminary headers when streaming), this will be `null`.
*/
version: ExecutionVersion | null | undefined;
}
/**
* A version of {@link HandlerResponse} where each function is safely
* promisified and requires a reason for each access.
*
* This enables us to provide accurate errors for each access without having to
* wrap every access in a try/catch.
*/
type ActionHandlerResponseWithErrors = { [K in keyof HandlerResponse]: NonNullable<HandlerResponse[K]> extends ((...args: infer Args) => infer R) ? R extends MaybePromise<infer PR> ? (errMessage: string, ...args: Args) => Promise<PR> : (errMessage: string, ...args: Args) => Promise<R> : HandlerResponse[K] };
/**
* A version of {@link ActionHandlerResponseWithErrors} that includes helper
* functions that provide sensible defaults on top of the direct access given
* from the bare response.
*/
interface HandlerResponseWithErrors extends ActionHandlerResponseWithErrors {
/**
* Fetch a query string value from the request. If no `querystring` action has
* been provided by the `serve()` handler, this will fall back to using the
* provided URL present in the request to parse the query string from instead.
*/
queryStringWithDefaults: (reason: string, key: string) => Promise<string | undefined>;
}
//#endregion
export { ActionResponse, InngestCommHandler, ServeHandlerOptions, SyncAdapterOptions, SyncHandlerOptions };
//# sourceMappingURL=InngestCommHandler.d.ts.map