@whook/whook
Version:
Build strong and efficient REST web services.
303 lines (272 loc) • 9.37 kB
text/typescript
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;
}