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.

1,045 lines (1,044 loc) • 63.4 kB
"use strict"; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _RequestSignature_instances, _RequestSignature_verifySignature; Object.defineProperty(exports, "__esModule", { value: true }); exports.InngestCommHandler = void 0; const debug_1 = __importDefault(require("debug")); const zod_1 = require("zod"); const ServerTiming_js_1 = require("../helpers/ServerTiming.js"); const consts_js_1 = require("../helpers/consts.js"); const devserver_js_1 = require("../helpers/devserver.js"); const enum_js_1 = require("../helpers/enum.js"); const env_js_1 = require("../helpers/env.js"); const errors_js_1 = require("../helpers/errors.js"); const functions_js_1 = require("../helpers/functions.js"); const net_js_1 = require("../helpers/net.js"); const promises_js_1 = require("../helpers/promises.js"); const stream_js_1 = require("../helpers/stream.js"); const strings_js_1 = require("../helpers/strings.js"); const types_js_1 = require("../types.js"); const version_js_1 = require("../version.js"); const InngestExecution_js_1 = require("./execution/InngestExecution.js"); /** * A schema for the response from Inngest when registering. */ const registerResSchema = zod_1.z.object({ status: zod_1.z.number().default(200), skipped: zod_1.z.boolean().optional().default(false), modified: zod_1.z.boolean().optional().default(false), error: zod_1.z.string().default("Successfully registered"), }); /** * `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 */ class InngestCommHandler { constructor(options) { var _a; /** * A private collection of functions that are being served. This map is used * to find and register functions when interacting with Inngest Cloud. */ this.fns = {}; this.env = (0, env_js_1.allProcessEnv)(); // Set input options directly so we can reference them later this._options = options; /** * v2 -> v3 migration error. * * If a serve handler is passed a client as the first argument, it'll be * spread in to these options. We should be able to detect this by picking * up a unique property on the object. */ if (Object.prototype.hasOwnProperty.call(options, "eventKey")) { throw new Error(`${consts_js_1.logPrefix} You've passed an Inngest client as the first argument to your serve handler. This is no longer supported in v3; please pass the Inngest client as the \`client\` property of an options object instead. See https://www.inngest.com/docs/sdk/migration`); } this.frameworkName = options.frameworkName; this.client = options.client; if (options.id) { console.warn(`${consts_js_1.logPrefix} The \`id\` serve option is deprecated and will be removed in v4`); } this.id = options.id || this.client.id; this.handler = options.handler; /** * Provide a hidden option to allow expired signatures to be accepted during * testing. */ this.allowExpiredSignatures = Boolean( // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, prefer-rest-params (_a = arguments["0"]) === null || _a === void 0 ? void 0 : _a.__testingAllowExpiredSignatures); // Ensure we filter any undefined functions in case of missing imports. this.rawFns = options.functions.filter(Boolean); if (this.rawFns.length !== options.functions.length) { // TODO PrettyError console.warn(`Some functions passed to serve() are undefined and misconfigured. Please check your imports.`); } this.fns = this.rawFns.reduce((acc, fn) => { const configs = fn["getConfig"]({ baseUrl: new URL("https://example.com"), appPrefix: this.id, }); const fns = configs.reduce((acc, { id }, index) => { return Object.assign(Object.assign({}, acc), { [id]: { fn, onFailure: Boolean(index) } }); }, {}); configs.forEach(({ id }) => { if (acc[id]) { // TODO PrettyError throw new Error(`Duplicate function ID "${id}"; please change a function's name or provide an explicit ID to avoid conflicts.`); } }); return Object.assign(Object.assign({}, acc), fns); }, {}); this.inngestRegisterUrl = new URL("/fn/register", this.apiBaseUrl); this.signingKey = options.signingKey; this.signingKeyFallback = options.signingKeyFallback; this._serveHost = options.serveHost || this.env[consts_js_1.envKeys.InngestServeHost]; this._servePath = options.servePath || this.env[consts_js_1.envKeys.InngestServePath]; this.skipSignatureValidation = options.skipSignatureValidation || false; const defaultLogLevel = "info"; this.logLevel = zod_1.z .enum(types_js_1.logLevels) .default(defaultLogLevel) .catch((ctx) => { this.log("warn", `Unknown log level passed: ${String(ctx.input)}; defaulting to ${defaultLogLevel}`); return defaultLogLevel; }) .parse(options.logLevel || this.env[consts_js_1.envKeys.InngestLogLevel]); if (this.logLevel === "debug") { /** * `debug` is an old library; sometimes its runtime detection doesn't work * for newer pairings of framework/runtime. * * One silly symptom of this is that `Debug()` returns an anonymous * function with no extra properties instead of a `Debugger` instance if * the wrong code is consumed following a bad detection. This results in * the following `.enable()` call failing, so we just try carefully to * enable it here. */ if (debug_1.default.enable && typeof debug_1.default.enable === "function") { debug_1.default.enable(`${consts_js_1.debugPrefix}:*`); } } const defaultStreamingOption = false; this.streaming = zod_1.z .union([zod_1.z.enum(["allow", "force"]), zod_1.z.literal(false)]) .default(defaultStreamingOption) .catch((ctx) => { this.log("warn", `Unknown streaming option passed: ${String(ctx.input)}; defaulting to ${String(defaultStreamingOption)}`); return defaultStreamingOption; }) .parse(options.streaming || this.env[consts_js_1.envKeys.InngestStreaming]); this.fetch = options.fetch ? (0, env_js_1.getFetch)(options.fetch) : this.client["fetch"]; } /** * 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. */ get apiBaseUrl() { return (this._options.baseUrl || this.env[consts_js_1.envKeys.InngestApiBaseUrl] || this.env[consts_js_1.envKeys.InngestBaseUrl] || this.client.apiBaseUrl || consts_js_1.defaultInngestApiBaseUrl); } /** * 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. */ get eventApiBaseUrl() { return (this._options.baseUrl || this.env[consts_js_1.envKeys.InngestEventApiBaseUrl] || this.env[consts_js_1.envKeys.InngestBaseUrl] || this.client.eventBaseUrl || consts_js_1.defaultInngestEventBaseUrl); } /** * 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`. */ get serveHost() { return this._serveHost || this.env[consts_js_1.envKeys.InngestServeHost]; } /** * 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. */ get servePath() { return this._servePath || this.env[consts_js_1.envKeys.InngestServePath]; } get hashedEventKey() { if (!this.client["eventKey"] || this.client["eventKey"] === consts_js_1.dummyEventKey) { return undefined; } return (0, strings_js_1.hashEventKey)(this.client["eventKey"]); } // hashedSigningKey creates a sha256 checksum of the signing key with the // same signing key prefix. get hashedSigningKey() { if (!this.signingKey) { return undefined; } return (0, strings_js_1.hashSigningKey)(this.signingKey); } get hashedSigningKeyFallback() { if (!this.signingKeyFallback) { return undefined; } return (0, strings_js_1.hashSigningKey)(this.signingKeyFallback); } /** * Returns a `boolean` representing whether this handler will stream responses * or not. Takes into account the user's preference and the platform's * capabilities. */ async shouldStream(actions) { const rawProbe = await actions.queryStringWithDefaults("testing for probe", consts_js_1.queryKeys.Probe); if (rawProbe !== undefined) { return false; } // We must be able to stream responses to continue. if (!actions.transformStreamingResponse) { return false; } // If the user has forced streaming, we should always stream. if (this.streaming === "force") { return true; } // If the user has allowed streaming, we should stream if the platform // supports it. return (this.streaming === "allow" && (0, env_js_1.platformSupportsStreaming)(this.frameworkName, this.env)); } /** * `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() { const handler = async (...args) => { var _a, _b; const timer = new ServerTiming_js_1.ServerTiming(); /** * Used for testing, allow setting action overrides externally when * calling the handler. Always search the final argument. */ const lastArg = args[args.length - 1]; const actionOverrides = typeof lastArg === "object" && lastArg !== null && "actionOverrides" in lastArg && typeof lastArg["actionOverrides"] === "object" && lastArg["actionOverrides"] !== null ? lastArg["actionOverrides"] : {}; /** * We purposefully `await` the handler, as it could be either sync or * async. */ const rawActions = Object.assign(Object.assign({}, (await timer .wrap("handler", () => this.handler(...args)) .catch((0, errors_js_1.rethrowError)("Serve handler failed to run")))), actionOverrides); /** * Map over every `action` in `rawActions` and create a new `actions` * object where each function is safely promisified with each access * requiring a reason. * * This helps us provide high quality errors about what's going wrong for * each access without having to wrap every access in a try/catch. */ const promisifiedActions = Object.entries(rawActions).reduce((acc, [key, value]) => { if (typeof value !== "function") { return acc; } return Object.assign(Object.assign({}, acc), { [key]: (reason, ...args) => { const errMessage = [ `Failed calling \`${key}\` from serve handler`, reason, ] .filter(Boolean) .join(" when "); const fn = () => value(...args); return (0, promises_js_1.runAsPromise)(fn) .catch((0, errors_js_1.rethrowError)(errMessage)) .catch((err) => { this.log("error", err); throw err; }); } }); }, {}); /** * Mapped promisified handlers from userland `serve()` function mixed in * with some helpers. */ const actions = Object.assign(Object.assign(Object.assign({}, promisifiedActions), { queryStringWithDefaults: async (reason, key) => { var _a; const url = await actions.url(reason); const ret = (await ((_a = actions.queryString) === null || _a === void 0 ? void 0 : _a.call(actions, reason, key, url))) || url.searchParams.get(key) || undefined; return ret; } }), actionOverrides); const [env, expectedServerKind] = await Promise.all([ (_a = actions.env) === null || _a === void 0 ? void 0 : _a.call(actions, "starting to handle request"), actions.headers("checking expected server kind", consts_js_1.headerKeys.InngestServerKind), ]); // Always make sure to merge whatever env we've been given with // `process.env`; some platforms may not provide all the necessary // environment variables or may use two sources. this.env = Object.assign(Object.assign({}, (0, env_js_1.allProcessEnv)()), env); const getInngestHeaders = () => (0, env_js_1.inngestHeaders)({ env: this.env, framework: this.frameworkName, client: this.client, expectedServerKind: expectedServerKind || undefined, extras: { "Server-Timing": timer.getHeader(), }, }); const assumedMode = (0, env_js_1.getMode)({ env: this.env, client: this.client }); if (assumedMode.isExplicit) { this._mode = assumedMode; } else { const serveIsProd = await ((_b = actions.isProduction) === null || _b === void 0 ? void 0 : _b.call(actions, "starting to handle request")); if (typeof serveIsProd === "boolean") { this._mode = new env_js_1.Mode({ type: serveIsProd ? "cloud" : "dev", isExplicit: false, }); } else { this._mode = assumedMode; } } this.upsertKeysFromEnv(); const methodP = actions.method("starting to handle request"); const headerPromises = [ consts_js_1.headerKeys.TraceParent, consts_js_1.headerKeys.TraceState, ].map(async (header) => { const value = await actions.headers(`fetching ${header} for forwarding`, header); return { header, value }; }); const contentLength = await actions .headers("checking signature for request", consts_js_1.headerKeys.ContentLength) .then((value) => { if (!value) { return undefined; } return parseInt(value, 10); }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const [signature, method, body] = await Promise.all([ actions .headers("checking signature for request", consts_js_1.headerKeys.Signature) .then((headerSignature) => { return headerSignature !== null && headerSignature !== void 0 ? headerSignature : undefined; }), methodP, methodP.then((method) => { if (method === "POST" || method === "PUT") { if (!contentLength) { // Return empty string because req.json() will throw an error. return ""; } return actions.body(`checking body for request signing as method is ${method}`); } return ""; }), ]); const signatureValidation = this.validateSignature(signature, body); const headersToForwardP = Promise.all(headerPromises).then((fetchedHeaders) => { return fetchedHeaders.reduce((acc, { header, value }) => { if (value) { acc[header] = value; } return acc; }, {}); }); const actionRes = timer.wrap("action", () => this.handleAction({ actions, timer, getInngestHeaders, reqArgs: args, signatureValidation, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment body, method, headers: headersToForwardP, })); /** * Prepares an action response by merging returned data to provide * trailing information such as `Server-Timing` headers. * * It should always prioritize the headers returned by the action, as they * may contain important information such as `Content-Type`. */ const prepareActionRes = async (res) => { var _a; const headers = Object.assign(Object.assign(Object.assign(Object.assign({}, getInngestHeaders()), (await headersToForwardP)), res.headers), (res.version === null ? {} : { [consts_js_1.headerKeys.RequestVersion]: ((_a = res.version) !== null && _a !== void 0 ? _a : InngestExecution_js_1.PREFERRED_EXECUTION_VERSION).toString(), })); let signature; try { signature = await signatureValidation.then((result) => { if (!result.success || !result.keyUsed) { return undefined; } return this.getResponseSignature(result.keyUsed, res.body); }); } catch (err) { // If we fail to sign, retun a 500 with the error. return Object.assign(Object.assign({}, res), { headers, body: (0, strings_js_1.stringify)((0, errors_js_1.serializeError)(err)), status: 500 }); } if (signature) { headers[consts_js_1.headerKeys.Signature] = signature; } return Object.assign(Object.assign({}, res), { headers }); }; if (await this.shouldStream(actions)) { const method = await actions.method("starting streaming response"); if (method === "POST") { const { stream, finalize } = await (0, stream_js_1.createStream)(); /** * Errors are handled by `handleAction` here to ensure that an * appropriate response is always given. */ void actionRes.then((res) => { return finalize(prepareActionRes(res)); }); return timer.wrap("res", () => { var _a; return (_a = actions.transformStreamingResponse) === null || _a === void 0 ? void 0 : _a.call(actions, "starting streaming response", { status: 201, headers: getInngestHeaders(), body: stream, version: null, }); }); } } return timer.wrap("res", async () => { return actionRes.then(prepareActionRes).then((actionRes) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return actions.transformResponse("sending back response", actionRes); }); }); }; /** * Some platforms check (at runtime) the length of the function being used * to handle an endpoint. If this is a variadic function, it will fail that * check. * * Therefore, we expect the arguments accepted to be the same length as the * `handler` function passed internally. * * We also set a name to avoid a common useless name in tracing such as * `"anonymous"` or `"bound function"`. * * https://github.com/getsentry/sentry-javascript/issues/3284 */ Object.defineProperties(handler, { name: { value: "InngestHandler", }, length: { value: this.handler.length, }, }); return handler; } get mode() { return this._mode; } set mode(m) { this._mode = m; if (m) { this.client["mode"] = m; } } /** * 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). */ async handleAction({ actions, timer, getInngestHeaders, reqArgs, signatureValidation, body, method, headers, }) { var _a; // This is when the request body is completely missing; it does not // include an empty body. This commonly happens when the HTTP framework // doesn't have body parsing middleware. const isMissingBody = body === undefined; try { let url = await actions.url("starting to handle request"); if (method === "POST") { if (isMissingBody) { this.log("error", "Missing body when executing, possibly due to missing request body middleware"); return { status: 500, headers: { "Content-Type": "application/json", }, body: (0, strings_js_1.stringify)((0, errors_js_1.serializeError)(new Error("Missing request body when executing, possibly due to missing request body middleware"))), version: undefined, }; } const validationResult = await signatureValidation; if (!validationResult.success) { return { status: 401, headers: { "Content-Type": "application/json", }, body: (0, strings_js_1.stringify)((0, errors_js_1.serializeError)(validationResult.err)), version: undefined, }; } const rawProbe = await actions.queryStringWithDefaults("testing for probe", consts_js_1.queryKeys.Probe); if (rawProbe) { const probe = (0, enum_js_1.enumFromValue)(consts_js_1.probe, rawProbe); if (!probe) { // If we're here, we've received a probe that we don't recognize. // Fail. return { status: 400, headers: { "Content-Type": "application/json", }, body: (0, strings_js_1.stringify)((0, errors_js_1.serializeError)(new Error(`Unknown probe "${rawProbe}"`))), version: undefined, }; } // Provide actions for every probe available. const probeActions = { [consts_js_1.probe.Trust]: () => ({ status: 200, headers: { "Content-Type": "application/json", }, body: "", version: undefined, }), }; return probeActions[probe](); } const fnId = await actions.queryStringWithDefaults("processing run request", consts_js_1.queryKeys.FnId); if (!fnId) { // TODO PrettyError throw new Error("No function ID found in request"); } const stepId = (await actions.queryStringWithDefaults("processing run request", consts_js_1.queryKeys.StepId)) || null; const { version, result } = this.runStep({ functionId: fnId, data: body, stepId, timer, reqArgs, headers: await headers, }); const stepOutput = await result; /** * Functions can return `undefined`, but we'll always convert this to * `null`, as this is appropriately serializable by JSON. */ const opDataUndefinedToNull = (op) => { op.data = (0, functions_js_1.undefinedToNull)(op.data); return op; }; const resultHandlers = { "function-rejected": (result) => { return { status: result.retriable ? 500 : 400, headers: Object.assign({ "Content-Type": "application/json", [consts_js_1.headerKeys.NoRetry]: result.retriable ? "false" : "true" }, (typeof result.retriable === "string" ? { [consts_js_1.headerKeys.RetryAfter]: result.retriable } : {})), body: (0, strings_js_1.stringify)((0, functions_js_1.undefinedToNull)(result.error)), version, }; }, "function-resolved": (result) => { return { status: 200, headers: { "Content-Type": "application/json", }, body: (0, strings_js_1.stringify)((0, functions_js_1.undefinedToNull)(result.data)), version, }; }, "step-not-found": (result) => { return { status: 500, headers: { "Content-Type": "application/json", [consts_js_1.headerKeys.NoRetry]: "false", }, body: (0, strings_js_1.stringify)({ error: `Could not find step "${result.step.displayName || result.step.id}" to run; timed out`, }), version, }; }, "step-ran": (result) => { const step = opDataUndefinedToNull(result.step); return { status: 206, headers: Object.assign({ "Content-Type": "application/json" }, (typeof result.retriable !== "undefined" ? Object.assign({ [consts_js_1.headerKeys.NoRetry]: result.retriable ? "false" : "true" }, (typeof result.retriable === "string" ? { [consts_js_1.headerKeys.RetryAfter]: result.retriable } : {})) : {})), body: (0, strings_js_1.stringify)([step]), version, }; }, "steps-found": (result) => { const steps = result.steps.map(opDataUndefinedToNull); return { status: 206, headers: { "Content-Type": "application/json", }, body: (0, strings_js_1.stringify)(steps), version, }; }, }; const handler = resultHandlers[stepOutput.type]; try { return await handler(stepOutput); } catch (err) { this.log("error", "Error handling execution result", err); throw err; } } // TODO: This feels hacky, so we should probably make it not hacky. const env = (_a = getInngestHeaders()[consts_js_1.headerKeys.Environment]) !== null && _a !== void 0 ? _a : null; if (method === "GET") { return { status: 200, body: (0, strings_js_1.stringify)(await this.introspectionBody({ actions, env, signatureValidation, url, })), headers: { "Content-Type": "application/json", }, version: undefined, }; } if (method === "PUT") { const [deployId, inBandSyncRequested] = await Promise.all([ actions .queryStringWithDefaults("processing deployment request", consts_js_1.queryKeys.DeployId) .then((deployId) => { return deployId === "undefined" ? undefined : deployId; }), Promise.resolve((0, env_js_1.parseAsBoolean)(this.env[consts_js_1.envKeys.InngestAllowInBandSync])) .then((allowInBandSync) => { if (allowInBandSync !== undefined && !allowInBandSync) { return consts_js_1.syncKind.OutOfBand; } return actions.headers("processing deployment request", consts_js_1.headerKeys.InngestSyncKind); }) .then((kind) => { return kind === consts_js_1.syncKind.InBand; }), ]); if (inBandSyncRequested) { if (isMissingBody) { this.log("error", "Missing body when syncing, possibly due to missing request body middleware"); return { status: 500, headers: { "Content-Type": "application/json", }, body: (0, strings_js_1.stringify)((0, errors_js_1.serializeError)(new Error("Missing request body when syncing, possibly due to missing request body middleware"))), version: undefined, }; } // Validation can be successful if we're in dev mode and did not // actually validate a key. In this case, also check that we did indeed // use a particular key to validate. const sigCheck = await signatureValidation; if (!sigCheck.success) { return { status: 401, body: (0, strings_js_1.stringify)({ code: "sig_verification_failed", }), headers: { "Content-Type": "application/json", }, version: undefined, }; } const res = types_js_1.inBandSyncRequestBodySchema.safeParse(body); if (!res.success) { return { status: 400, body: (0, strings_js_1.stringify)({ code: "invalid_request", message: res.error.message, }), headers: { "Content-Type": "application/json", }, version: undefined, }; } // We can trust the URL here because it's coming from // signature-verified request. url = this.reqUrl(new URL(res.data.url)); // This should be an in-band sync const respBody = await this.inBandRegisterBody({ actions, deployId, env, signatureValidation, url, }); return { status: 200, body: (0, strings_js_1.stringify)(respBody), headers: { "Content-Type": "application/json", [consts_js_1.headerKeys.InngestSyncKind]: consts_js_1.syncKind.InBand, }, version: undefined, }; } // If we're here, this is a legacy out-of-band sync const { status, message, modified } = await this.register(this.reqUrl(url), deployId, getInngestHeaders); return { status, body: (0, strings_js_1.stringify)({ message, modified }), headers: { "Content-Type": "application/json", [consts_js_1.headerKeys.InngestSyncKind]: consts_js_1.syncKind.OutOfBand, }, version: undefined, }; } } catch (err) { return { status: 500, body: (0, strings_js_1.stringify)(Object.assign({ type: "internal" }, (0, errors_js_1.serializeError)(err))), headers: { "Content-Type": "application/json", }, version: undefined, }; } return { status: 405, body: JSON.stringify({ message: "No action found; request was likely not POST, PUT, or GET", mode: this._mode, }), headers: {}, version: undefined, }; } runStep({ functionId, stepId, data, timer, reqArgs, headers, }) { var _a, _b; const fn = this.fns[functionId]; if (!fn) { // TODO PrettyError throw new Error(`Could not find function with ID "${functionId}"`); } const immediateFnData = (0, functions_js_1.parseFnData)(data); let { version } = immediateFnData; // Handle opting in to optimized parallelism in v3. if (version === InngestExecution_js_1.ExecutionVersion.V1 && ((_b = (_a = fn.fn)["shouldOptimizeParallelism"]) === null || _b === void 0 ? void 0 : _b.call(_a))) { version = InngestExecution_js_1.ExecutionVersion.V2; } const result = (0, promises_js_1.runAsPromise)(async () => { const anyFnData = await (0, functions_js_1.fetchAllFnData)({ data: immediateFnData, api: this.client["inngestApi"], version, }); if (!anyFnData.ok) { throw new Error(anyFnData.error); } const executionStarters = ((s) => s)({ [InngestExecution_js_1.ExecutionVersion.V0]: ({ event, events, steps, ctx, version }) => { var _a, _b, _c; const stepState = Object.entries(steps !== null && steps !== void 0 ? steps : {}).reduce((acc, [id, data]) => { return Object.assign(Object.assign({}, acc), { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment [id]: { id, data } }); }, {}); return { version, partialOptions: { client: this.client, runId: (ctx === null || ctx === void 0 ? void 0 : ctx.run_id) || "", data: { event: event, events: events, runId: (ctx === null || ctx === void 0 ? void 0 : ctx.run_id) || "", attempt: (_a = ctx === null || ctx === void 0 ? void 0 : ctx.attempt) !== null && _a !== void 0 ? _a : 0, }, stepState, requestedRunStep: stepId === "step" ? undefined : stepId || undefined, timer, isFailureHandler: fn.onFailure, stepCompletionOrder: (_c = (_b = ctx === null || ctx === void 0 ? void 0 : ctx.stack) === null || _b === void 0 ? void 0 : _b.stack) !== null && _c !== void 0 ? _c : [], reqArgs, headers, }, }; }, [InngestExecution_js_1.ExecutionVersion.V1]: ({ event, events, steps, ctx, version }) => { var _a, _b, _c; const stepState = Object.entries(steps !== null && steps !== void 0 ? steps : {}).reduce((acc, [id, result]) => { return Object.assign(Object.assign({}, acc), { [id]: result.type === "data" ? // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { id, data: result.data } : result.type === "input" ? // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { id, input: result.input } : { id, error: result.error } }); }, {}); return { version, partialOptions: { client: this.client, runId: (ctx === null || ctx === void 0 ? void 0 : ctx.run_id) || "", data: { event: event, events: events, runId: (ctx === null || ctx === void 0 ? void 0 : ctx.run_id) || "", attempt: (_a = ctx === null || ctx === void 0 ? void 0 : ctx.attempt) !== null && _a !== void 0 ? _a : 0, }, stepState, requestedRunStep: stepId === "step" ? undefined : stepId || undefined, timer, isFailureHandler: fn.onFailure, disableImmediateExecution: ctx === null || ctx === void 0 ? void 0 : ctx.disable_immediate_execution, stepCompletionOrder: (_c = (_b = ctx === null || ctx === void 0 ? void 0 : ctx.stack) === null || _b === void 0 ? void 0 : _b.stack) !== null && _c !== void 0 ? _c : [], reqArgs, headers, }, }; }, [InngestExecution_js_1.ExecutionVersion.V2]: ({ event, events, steps, ctx, version }) => { var _a, _b, _c; const stepState = Object.entries(steps !== null && steps !== void 0 ? steps : {}).reduce((acc, [id, result]) => { return Object.assign(Object.assign({}, acc), { [id]: result.type === "data" ? // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { id, data: result.data } : result.type === "input" ? // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment { id, input: result.input } : { id, error: result.error } }); }, {}); return { version, partialOptions: { client: this.client, runId: (ctx === null || ctx === void 0 ? void 0 : ctx.run_id) || "", data: { event: event, events: events, runId: (ctx === null || ctx === void 0 ? void 0 : ctx.run_id) || "", attempt: (_a = ctx === null || ctx === void 0 ? void 0 : ctx.attempt) !== null && _a !== void 0 ? _a : 0, }, stepState, requestedRunStep: stepId === "step" ? undefined : stepId || undefined, timer, isFailureHandler: fn.onFailure, disableImmediateExecution: ctx === null || ctx === void 0 ? void 0 : ctx.disable_immediate_execution, stepCompletionOrder: (_c = (_b = ctx === null || ctx === void 0 ? void 0 : ctx.stack) === null || _b === void 0 ? void 0 : _b.stack) !== null && _c !== void 0 ? _c : [], reqArgs, headers, }, }; }, }); const executionOptions = await executionStarters[version](anyFnData.value); return fn.fn["createExecution"](executionOptions).start(); }); return { version, result }; } configs(url) { const configs = Object.values(this.rawFns).reduce((acc, fn) => [ ...acc, ...fn["getConfig"]({ baseUrl: url, appPrefix: this.id }), ], []); for (const config of configs) { const check = types_js_1.functionConfigSchema.safeParse(config); if (!check.success) { const errors = check.error.errors.map((err) => err.message).join("; "); this.log("warn", `Config invalid for function "${config.id}" : ${errors}`); } } return configs; } /** * 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. */ reqUrl(url) { let ret = new URL(url); const serveHost = this.serveHost || this.env[consts_js_1.envKeys.InngestServeHost]; const servePath = this.servePath || this.env[consts_js_1.envKeys.InngestServePath]; if (servePath) { ret.pathname = servePath; } if (serveHost) { ret = new URL(ret.pathname + ret.search, serveHost); } return ret; } registerBody({ url, deployId, }) { const body = { url: url.href, deployType: "ping", framework: this.frameworkName, appName: this.id, functions: this.configs(url), sdk: `js:v${version_js_1.version}`, v: "0.1", deployId: deployId || undefined, capabilities: { trust_probe: "v1", connect: "v1", }, appVersion: this.client.appVersion, }; return body; } async inBandRegisterBody({ actions, deployId, env, signatureValidation, url, }) { const registerBody = this.registerBody({ deployId, url }); const introspectionBody = await this.introspectionBody({ actions, env, signatureValidation, url, }); const body = { app_id: this.id, appVersion: this.client.appVersion, capabilities: registerBody.capabilities, env, framework: registerBody.framework,