UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

303 lines (272 loc) 9.37 kB
import { autoService, location, name } from 'knifecycle'; import { noop } from '../libs/utils.js'; import { type LogService } from 'common-services'; import { type OpenAPIComponents, type OpenAPIExtension, type OpenAPIPaths, } from 'ya-open-api-types'; import { type WhookPluginName } from './WHOOK_RESOLVED_PLUGINS.js'; import { type ExpressiveJSONSchema } from 'ya-json-schema-types'; import { DEFAULT_ROUTE_CONFIG, type WhookRouteDefinition, type WhookRouteModule, } from '../types/routes.js'; import { DEFAULT_COMMAND_CONFIG, type WhookCommandDefinition, type WhookCommandModule, } from '../types/commands.js'; import { DEFAULT_CRON_CONFIG, type WhookCronDefinition, type WhookCronModule, } from '../types/crons.js'; import { DEFAULT_TRANSFORMER_CONFIG, type WhookTransformerDefinition, type WhookTransformerModule, } from '../types/transformers.js'; import { type WhookRoutesDefinitionsService } from './ROUTES_DEFINITIONS.js'; import { type WhookCommandsDefinitionsService } from './COMMANDS_DEFINITIONS.js'; import { type WhookCronsDefinitionsService } from './CRONS_DEFINITIONS.js'; import { type WhookConsumersDefinitionsService } from './CONSUMERS_DEFINITIONS.js'; import { type WhookTransformersDefinitionsService } from './TRANSFORMERS_DEFINITIONS.js'; import { type WhookOpenAPI } from '../types/openapi.js'; import { type WhookConsumerModule, type WhookConsumerDefinition, DEFAULT_CONSUMER_CONFIG, } from '../types/consumers.js'; import { combineComponents } from '../types/base.js'; /* Architecture Note #2.9.2.1: Definitions loader The `DEFINITIONS` service provide a convenient way to gather your various definitions from the routes, crons or commands you created in the `src/(routes|cron|commands)` folder. */ export const DEFAULT_SECURITY_DEFINITIONS: WhookSecurityDefinitions = { securitySchemes: {}, security: [], }; export interface WhookSecurityDefinitions { securitySchemes: NonNullable< NonNullable<WhookOpenAPI['components']>['securitySchemes'] >; security: NonNullable<WhookOpenAPI['security']>; } export interface WhookDefinitionsConfig { WHOOK_PLUGINS?: WhookPluginName[]; } export type WhookDefinitionsDependencies = WhookDefinitionsConfig & { ROUTES_DEFINITIONS: WhookRoutesDefinitionsService; COMMANDS_DEFINITIONS: WhookCommandsDefinitionsService; CRONS_DEFINITIONS: WhookCronsDefinitionsService; CONSUMERS_DEFINITIONS: WhookConsumersDefinitionsService; TRANSFORMERS_DEFINITIONS: WhookTransformersDefinitionsService; SECURITY_DEFINITIONS?: WhookSecurityDefinitions; log?: LogService; }; export interface WhookDefinitions { paths: OpenAPIPaths<ExpressiveJSONSchema, OpenAPIExtension>; components: OpenAPIComponents<ExpressiveJSONSchema, OpenAPIExtension>; security: NonNullable<WhookOpenAPI['security']>; configs: Record< string, | ({ type: 'command'; } & WhookCommandDefinition) | ({ type: 'route'; } & WhookRouteDefinition) | (WhookCronDefinition & { type: 'cron'; }) | (WhookConsumerDefinition & { type: 'consumer'; }) | (WhookTransformerDefinition & { type: 'transformer'; }) >; } export default location( name('DEFINITIONS', autoService(initDefinitions)), import.meta.url, ); /** * Initialize the DEFINITIONS service. * @param {Object} services * The service dependencies * @param {Object} [services.ROUTES_DEFINITIONS] * The API routes modules * @param {Object} [services.COMMANDS_DEFINITIONS] * The commands modules * @param {Object} [services.CRONS_DEFINITIONS] * The crons modules * @param {Object} [services.CONSUMERS_DEFINITIONS] * The consumers modules * @param {Object} [services.TRANSFORMERS_DEFINITIONS] * The transformers modules * @param {Object} [services.log=noop] * An optional logging service * @return {Promise<String>} * A promise of a containing the actual host. */ async function initDefinitions({ ROUTES_DEFINITIONS, COMMANDS_DEFINITIONS, CRONS_DEFINITIONS, CONSUMERS_DEFINITIONS, TRANSFORMERS_DEFINITIONS, SECURITY_DEFINITIONS = DEFAULT_SECURITY_DEFINITIONS, log = noop, }: WhookDefinitionsDependencies): Promise<WhookDefinitions> { log('debug', `🈁 - Generating the DEFINITIONS`); const routesModules: { file: string; module: WhookRouteModule; }[] = Object.keys(ROUTES_DEFINITIONS).map((key) => ({ file: ROUTES_DEFINITIONS[key].url, module: ROUTES_DEFINITIONS[key].module, })); const commandsModules: { file: string; module: WhookCommandModule; }[] = Object.keys(COMMANDS_DEFINITIONS).map((key) => ({ file: COMMANDS_DEFINITIONS[key].url, module: COMMANDS_DEFINITIONS[key].module, })); const cronsModules: { file: string; module: WhookCronModule; }[] = Object.keys(CRONS_DEFINITIONS).map((key) => ({ file: CRONS_DEFINITIONS[key].url, module: CRONS_DEFINITIONS[key].module, })); const consumersModules: { file: string; module: WhookConsumerModule; }[] = Object.keys(CONSUMERS_DEFINITIONS).map((key) => ({ file: CONSUMERS_DEFINITIONS[key].url, module: CONSUMERS_DEFINITIONS[key].module, })); const transformersModules: { file: string; module: WhookTransformerModule; }[] = Object.keys(TRANSFORMERS_DEFINITIONS).map((key) => ({ file: TRANSFORMERS_DEFINITIONS[key].url, module: TRANSFORMERS_DEFINITIONS[key].module, })); const DEFINITIONS: WhookDefinitions = { paths: routesModules.reduce< OpenAPIPaths<ExpressiveJSONSchema, OpenAPIExtension> >((paths, { file, module }: { file: string; module: WhookRouteModule }) => { const definition = module.definition as WhookRouteDefinition; if (!definition) { log('debug', `🈁 - Handler module at "${file}" exports no definition!`); return paths; } if (paths[definition.path]?.[definition.method]) { log( 'warning', `⚠️ - Overriding an existing definition ("${definition.method}" "${definition.path}").`, ); } return { ...paths, ...(definition ? { [definition.path]: { ...(paths[definition.path] || {}), [definition.method]: definition.operation, }, } : {}), }; }, {}), components: { ...combineComponents( { log }, routesModules .concat(commandsModules as unknown as typeof routesModules) .concat(cronsModules as unknown as typeof routesModules) .concat(transformersModules as unknown as typeof routesModules) .concat(consumersModules as unknown as typeof routesModules) .map(({ module }) => module), ), securitySchemes: SECURITY_DEFINITIONS.securitySchemes, }, security: SECURITY_DEFINITIONS.security, configs: {}, }; for (const routeModule of routesModules) { if (!routeModule.module.definition) { continue; } const operationId = routeModule.module.definition.operation.operationId; if (DEFINITIONS.configs[operationId]) { log( 'error', `💥 - Cannot override an existing definition (${operationId}).`, ); } DEFINITIONS.configs[operationId] = { type: 'route', config: routeModule.module.definition.config || DEFAULT_ROUTE_CONFIG, path: routeModule.module.definition.path, method: routeModule.module.definition.method, operation: routeModule.module.definition.operation, }; } for (const cronModule of cronsModules) { const name = cronModule.module.definition.name; if (DEFINITIONS.configs[name]) { log('error', `💥 - Cannot override an existing definition (${name}).`); } DEFINITIONS.configs[name] = { type: 'cron', ...cronModule.module.definition, config: cronModule.module.definition.config || DEFAULT_CRON_CONFIG, schedules: cronModule.module.definition.schedules || [], }; } for (const consumerModule of consumersModules) { const name = consumerModule.module.definition.name; if (DEFINITIONS.configs[name]) { log('error', `💥 - Cannot override an existing definition (${name}).`); } DEFINITIONS.configs[name] = { type: 'consumer', ...consumerModule.module.definition, config: consumerModule.module.definition.config || DEFAULT_CONSUMER_CONFIG, }; } for (const transformerModule of transformersModules) { const name = transformerModule.module.definition.name; if (DEFINITIONS.configs[name]) { log('error', `💥 - Cannot override an existing definition (${name}).`); } DEFINITIONS.configs[name] = { type: 'transformer', ...transformerModule.module.definition, config: transformerModule.module.definition.config || DEFAULT_TRANSFORMER_CONFIG, }; } for (const commandModule of commandsModules) { const name = commandModule.module.definition.name; if (DEFINITIONS.configs[name]) { log('error', `💥 - Cannot override an existing definition (${name}).`); } DEFINITIONS.configs[name] = { type: 'command', ...commandModule.module.definition, config: commandModule.module.definition.config || DEFAULT_COMMAND_CONFIG, }; } return DEFINITIONS; }