UNPKG

opinionated-machine

Version:

Very opinionated DI framework for fastify, built on top of awilix

80 lines 3.53 kB
import { gatewayMetadataSchema } from "./gatewayMetadata.js"; import { GATEWAY_METADATA_SYMBOL } from "./gatewaySymbol.js"; /** * Validate gateway metadata and stamp it onto a route via the * `GATEWAY_METADATA_SYMBOL` non-enumerable property. * * Shared between `withGatewayMetadata` (the post-hoc helper) and * `buildApiRoute` (which accepts `gatewayMetadata` inline via its options). * Centralising the validate-and-stamp logic here keeps both authoring styles * behaviourally identical: same Zod errors at the call site, same hidden * symbol storage, same value visible to `readGatewayMetadata` and * `buildGatewayManifest`. * * Parameter is `unknown` because the contract-narrowed `GatewayMetadata<C>` * shapes from the two call sites aren't structurally assignable to each * other (variant `ContractRateLimitKey<C>`), and the runtime Zod schema is * the actual source of truth either way. */ export function attachGatewayMetadata(route, metadata) { // Validate eagerly so a bad shape fails at the call site (with a clean // Zod issue path) rather than later when DIContext.buildGatewayManifest // walks every route at once. const validated = gatewayMetadataSchema.parse(metadata); Object.defineProperty(route, GATEWAY_METADATA_SYMBOL, { value: validated, enumerable: false, configurable: true, writable: true, }); return route; } /** * Attach gateway metadata to a route built by `buildFastifyRoute` / `buildApiRoute`. * * The metadata is stamped on the route via a non-enumerable `Symbol` property, * so Fastify never sees it (it walks own enumerable keys when registering * routes). The same route reference is returned — no copy, no spread. * * Apply at the `buildRoutes()` return site (or in the `routes` object for * `AbstractApiController`) so all gateway annotations for a controller live in * a single, scannable block: * * @example * ```ts * public buildRoutes(): BuildRoutesReturnType<typeof MyController.contracts> { * return { * getItem: withGatewayMetadata(MyController.contracts.getItem, this.getItem, { * cache: { ttl: '60s' }, * match: { customHeaders: { 'x-tenant-id': { regex: '^t_' } } }, * }), * // un-annotated routes pass through directly — they still inherit * // controller- and service-wide gateway defaults. * deleteItem: this.deleteItem, * } * } * ``` * * For `buildApiRoute`-built routes, `options.gatewayMetadata` is the simpler * inline path; this helper remains the right tool for routes built with * `buildFastifyRoute` and for cases where the route is constructed elsewhere. * * @param _contract - The contract is taken purely to drive type inference for * `match.headers`, `match.query`, and `rateLimit.key`. It is not stored. * @param route - The route returned by `buildFastifyRoute` or `buildApiRoute`. * @param metadata - Per-route gateway metadata. * @returns The same `route` reference, with metadata attached via Symbol. */ export function withGatewayMetadata(_contract, route, metadata) { return attachGatewayMetadata(route, metadata); } /** * Read gateway metadata previously stamped on a route — either by * `withGatewayMetadata`, by `buildApiRoute(..., { gatewayMetadata })`, or by * the shared `attachGatewayMetadata` helper. Returns `undefined` if no * metadata was attached. */ export function readGatewayMetadata(route) { return route[GATEWAY_METADATA_SYMBOL]; } //# sourceMappingURL=withGatewayMetadata.js.map