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.
516 lines • 20.8 kB
TypeScript
import { ServerTiming } from "../helpers/ServerTiming.js";
import { Mode, type Env } from "../helpers/env.js";
import { type MaybePromise } from "../helpers/types.js";
import { type AuthenticatedIntrospection, type FunctionConfig, type InBandRegisterRequest, type LogLevel, type RegisterOptions, type RegisterRequest, type UnauthenticatedIntrospection } from "../types.js";
import { type Inngest } from "./Inngest.js";
import { type InngestFunction } from "./InngestFunction.js";
import { ExecutionVersion, type ExecutionResult } from "./execution/InngestExecution.js";
/**
* 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
*/
export 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[];
}
export interface InternalServeHandlerOptions extends ServeHandlerOptions {
/**
* Can be used to override the framework name given to a particular serve
* handler.
*/
frameworkName?: string;
}
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;
}
/**
* `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
*/
export declare class InngestCommHandler<Input extends any[] = any[], Output = any, StreamOutput = any> {
/**
* The ID 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.
*/
readonly id: string;
/**
* 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 signing key used to validate requests from Inngest. This is
* intentionally mutable so that we can pick up the signing key from the
* environment during execution if needed.
*/
protected signingKey: string | undefined;
/**
* The same as signingKey, except used as a fallback when auth fails using the
* primary signing key.
*/
protected signingKeyFallback: string | undefined;
/**
* A property that can be set to indicate whether we believe we are in
* production mode.
*
* Should be set every time a request is received.
*/
protected _mode: Mode | undefined;
/**
* The localized `fetch` implementation used by this handler.
*/
private readonly fetch;
/**
* The host 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 hostname here to ensure that the path is reported
* correctly when registering functions with Inngest.
*
* To also provide a custom path, use `servePath`.
*/
private readonly _serveHost;
/**
* 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 `serveHost`.
*/
private readonly _servePath;
/**
* The minimum level to log from the Inngest serve handler.
*/
protected readonly logLevel: LogLevel;
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>);
/**
* Get the API base URL for the Inngest API.
*
* This is a getter to encourage checking the environment for the API base URL
* each time it's accessed, as it may change during execution.
*/
protected get apiBaseUrl(): string;
/**
* Get the event API base URL for the Inngest API.
*
* This is a getter to encourage checking the environment for the event API
* base URL each time it's accessed, as it may change during execution.
*/
protected get eventApiBaseUrl(): string;
/**
* The host 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 hostname here to ensure that the path is reported
* correctly when registering functions with Inngest.
*
* To also provide a custom path, use `servePath`.
*/
protected get serveHost(): 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 `serveHost`.
*
* 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;
/**
* `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(): (...args: Input) => Promise<Awaited<Output>>;
private get mode();
private set mode(value);
/**
* 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({ functionId, stepId, data, timer, reqArgs, headers, }: {
functionId: string;
stepId: string | null;
data: unknown;
timer: ServerTiming;
reqArgs: unknown[];
headers: Record<string, string>;
}): {
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 `serveHost` 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: () => Record<string, string>): Promise<{
status: number;
message: string;
modified: boolean;
}>;
/**
* Given an environment, upsert any missing keys. This is useful in
* situations where environment variables are passed directly to handlers or
* are otherwise difficult to access during initialization.
*/
private upsertKeysFromEnv;
/**
* 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): string;
/**
* Log to stdout/stderr if the log level is set to include the given level.
* The default log level is `"info"`.
*
* This is an abstraction over `console.log` and will try to use the correct
* method for the given log level. For example, `log("error", "foo")` will
* call `console.error("foo")`.
*/
protected log(level: LogLevel, ...args: unknown[]): void;
}
/**
* The broad definition of a handler passed when instantiating an
* {@link InngestCommHandler} instance.
*/
export type Handler<Input extends any[] = any[], Output = any, StreamOutput = any> = (...args: Input) => HandlerResponse<Output, StreamOutput>;
export type HandlerResponse<Output = any, StreamOutput = any> = {
body: () => MaybePromise<any>;
env?: () => MaybePromise<Env | undefined>;
headers: (key: string) => MaybePromise<string | null | undefined>;
/**
* Whether the current environment is production. This is used to determine
* some functionality like whether to connect to the dev server or whether to
* show debug logging.
*
* If this is not provided--or is provided and returns `undefined`--we'll try
* to automatically detect whether we're in production by checking various
* environment variables.
*/
isProduction?: () => MaybePromise<boolean | 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;
};
/**
* The response from the Inngest SDK before it is transformed in to a
* framework-compatible response by an {@link InngestCommHandler} instance.
*/
export 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.
*/
export 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.
*/
export 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>;
}
export {};
//# sourceMappingURL=InngestCommHandler.d.ts.map