@inngest/middleware-validation
Version:
Schema validation middleware for Inngest.
175 lines (174 loc) • 9.2 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validationMiddleware = void 0;
const inngest_1 = require("inngest");
const zod_1 = require("zod");
/**
* Middleware that validates events using Zod schemas passed using
* `EventSchemas.fromZod()`.
*/
const validationMiddleware = (opts) => {
const mw = new inngest_1.InngestMiddleware({
name: "Inngest: Runtime schema validation",
init({ client }) {
/**
* Given an `event`, validate it against its schema.
*/
const validateEvent = (event_1, ...args_1) => __awaiter(this, [event_1, ...args_1], void 0, function* (event, potentialInvokeEvents = []) {
var _a, _b;
let schemasToAttempt = new Set([event.name]);
let hasSchema = false;
/**
* Trust internal events; don't allow overwriting their typing.
*/
if (event.name.startsWith("inngest/")) {
if (event.name !== inngest_1.internalEvents.FunctionInvoked) {
return event;
}
/**
* If this is an `inngest/function.invoked` event, try validating the
* payload against one of the function's schemas.
*/
schemasToAttempt = new Set(potentialInvokeEvents);
hasSchema = [...schemasToAttempt.values()].some((schemaName) => {
var _a;
return Boolean((_a = client["schemas"]) === null || _a === void 0 ? void 0 : _a["runtimeSchemas"][schemaName]);
});
}
else {
hasSchema = Boolean((_a = client["schemas"]) === null || _a === void 0 ? void 0 : _a["runtimeSchemas"][event.name]);
}
if (!hasSchema) {
if (opts === null || opts === void 0 ? void 0 : opts.disallowSchemalessEvents) {
throw new inngest_1.NonRetriableError(`Event "${event.name}" has no schema defined; disallowing`);
}
return event;
}
const errors = {};
for (const schemaName of schemasToAttempt) {
try {
const schema = (_b = client["schemas"]) === null || _b === void 0 ? void 0 : _b["runtimeSchemas"][schemaName];
/**
* The schema could be a full Zod object.
*/
if (helpers.isZodObject(schema)) {
const check = yield schema.passthrough().safeParseAsync(event);
if (check.success) {
return check.data;
}
throw new inngest_1.NonRetriableError(`${check.error.name}: ${check.error.message}`);
}
/**
* The schema could also be a regular object with Zod objects
* inside.
*/
if (helpers.isObject(schema)) {
// It could be a partial schema; validate each field
return yield Object.keys(schema).reduce((acc, key) => __awaiter(this, void 0, void 0, function* () {
const fieldSchema = schema[key];
const eventField = event[key];
if (!helpers.isZodObject(fieldSchema) || !eventField) {
return acc;
}
const check = yield fieldSchema
.passthrough()
.safeParseAsync(eventField);
if (check.success) {
return Object.assign(Object.assign({}, (yield acc)), { [key]: check.data });
}
throw new inngest_1.NonRetriableError(`${check.error.name}: ${check.error.message}`);
}), Promise.resolve(Object.assign({}, event)));
}
/**
* Didn't find anything? Throw or warn.
*
* We only allow this for assessing single schemas, as otherwise
* we're assessing an invocation would could be multiple.
*/
if ((opts === null || opts === void 0 ? void 0 : opts.disallowUnknownSchemas) && schemasToAttempt.size === 1) {
throw new inngest_1.NonRetriableError(`Event "${event.name}" has an unknown schema; disallowing`);
}
else {
console.warn("Unknown schema found; cannot validate, but allowing");
}
}
catch (err) {
errors[schemaName] = err;
}
}
if (Object.keys(errors).length) {
throw new inngest_1.NonRetriableError(`Event "${event.name}" failed validation:\n\n${Object.keys(errors)
.map((key) => `Using ${key}: ${errors[key].message}`)
.join("\n\n")}`);
}
return event;
});
return Object.assign(Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.disableIncomingValidation)
? {}
: {
onFunctionRun(_a) {
return __awaiter(this, arguments, void 0, function* ({ fn }) {
const backupEvents = (fn.opts.triggers || []).reduce((acc, trigger) => {
if (trigger.event) {
return [...acc, trigger.event];
}
return acc;
}, []);
return {
transformInput(_a) {
return __awaiter(this, arguments, void 0, function* ({ ctx: { events } }) {
const validatedEvents = yield Promise.all(events.map((event) => {
return validateEvent(event, backupEvents);
}));
return {
ctx: {
event: validatedEvents[0],
events: validatedEvents,
},
};
});
},
};
});
},
})), ((opts === null || opts === void 0 ? void 0 : opts.disableOutgoingValidation)
? {}
: {
onSendEvent() {
return __awaiter(this, void 0, void 0, function* () {
return {
transformInput(_a) {
return __awaiter(this, arguments, void 0, function* ({ payloads }) {
return {
payloads: yield Promise.all(payloads.map((payload) => {
return validateEvent(payload);
})),
};
});
},
};
});
},
}));
},
});
return mw;
};
exports.validationMiddleware = validationMiddleware;
const helpers = {
isZodObject: (value) => {
return value instanceof zod_1.ZodType && value._def.typeName === "ZodObject";
},
isObject: (value) => {
return typeof value === "object" && value !== null && !Array.isArray(value);
},
};
;