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
JavaScript
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