@whook/whook
Version:
Build strong and efficient REST web services.
110 lines • 5.13 kB
JavaScript
import { readdir as _readDir } from 'node:fs/promises';
import { noop } from 'common-services';
import { WHOOK_DEFAULT_PLUGINS, } from './WHOOK_RESOLVED_PLUGINS.js';
import { autoService, name, location } from 'knifecycle';
import { extname, join as pathJoin } from 'node:path';
import { printStackTrace } from 'yerror';
export const DEFAULT_ROUTES_DEFINITIONS_OPTIONS = {
ignoredFilePatterns: ['^__', '\\.(test|d)\\.(?:js|mjs|ts|mts)$'],
fileNamePatterns: ['([^.]+)\\.(?:js|mjs|ts|mts)$'],
serviceNamePatterns: [
'^((head|get|put|post|delete|options)[A-Z][a-zA-Z0-9]+)$',
],
};
export const DEFAULT_ROUTE_DEFINITION_FILTER = () => false;
/**
* Initialize the ROUTES_DEFINITIONS service gathering
* the project routes definitions.
* @param {Object} services
* The services ROUTES_DEFINITIONS depends on
* @param {Array<String>} [services.WHOOK_PLUGINS]
* The activated plugins
* @param {Array} services.WHOOK_RESOLVED_PLUGINS
* The resolved plugins
* @param {Object} [services.ROUTES_DEFINITIONS_OPTIONS]
* The options to load the routes in the file system
* @param {Object} [services.ROUTE_DEFINITION_FILTER]
* A function to filter the routes per definitions
* @param {Object} services.importer
* A service allowing to dynamically import ES modules
* @param {Object} [services.log=noop]
* An optional logging service
* @return {Promise<Object>}
* A promise of a containing the actual host.
*/
async function initRoutesDefinitions({ APP_ENV, WHOOK_PLUGINS = WHOOK_DEFAULT_PLUGINS, WHOOK_RESOLVED_PLUGINS, ROUTES_DEFINITIONS_OPTIONS = DEFAULT_ROUTES_DEFINITIONS_OPTIONS, ROUTE_DEFINITION_FILTER = DEFAULT_ROUTE_DEFINITION_FILTER, importer, log = noop, readDir = _readDir, }) {
log('debug', `🈁 - Gathering the routes modules.`);
const apiHandlers = {};
for (const pluginName of WHOOK_PLUGINS) {
const resolvedPlugin = WHOOK_RESOLVED_PLUGINS[pluginName];
const pluginHasHandlers = resolvedPlugin.types.includes('routes');
if (!pluginHasHandlers) {
continue;
}
for (const file of await readDir(new URL(pathJoin('.', 'routes'), resolvedPlugin.mainURL))) {
if (file === '..' || file === '.') {
continue;
}
const isIgnored = (ROUTES_DEFINITIONS_OPTIONS.ignoredFilePatterns || []).some((pattern) => new RegExp(pattern).test(file));
if (isIgnored) {
log('debug', `⏳ - Skipped "${file}" per ignore patterns.`);
continue;
}
const handlerName = ROUTES_DEFINITIONS_OPTIONS.fileNamePatterns.map((pattern) => new RegExp(pattern).exec(file)?.[1])[0];
if (!handlerName) {
log('debug', `⏳ - Skipped "${file}" per file patterns.`);
continue;
}
// Avoid loading the same handlerName twice if
// overridden upstream by another plugin or the
// root project path
if (apiHandlers[handlerName]) {
log('debug', `⏳ - Skipped "${file}" since already loaded upstream.`);
continue;
}
const nameMatches = ROUTES_DEFINITIONS_OPTIONS.serviceNamePatterns.some((pattern) => new RegExp(pattern).test(handlerName));
if (!nameMatches) {
log('debug', `⏳ - Skipped "${file}" per service name patterns.`);
continue;
}
const url = new URL(pathJoin('.', 'routes', handlerName + extname(resolvedPlugin.mainURL)), resolvedPlugin.mainURL).toString();
let module;
try {
module = await importer(url);
}
catch (err) {
log('error', `🔴 - Got an error while loading an handler file: ${file}`);
log('error-stack', printStackTrace(err));
}
if (!module.definition) {
log('debug', `⏳ - Module "${file}" has no definition!`);
apiHandlers[handlerName] = {
url,
name: handlerName,
pluginName,
module,
};
continue;
}
if (module.definition.config?.environments &&
module.definition.config.environments !== 'all' &&
!module.definition.config.environments.includes(APP_ENV)) {
log('debug', `⏳ - Skipped "${file}" since disabled by the application environment (${APP_ENV})!`);
continue;
}
if (ROUTE_DEFINITION_FILTER(module.definition)) {
log('debug', `⏳ - Skipped "${file}" due to routes handlers filter.`);
continue;
}
apiHandlers[handlerName] = {
url,
name: handlerName,
pluginName,
module,
};
}
}
return apiHandlers;
}
export default location(name('ROUTES_DEFINITIONS', autoService(initRoutesDefinitions)), import.meta.url);
//# sourceMappingURL=ROUTES_DEFINITIONS.js.map