UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

79 lines 3.33 kB
import { inferSoleSuccessStatus } from "../contracts/index.js"; /** * Trusted run key shared with `@beignet/core/application` via the global * symbol registry, so the binder never imports the application builder at * runtime. */ const USE_CASE_TRUSTED_RUN_KEY = Symbol.for("beignet.useCase.trustedRun"); const USE_CASE_OUTPUT_VALIDATED_KEY = Symbol.for("beignet.useCase.outputValidated"); function isPlainObject(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } /** * Default input mapping for binder routes. * * Merges parsed query, body, and path objects into one input object. Path * keys win all collisions, then body keys, then query keys. Headers are never * merged: parsed headers include every raw request header, so merging them * would poison the use case input. Non-object bodies (text, arrays, scalars) * are excluded. Routes that need headers or non-object bodies declare an * explicit `input` mapper. */ export function defaultBinderInput(parts) { return { ...(isPlainObject(parts.query) ? parts.query : {}), ...(isPlainObject(parts.body) ? parts.body : {}), ...(isPlainObject(parts.path) ? parts.path : {}), }; } /** * Whether a route definition is a binder route. */ export function isUseCaseRouteDef(route) { return route.useCase !== undefined && route.useCase !== null; } function computeTrustedInput(contract, def) { if (def.input) return false; const sources = [contract.pathParams, contract.query, contract.body].filter((schema) => schema !== null && schema !== undefined); return sources.length === 1 && sources[0] === def.useCase.inputSchema; } function computeResponseExemption(contract, def, status) { return def.useCase[USE_CASE_OUTPUT_VALIDATED_KEY] === true && contract.responses[status] === def.useCase.outputSchema ? status : undefined; } /** * Synthesize the route handler for a binder route at registration time. * * Resolves the success status, decides whether the validated request parts can * skip the use case's input parse, and computes whether server-side response * validation is redundant for the success status. */ export function createUseCaseRouteHandler(contract, def) { const status = def.status ?? inferSoleSuccessStatus(contract); if (status === undefined) { throw new Error(`Route binder for contract "${contract.name}" cannot infer a success ` + `status: the contract declares ${Object.keys(contract.responses).length === 0 ? "no responses" : "zero or multiple 2xx responses"}. Declare exactly one 2xx response or pass an explicit status.`); } const mapInput = def.input ?? defaultBinderInput; const trustedRun = computeTrustedInput(contract, def) ? def.useCase[USE_CASE_TRUSTED_RUN_KEY] : undefined; const run = trustedRun ?? def.useCase.run; const handler = async ({ ctx, path, query, headers, body, }) => ({ status, body: await run.call(def.useCase, { ctx, input: mapInput({ path, query, headers, body }), }), }); return { handler, responseValidationExemptStatus: computeResponseExemption(contract, def, status), }; } //# sourceMappingURL=use-case-route.js.map