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.

336 lines (334 loc) 8.83 kB
const require_strings = require('../../helpers/strings.cjs'); const require_utils = require('./utils.cjs'); //#region src/components/middleware/manager.ts /** * Manages middleware. Hides middleware complexity from elsewhere in the * codebase. Not for for public use. */ var MiddlewareManager = class { fnArg; getStepState; /** * Whether any middleware defines `transformStepInput`. Used for perf * optimization. */ hasTransformStepInput; /** * Whether memoization has ended. Used for idempotency, since memoization must * only call once per request. */ memoizationEnded = false; fn; middleware; internalLogger; /** * Infinite recursion guard for `wrapStep`. Prevents a middleware from * wrapping steps it creates inside its own `wrapStep` via `ctx.step.run`. */ activeWrapStep = /* @__PURE__ */ new Set(); constructor(fnArg, getStepState, middleware = [], fn, logger) { this.fnArg = fnArg; this.getStepState = getStepState; this.middleware = middleware; this.fn = fn; this.internalLogger = logger; this.hasTransformStepInput = middleware.some((mw) => Boolean(mw?.transformStepInput)); } hasMiddleware() { return this.middleware.length > 0; } /** * Derives step-kind, extracts input, runs `transformStepInput` middleware, * and creates a deferred handler entry point. Does NOT build the wrapStep * chain — the caller should do that after any post-processing (e.g. ID * collision resolution) so middleware sees final values. */ async applyToStep(input) { const stepType = require_utils.stepTypeFromOpCode(input.op, input.opts, this.internalLogger); const stepInput = require_utils.stepInputFromOpts(stepType, input.opts); const stepInfo = this.buildStepInfo({ hashedId: input.hashedId, userlandId: input.userlandId, displayName: input.displayName, memoized: input.memoized, stepType, input: stepInput }); if (this.hasTransformStepInput) { const originalInput = stepInfo.input; const transformed = await this.transformStepInput(stepInfo); stepInfo.options = transformed.stepOptions; if (originalInput === void 0 && transformed.input.length === 0) stepInfo.input = void 0; else stepInfo.input = transformed.input; } let opName; if (stepType === "sleep" && stepInfo.input !== void 0) { if (!require_utils.isTimeStrInput(stepInfo.input[0])) throw new Error("Sleep time must be a string, number, or Date"); opName = require_strings.timeStr(stepInfo.input[0]); } const opOpts = require_utils.optsFromStepInput(stepType, stepInfo.input); let actualHandler; const entryPoint = async () => { if (!actualHandler) throw new Error("Handler not initialized"); return actualHandler(); }; const setActualHandler = (handler) => { actualHandler = handler; }; return { entryPoint, opName, opOpts, setActualHandler, stepInfo }; } buildStepInfo(opts) { return { hashedId: opts.hashedId, input: opts.input, memoized: opts.memoized, options: { id: opts.userlandId, ...opts.displayName !== void 0 && { name: opts.displayName } }, stepType: opts.stepType }; } buildSteps() { const result = {}; const stepState = this.getStepState(); for (const [id, op] of Object.entries(stepState)) if (op.error !== void 0) result[id] = { type: "error", error: op.error }; else if (op.input !== void 0) result[id] = { type: "input", input: op.input }; else result[id] = { type: "data", data: op.data }; return result; } /** * Apply transformFunctionInput middleware in forward order. * Each middleware builds on the previous result. */ async transformFunctionInput() { let result = { ctx: this.fnArg, fn: this.fn, steps: this.buildSteps() }; for (const mw of this.middleware) if (mw?.transformFunctionInput) result = await mw.transformFunctionInput(result); return result; } /** * Wrap a run handler with wrapFunctionHandler middlewares (reverse order for * onion layering, same pattern as wrapStepHandler). */ wrapRunHandler(handler) { let chain = handler; for (let i = this.middleware.length - 1; i >= 0; i--) { const mw = this.middleware[i]; if (mw?.wrapFunctionHandler) { const next = chain; chain = () => mw.wrapFunctionHandler({ ctx: this.fnArg, fn: this.fn, next }); } } return chain; } /** * Apply transformStepInput middleware in forward order. * Each middleware builds on the previous result. */ async transformStepInput(stepInfo) { let result = { fn: this.fn, stepInfo: { hashedId: stepInfo.hashedId, memoized: stepInfo.memoized, stepType: stepInfo.stepType }, stepOptions: { ...stepInfo.options }, input: [...stepInfo.input ?? []] }; for (const mw of this.middleware) if (mw?.transformStepInput) result = await mw.transformStepInput(result); return result; } /** * Wrap a step handler with wrapStep middlewares (reverse order for * onion layering). Returns the wrapped handler. */ buildWrapStepChain(handler, stepInfo) { let chain = handler; for (let i = this.middleware.length - 1; i >= 0; i--) { const mw = this.middleware[i]; if (mw?.wrapStep) { const next = chain; chain = () => { if (!mw.wrapStep) throw new require_utils.UnreachableError("wrapStep is undefined"); if (this.activeWrapStep.has(mw)) return next(); this.activeWrapStep.add(mw); const guardedNext = () => { this.activeWrapStep.delete(mw); return next().finally(() => { this.activeWrapStep.add(mw); }); }; return mw.wrapStep({ ctx: this.fnArg, fn: this.fn, next: guardedNext, stepInfo }).finally(() => { this.activeWrapStep.delete(mw); }); }; } } return chain; } async onStepStart(stepInfo) { for (const mw of this.middleware) if (mw?.onStepStart) try { await mw.onStepStart({ ctx: this.fnArg, fn: this.fn, stepInfo }); } catch (err) { this.internalLogger.error({ err, hook: "onStepStart", mw: mw.id }, "middleware error"); } } async onStepComplete(stepInfo, output) { for (const mw of this.middleware) if (mw?.onStepComplete) try { await mw.onStepComplete({ ctx: this.fnArg, fn: this.fn, output, stepInfo }); } catch (err) { this.internalLogger.error({ err, hook: "onStepComplete", mw: mw.id }, "middleware error"); } } /** * Build a wrapStepHandler chain around the actual step handler. * Called once per `step.run` attempt (not for memoized steps). * Simpler than buildWrapStepChain — no recursion guard needed. */ buildWrapStepHandlerChain(handler, stepInfo) { let chain = handler; for (let i = this.middleware.length - 1; i >= 0; i--) { const mw = this.middleware[i]; if (mw?.wrapStepHandler) { const next = chain; chain = () => mw.wrapStepHandler({ ctx: this.fnArg, fn: this.fn, next, stepInfo }); } } return chain; } async onStepError(stepInfo, error, isFinalAttempt) { for (const mw of this.middleware) if (mw?.onStepError) try { await mw.onStepError({ ctx: this.fnArg, error, fn: this.fn, isFinalAttempt, stepInfo }); } catch (err) { this.internalLogger.error({ err, hook: "onStepError", mw: mw.id }, "middleware error"); } } /** * Idempotent: safe to call from every code path that might end memoization. */ async onMemoizationEnd() { if (this.memoizationEnded) return; this.memoizationEnded = true; for (const mw of this.middleware) if (mw?.onMemoizationEnd) try { await mw.onMemoizationEnd({ ctx: this.fnArg, fn: this.fn }); } catch (err) { this.internalLogger.error({ err, hook: "onMemoizationEnd", mw: mw.id }, "middleware error"); } } async onRunStart() { for (const mw of this.middleware) if (mw?.onRunStart) try { await mw.onRunStart({ ctx: this.fnArg, fn: this.fn }); } catch (err) { this.internalLogger.error({ err, hook: "onRunStart", mw: mw.id }, "middleware error"); } } async onRunComplete(output) { for (const mw of this.middleware) if (mw?.onRunComplete) try { await mw.onRunComplete({ ctx: this.fnArg, fn: this.fn, output }); } catch (err) { this.internalLogger.error({ err, hook: "onRunComplete", mw: mw.id }, "middleware error"); } } async onRunError(error, isFinalAttempt) { for (const mw of this.middleware) if (mw?.onRunError) try { await mw.onRunError({ ctx: this.fnArg, error, fn: this.fn, isFinalAttempt }); } catch (err) { this.internalLogger.error({ err, hook: "onRunError", mw: mw.id }, "middleware error"); } } }; //#endregion exports.MiddlewareManager = MiddlewareManager; //# sourceMappingURL=manager.cjs.map