n8n
Version:
n8n Workflow Automation Tool
172 lines • 7.77 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDiscoverResponse = buildDiscoverResponse;
exports._resetCache = _resetCache;
const json_schema_ref_parser_1 = __importDefault(require("@apidevtools/json-schema-ref-parser"));
const is_record_1 = require("@n8n/utils/is-record");
const path_1 = __importDefault(require("path"));
const public_api_scope_lookup_1 = require("../../shared/public-api-scope-lookup");
require("../../controllers");
const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'patch'];
let cachedEndpointsPromise;
function extractRequestSchema(operation) {
if (!(0, is_record_1.isRecord)(operation.requestBody))
return undefined;
const content = operation.requestBody.content;
if (!(0, is_record_1.isRecord)(content))
return undefined;
const json = content['application/json'];
if (!(0, is_record_1.isRecord)(json))
return undefined;
const schema = json.schema;
return (0, is_record_1.isRecord)(schema) ? schema : undefined;
}
async function parseEndpointsFromSpec() {
if (!cachedEndpointsPromise) {
cachedEndpointsPromise = _parseEndpointsFromSpec();
}
return await cachedEndpointsPromise;
}
async function _parseEndpointsFromSpec() {
const specPath = path_1.default.join(__dirname, '..', '..', 'openapi.yml');
const publicApiRoot = path_1.default.join(__dirname, '..', '..', '..');
const spec = await json_schema_ref_parser_1.default.dereference(specPath);
if (!(0, is_record_1.isRecord)(spec) || !(0, is_record_1.isRecord)(spec.paths))
return [];
const endpoints = [];
const handlerCache = new Map();
const controllerScopes = (0, public_api_scope_lookup_1.loadPublicControllerScopeMap)();
for (const [pathKey, pathValue] of Object.entries(spec.paths)) {
if (!(0, is_record_1.isRecord)(pathValue))
continue;
for (const method of HTTP_METHODS) {
const operation = pathValue[method];
if (!(0, is_record_1.isRecord)(operation))
continue;
const eovOperationId = operation['x-eov-operation-id'];
const handlerPath = operation['x-eov-operation-handler'];
const openApiOperationId = operation.operationId;
const hasEovHandler = typeof eovOperationId === 'string' && typeof handlerPath === 'string';
const routeKey = (0, public_api_scope_lookup_1.publicApiRouteKey)(method, pathKey);
const hasControllerHandler = controllerScopes.has(routeKey);
if (!hasEovHandler && !hasControllerHandler) {
continue;
}
const operationId = (typeof eovOperationId === 'string' ? eovOperationId : undefined) ??
(typeof openApiOperationId === 'string' ? openApiOperationId : undefined);
if (!operationId) {
continue;
}
const tags = Array.isArray(operation.tags) ? operation.tags : [];
const tag = typeof tags[0] === 'string' ? tags[0] : 'Other';
let eovHandlerScope;
if (hasEovHandler) {
let handlerModule = handlerCache.get(handlerPath);
if (!handlerModule) {
try {
const fullHandlerPath = path_1.default.join(publicApiRoot, `${handlerPath}.js`);
const imported = await import(fullHandlerPath);
if (!(0, is_record_1.isRecord)(imported))
continue;
const loaded = (0, is_record_1.isRecord)(imported.default) ? imported.default : imported;
if (!(0, is_record_1.isRecord)(loaded))
continue;
handlerModule = loaded;
handlerCache.set(handlerPath, handlerModule);
}
catch {
continue;
}
}
const middlewareChain = handlerModule[eovOperationId];
eovHandlerScope = Array.isArray(middlewareChain)
? (0, public_api_scope_lookup_1.extractScopeFromEovHandlerChain)(middlewareChain)
: undefined;
}
const implemented = (0, public_api_scope_lookup_1.resolvePublicApiImplementedScope)(controllerScopes, method, pathKey, eovHandlerScope);
endpoints.push({
method: method.toUpperCase(),
path: `/api/v1${pathKey}`,
operationId,
tag,
scope: implemented ?? null,
requestSchema: extractRequestSchema(operation),
});
}
}
return endpoints;
}
async function buildDiscoverResponse(callerScopes, options) {
const allEndpoints = await parseEndpointsFromSpec();
const scopeSet = new Set(callerScopes);
const includeSchemas = options?.includeSchemas === true;
const filtered = allEndpoints.filter((ep) => ep.scope === null || scopeSet.has(ep.scope));
const resources = {};
for (const ep of filtered) {
const resourceKey = ep.tag.toLowerCase();
if (!resources[resourceKey]) {
resources[resourceKey] = { operations: [], endpoints: [] };
}
const entry = {
method: ep.method,
path: ep.path,
operationId: ep.operationId,
};
if (includeSchemas && ep.requestSchema) {
entry.requestSchema = ep.requestSchema;
}
resources[resourceKey].endpoints.push(entry);
const operation = ep.scope?.split(':')[1];
if (operation && !resources[resourceKey].operations.includes(operation)) {
resources[resourceKey].operations.push(operation);
}
}
const resourceFilter = options?.resource?.toLowerCase();
const operationFilter = options?.operation?.toLowerCase();
let filteredResources = resources;
if (resourceFilter) {
const match = filteredResources[resourceFilter];
filteredResources = match ? { [resourceFilter]: match } : {};
}
if (operationFilter) {
const scopeByOperationId = new Map(filtered.map((f) => [f.operationId, f.scope?.split(':')[1]?.toLowerCase()]));
const result = {};
for (const [key, info] of Object.entries(filteredResources)) {
const matchingEndpoints = info.endpoints.filter((ep) => scopeByOperationId.get(ep.operationId) === operationFilter);
if (matchingEndpoints.length > 0) {
result[key] = {
operations: info.operations.filter((o) => o.toLowerCase() === operationFilter),
endpoints: matchingEndpoints,
};
}
}
filteredResources = result;
}
const allOperations = [...new Set(Object.values(resources).flatMap((r) => r.operations))];
return {
scopes: callerScopes,
resources: filteredResources,
filters: {
resource: {
description: 'Filter to a specific resource',
values: Object.keys(resources),
},
operation: {
description: 'Filter to a specific operation',
values: allOperations,
},
include: {
description: 'Include additional data',
values: ['schemas'],
},
},
specUrl: '/api/v1/openapi.yml',
};
}
function _resetCache() {
cachedEndpointsPromise = undefined;
}
//# sourceMappingURL=discover.service.js.map