@emmett-community/emmett-expressjs-with-openapi 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/openapi-parser.ts"],"sourcesContent":["/**\n * OpenAPI Parser for handler module discovery.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Parses OpenAPI specifications to extract handler module paths from\n * x-eov-operation-handler fields, enabling automatic handler discovery\n * and registration.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';\nimport { type Logger, safeLog } from '../observability';\n\ntype OpenApiDocument = OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;\n\nexport type HandlerModuleInfo = {\n moduleName: string; // e.g., \"shoppingCarts\"\n relativePath: string; // from x-eov-operation-handler\n absolutePath: string; // resolved full path\n operationIds: string[]; // operations using this handler\n};\n\n/**\n * Extract handler modules from OpenAPI specification.\n *\n * @param apiSpec - OpenAPI spec (file path or object)\n * @param handlersBasePath - Base path for handler modules\n * @param logger - Optional logger for debug output\n * @returns Array of handler module information\n */\nexport async function extractHandlerModules(\n apiSpec: string | OpenApiDocument,\n handlersBasePath: string,\n logger?: Logger,\n): Promise<HandlerModuleInfo[]> {\n safeLog.debug(logger, 'Extracting handler modules from OpenAPI spec', {\n apiSpec: typeof apiSpec === 'string' ? apiSpec : '<object>',\n handlersBasePath,\n });\n\n // Load spec if it's a file path\n const spec =\n typeof apiSpec === 'string' ? await loadOpenApiSpec(apiSpec, logger) : apiSpec;\n\n // Validate spec structure\n if (!spec.paths || typeof spec.paths !== 'object') {\n throw new Error('Invalid OpenAPI specification: missing or invalid \"paths\" field');\n }\n\n // Extract handler modules from spec\n const handlersMap = new Map<string, HandlerModuleInfo>();\n\n for (const [pathKey, pathItem] of Object.entries(spec.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue;\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (method === 'parameters' || method === 'servers') continue;\n if (!operation || typeof operation !== 'object') continue;\n\n const handlerName = (operation as any)['x-eov-operation-handler'];\n const operationId =\n (operation as any)['x-eov-operation-id'] ||\n (operation as any).operationId;\n\n if (handlerName && typeof handlerName === 'string') {\n const absolutePath = resolveHandlerPath(\n handlersBasePath,\n handlerName,\n );\n\n if (!handlersMap.has(handlerName)) {\n handlersMap.set(handlerName, {\n moduleName: handlerName,\n relativePath: handlerName,\n absolutePath,\n operationIds: [],\n });\n }\n\n if (operationId) {\n handlersMap.get(handlerName)!.operationIds.push(operationId);\n }\n }\n }\n }\n\n const modules = Array.from(handlersMap.values());\n\n safeLog.debug(logger, 'Extracted handler modules', {\n count: modules.length,\n modules: modules.map((m) => m.moduleName),\n });\n\n return modules;\n}\n\n/**\n * Load OpenAPI specification from file.\n */\nasync function loadOpenApiSpec(\n filePath: string,\n logger?: Logger,\n): Promise<OpenApiDocument> {\n safeLog.debug(logger, 'Loading OpenAPI spec file', { filePath });\n try {\n const content = await readFile(filePath, 'utf-8');\n const ext = path.extname(filePath).toLowerCase();\n\n let parsed: OpenApiDocument;\n if (ext === '.json') {\n parsed = JSON.parse(content);\n } else if (ext === '.yaml' || ext === '.yml') {\n // Dynamic import to avoid bundling yaml if not needed\n const yaml = await import('yaml');\n parsed = yaml.parse(content);\n } else {\n throw new Error(\n `Unsupported OpenAPI file format: ${ext}. Use .json, .yaml, or .yml`,\n );\n }\n\n safeLog.debug(logger, 'OpenAPI spec loaded successfully', {\n filePath,\n format: ext,\n });\n\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`OpenAPI specification file not found: ${filePath}`);\n }\n throw error;\n }\n}\n\n/**\n * Resolve handler module path, preventing path traversal attacks.\n */\nfunction resolveHandlerPath(\n basePath: string,\n relativePath: string,\n): string {\n // Normalize to prevent path traversal\n const normalized = path.normalize(relativePath);\n\n // Resolve absolute path\n const absolutePath = path.resolve(basePath, normalized);\n\n // Ensure path is within basePath (no escape via ../)\n const resolvedBase = path.resolve(basePath);\n if (!absolutePath.startsWith(resolvedBase)) {\n throw new Error(\n `Invalid handler path: \"${relativePath}\" escapes base directory`,\n );\n }\n\n return absolutePath;\n}\n"],"mappings":";;;;;AAUA,SAAS,gBAAgB;AACzB,OAAO,UAAU;AAqBjB,eAAsB,sBACpB,SACA,kBACA,QAC8B;AAC9B,UAAQ,MAAM,QAAQ,gDAAgD;AAAA,IACpE,SAAS,OAAO,YAAY,WAAW,UAAU;AAAA,IACjD;AAAA,EACF,CAAC;AAGD,QAAM,OACJ,OAAO,YAAY,WAAW,MAAM,gBAAgB,SAAS,MAAM,IAAI;AAGzE,MAAI,CAAC,KAAK,SAAS,OAAO,KAAK,UAAU,UAAU;AACjD,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AAGA,QAAM,cAAc,oBAAI,IAA+B;AAEvD,aAAW,CAAC,SAAS,QAAQ,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AAC5D,QAAI,CAAC,YAAY,OAAO,aAAa,SAAU;AAE/C,eAAW,CAAC,QAAQ,SAAS,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC1D,UAAI,WAAW,gBAAgB,WAAW,UAAW;AACrD,UAAI,CAAC,aAAa,OAAO,cAAc,SAAU;AAEjD,YAAM,cAAe,UAAkB,yBAAyB;AAChE,YAAM,cACH,UAAkB,oBAAoB,KACtC,UAAkB;AAErB,UAAI,eAAe,OAAO,gBAAgB,UAAU;AAClD,cAAM,eAAe;AAAA,UACnB;AAAA,UACA;AAAA,QACF;AAEA,YAAI,CAAC,YAAY,IAAI,WAAW,GAAG;AACjC,sBAAY,IAAI,aAAa;AAAA,YAC3B,YAAY;AAAA,YACZ,cAAc;AAAA,YACd;AAAA,YACA,cAAc,CAAC;AAAA,UACjB,CAAC;AAAA,QACH;AAEA,YAAI,aAAa;AACf,sBAAY,IAAI,WAAW,EAAG,aAAa,KAAK,WAAW;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,KAAK,YAAY,OAAO,CAAC;AAE/C,UAAQ,MAAM,QAAQ,6BAA6B;AAAA,IACjD,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,UAAU;AAAA,EAC1C,CAAC;AAED,SAAO;AACT;AAKA,eAAe,gBACb,UACA,QAC0B;AAC1B,UAAQ,MAAM,QAAQ,6BAA6B,EAAE,SAAS,CAAC;AAC/D,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,UAAU,OAAO;AAChD,UAAM,MAAM,KAAK,QAAQ,QAAQ,EAAE,YAAY;AAE/C,QAAI;AACJ,QAAI,QAAQ,SAAS;AACnB,eAAS,KAAK,MAAM,OAAO;AAAA,IAC7B,WAAW,QAAQ,WAAW,QAAQ,QAAQ;AAE5C,YAAM,OAAO,MAAM,OAAO,MAAM;AAChC,eAAS,KAAK,MAAM,OAAO;AAAA,IAC7B,OAAO;AACL,YAAM,IAAI;AAAA,QACR,oCAAoC,GAAG;AAAA,MACzC;AAAA,IACF;AAEA,YAAQ,MAAM,QAAQ,oCAAoC;AAAA,MACxD;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,UAAU;AACtD,YAAM,IAAI,MAAM,yCAAyC,QAAQ,EAAE;AAAA,IACrE;AACA,UAAM;AAAA,EACR;AACF;AAKA,SAAS,mBACP,UACA,cACQ;AAER,QAAM,aAAa,KAAK,UAAU,YAAY;AAG9C,QAAM,eAAe,KAAK,QAAQ,UAAU,UAAU;AAGtD,QAAM,eAAe,KAAK,QAAQ,QAAQ;AAC1C,MAAI,CAAC,aAAa,WAAW,YAAY,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,0BAA0B,YAAY;AAAA,IACxC;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emmett-community/emmett-expressjs-with-openapi",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Express.js utilities for Emmett applications that want OpenAPI 3.x validation without pulling the whole Emmett core",
@@ -71,6 +71,7 @@
71
71
  }
72
72
  },
73
73
  "dependencies": {
74
+ "@opentelemetry/api": "^1.0.0",
74
75
  "yaml": "^2.8.1"
75
76
  },
76
77
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/handler-importer-4BVBIZX3.cjs","../src/internal/handler-importer.ts"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACA;ACIA,0BAA8B;AAY9B,MAAA,SAAsB,yBAAA,CACpB,OAAA,EACiC;AACjC,EAAA,MAAM,iBAAA,EAA2C,CAAC,CAAA;AAElD,EAAA,IAAA,CAAA,MAAW,OAAA,GAAU,OAAA,EAAS;AAC5B,IAAA,IAAI;AAEF,MAAA,MAAM,QAAA,EAAU,gCAAA,MAAc,CAAO,YAAY,CAAA,CAAE,IAAA;AAGnD,MAAA,MAAM,eAAA,EAAiB,MAAM,4DAAA,CAAO,OAAA,GAAA;AAGpC,MAAA,qDAAA,MAAsB,CAAO,YAAA,EAAc,cAAc,CAAA;AAGzD,MAAA,gBAAA,CAAiB,MAAA,CAAO,UAAU,EAAA,EAAI,cAAA;AAAA,IACxC,EAAA,MAAA,CAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,iCAAA,EAAoC,MAAA,CAAO,UAAU,CAAA,OAAA,EAAU,MAAA,CAAO,YAAY,CAAA,EAAA,EAC/E,KAAA,CAAgB,OACnB,CAAA;AAAA,MAAA;AACF,IAAA;AACF,EAAA;AAGF,EAAA;AACF;AD1BA;AACA;AACA","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/handler-importer-4BVBIZX3.cjs","sourcesContent":[null,"/**\n * Handler module importer.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Dynamically imports handler modules and registers them in the ESM resolver cache,\n * enabling automatic handler discovery without manual registration.\n */\n\nimport { pathToFileURL } from 'node:url';\nimport { registerHandlerModule } from './esm-resolver.js';\nimport type { HandlerModuleInfo } from './openapi-parser.js';\n\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Dynamically import and register all handler modules.\n *\n * @param modules - Handler module information from OpenAPI parser\n * @returns Object containing all imported modules, keyed by module name\n */\nexport async function importAndRegisterHandlers(\n modules: HandlerModuleInfo[],\n): Promise<ImportedHandlerModules> {\n const importedHandlers: ImportedHandlerModules = {};\n\n for (const module of modules) {\n try {\n // Convert to file:// URL for dynamic import\n const fileUrl = pathToFileURL(module.absolutePath).href;\n\n // Dynamically import the handler module\n const importedModule = await import(fileUrl);\n\n // Register in ESM resolver cache\n registerHandlerModule(module.absolutePath, importedModule);\n\n // Store in result object keyed by module name\n importedHandlers[module.moduleName] = importedModule;\n } catch (error) {\n throw new Error(\n `Failed to import handler module \"${module.moduleName}\" from ${module.absolutePath}: ${\n (error as Error).message\n }`,\n );\n }\n }\n\n return importedHandlers;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/handler-importer.ts"],"sourcesContent":["/**\n * Handler module importer.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Dynamically imports handler modules and registers them in the ESM resolver cache,\n * enabling automatic handler discovery without manual registration.\n */\n\nimport { pathToFileURL } from 'node:url';\nimport { registerHandlerModule } from './esm-resolver.js';\nimport type { HandlerModuleInfo } from './openapi-parser.js';\n\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Dynamically import and register all handler modules.\n *\n * @param modules - Handler module information from OpenAPI parser\n * @returns Object containing all imported modules, keyed by module name\n */\nexport async function importAndRegisterHandlers(\n modules: HandlerModuleInfo[],\n): Promise<ImportedHandlerModules> {\n const importedHandlers: ImportedHandlerModules = {};\n\n for (const module of modules) {\n try {\n // Convert to file:// URL for dynamic import\n const fileUrl = pathToFileURL(module.absolutePath).href;\n\n // Dynamically import the handler module\n const importedModule = await import(fileUrl);\n\n // Register in ESM resolver cache\n registerHandlerModule(module.absolutePath, importedModule);\n\n // Store in result object keyed by module name\n importedHandlers[module.moduleName] = importedModule;\n } catch (error) {\n throw new Error(\n `Failed to import handler module \"${module.moduleName}\" from ${module.absolutePath}: ${\n (error as Error).message\n }`,\n );\n }\n }\n\n return importedHandlers;\n}\n"],"mappings":";;;;;AASA,SAAS,qBAAqB;AAY9B,eAAsB,0BACpB,SACiC;AACjC,QAAM,mBAA2C,CAAC;AAElD,aAAW,UAAU,SAAS;AAC5B,QAAI;AAEF,YAAM,UAAU,cAAc,OAAO,YAAY,EAAE;AAGnD,YAAM,iBAAiB,MAAM,OAAO;AAGpC,4BAAsB,OAAO,cAAc,cAAc;AAGzD,uBAAiB,OAAO,UAAU,IAAI;AAAA,IACxC,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,oCAAoC,OAAO,UAAU,UAAU,OAAO,YAAY,KAC/E,MAAgB,OACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/openapi-parser.ts"],"sourcesContent":["/**\n * OpenAPI Parser for handler module discovery.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Parses OpenAPI specifications to extract handler module paths from\n * x-eov-operation-handler fields, enabling automatic handler discovery\n * and registration.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';\n\ntype OpenApiDocument = OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;\n\nexport type HandlerModuleInfo = {\n moduleName: string; // e.g., \"shoppingCarts\"\n relativePath: string; // from x-eov-operation-handler\n absolutePath: string; // resolved full path\n operationIds: string[]; // operations using this handler\n};\n\n/**\n * Extract handler modules from OpenAPI specification.\n *\n * @param apiSpec - OpenAPI spec (file path or object)\n * @param handlersBasePath - Base path for handler modules\n * @returns Array of handler module information\n */\nexport async function extractHandlerModules(\n apiSpec: string | OpenApiDocument,\n handlersBasePath: string,\n): Promise<HandlerModuleInfo[]> {\n // Load spec if it's a file path\n const spec =\n typeof apiSpec === 'string' ? await loadOpenApiSpec(apiSpec) : apiSpec;\n\n // Validate spec structure\n if (!spec.paths || typeof spec.paths !== 'object') {\n throw new Error('Invalid OpenAPI specification: missing or invalid \"paths\" field');\n }\n\n // Extract handler modules from spec\n const handlersMap = new Map<string, HandlerModuleInfo>();\n\n for (const [pathKey, pathItem] of Object.entries(spec.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue;\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (method === 'parameters' || method === 'servers') continue;\n if (!operation || typeof operation !== 'object') continue;\n\n const handlerName = (operation as any)['x-eov-operation-handler'];\n const operationId =\n (operation as any)['x-eov-operation-id'] ||\n (operation as any).operationId;\n\n if (handlerName && typeof handlerName === 'string') {\n const absolutePath = resolveHandlerPath(\n handlersBasePath,\n handlerName,\n );\n\n if (!handlersMap.has(handlerName)) {\n handlersMap.set(handlerName, {\n moduleName: handlerName,\n relativePath: handlerName,\n absolutePath,\n operationIds: [],\n });\n }\n\n if (operationId) {\n handlersMap.get(handlerName)!.operationIds.push(operationId);\n }\n }\n }\n }\n\n return Array.from(handlersMap.values());\n}\n\n/**\n * Load OpenAPI specification from file.\n */\nasync function loadOpenApiSpec(\n filePath: string,\n): Promise<OpenApiDocument> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext === '.json') {\n return JSON.parse(content);\n } else if (ext === '.yaml' || ext === '.yml') {\n // Dynamic import to avoid bundling yaml if not needed\n const yaml = await import('yaml');\n return yaml.parse(content);\n } else {\n throw new Error(\n `Unsupported OpenAPI file format: ${ext}. Use .json, .yaml, or .yml`,\n );\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`OpenAPI specification file not found: ${filePath}`);\n }\n throw error;\n }\n}\n\n/**\n * Resolve handler module path, preventing path traversal attacks.\n */\nfunction resolveHandlerPath(\n basePath: string,\n relativePath: string,\n): string {\n // Normalize to prevent path traversal\n const normalized = path.normalize(relativePath);\n\n // Resolve absolute path\n const absolutePath = path.resolve(basePath, normalized);\n\n // Ensure path is within basePath (no escape via ../)\n const resolvedBase = path.resolve(basePath);\n if (!absolutePath.startsWith(resolvedBase)) {\n throw new Error(\n `Invalid handler path: \"${relativePath}\" escapes base directory`,\n );\n }\n\n return absolutePath;\n}\n"],"mappings":";AAUA,SAAS,gBAAgB;AACzB,OAAO,UAAU;AAmBjB,eAAsB,sBACpB,SACA,kBAC8B;AAE9B,QAAM,OACJ,OAAO,YAAY,WAAW,MAAM,gBAAgB,OAAO,IAAI;AAGjE,MAAI,CAAC,KAAK,SAAS,OAAO,KAAK,UAAU,UAAU;AACjD,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AAGA,QAAM,cAAc,oBAAI,IAA+B;AAEvD,aAAW,CAAC,SAAS,QAAQ,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AAC5D,QAAI,CAAC,YAAY,OAAO,aAAa,SAAU;AAE/C,eAAW,CAAC,QAAQ,SAAS,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC1D,UAAI,WAAW,gBAAgB,WAAW,UAAW;AACrD,UAAI,CAAC,aAAa,OAAO,cAAc,SAAU;AAEjD,YAAM,cAAe,UAAkB,yBAAyB;AAChE,YAAM,cACH,UAAkB,oBAAoB,KACtC,UAAkB;AAErB,UAAI,eAAe,OAAO,gBAAgB,UAAU;AAClD,cAAM,eAAe;AAAA,UACnB;AAAA,UACA;AAAA,QACF;AAEA,YAAI,CAAC,YAAY,IAAI,WAAW,GAAG;AACjC,sBAAY,IAAI,aAAa;AAAA,YAC3B,YAAY;AAAA,YACZ,cAAc;AAAA,YACd;AAAA,YACA,cAAc,CAAC;AAAA,UACjB,CAAC;AAAA,QACH;AAEA,YAAI,aAAa;AACf,sBAAY,IAAI,WAAW,EAAG,aAAa,KAAK,WAAW;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,YAAY,OAAO,CAAC;AACxC;AAKA,eAAe,gBACb,UAC0B;AAC1B,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,UAAU,OAAO;AAChD,UAAM,MAAM,KAAK,QAAQ,QAAQ,EAAE,YAAY;AAE/C,QAAI,QAAQ,SAAS;AACnB,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,WAAW,QAAQ,WAAW,QAAQ,QAAQ;AAE5C,YAAM,OAAO,MAAM,OAAO,MAAM;AAChC,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,OAAO;AACL,YAAM,IAAI;AAAA,QACR,oCAAoC,GAAG;AAAA,MACzC;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,UAAU;AACtD,YAAM,IAAI,MAAM,yCAAyC,QAAQ,EAAE;AAAA,IACrE;AACA,UAAM;AAAA,EACR;AACF;AAKA,SAAS,mBACP,UACA,cACQ;AAER,QAAM,aAAa,KAAK,UAAU,YAAY;AAG9C,QAAM,eAAe,KAAK,QAAQ,UAAU,UAAU;AAGtD,QAAM,eAAe,KAAK,QAAQ,QAAQ;AAC1C,MAAI,CAAC,aAAa,WAAW,YAAY,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,0BAA0B,YAAY;AAAA,IACxC;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/openapi-parser-NFUD7ZGZ.cjs","../src/internal/openapi-parser.ts"],"names":[],"mappings":"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","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/openapi-parser-NFUD7ZGZ.cjs","sourcesContent":[null,"/**\n * OpenAPI Parser for handler module discovery.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Parses OpenAPI specifications to extract handler module paths from\n * x-eov-operation-handler fields, enabling automatic handler discovery\n * and registration.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';\n\ntype OpenApiDocument = OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;\n\nexport type HandlerModuleInfo = {\n moduleName: string; // e.g., \"shoppingCarts\"\n relativePath: string; // from x-eov-operation-handler\n absolutePath: string; // resolved full path\n operationIds: string[]; // operations using this handler\n};\n\n/**\n * Extract handler modules from OpenAPI specification.\n *\n * @param apiSpec - OpenAPI spec (file path or object)\n * @param handlersBasePath - Base path for handler modules\n * @returns Array of handler module information\n */\nexport async function extractHandlerModules(\n apiSpec: string | OpenApiDocument,\n handlersBasePath: string,\n): Promise<HandlerModuleInfo[]> {\n // Load spec if it's a file path\n const spec =\n typeof apiSpec === 'string' ? await loadOpenApiSpec(apiSpec) : apiSpec;\n\n // Validate spec structure\n if (!spec.paths || typeof spec.paths !== 'object') {\n throw new Error('Invalid OpenAPI specification: missing or invalid \"paths\" field');\n }\n\n // Extract handler modules from spec\n const handlersMap = new Map<string, HandlerModuleInfo>();\n\n for (const [pathKey, pathItem] of Object.entries(spec.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue;\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (method === 'parameters' || method === 'servers') continue;\n if (!operation || typeof operation !== 'object') continue;\n\n const handlerName = (operation as any)['x-eov-operation-handler'];\n const operationId =\n (operation as any)['x-eov-operation-id'] ||\n (operation as any).operationId;\n\n if (handlerName && typeof handlerName === 'string') {\n const absolutePath = resolveHandlerPath(\n handlersBasePath,\n handlerName,\n );\n\n if (!handlersMap.has(handlerName)) {\n handlersMap.set(handlerName, {\n moduleName: handlerName,\n relativePath: handlerName,\n absolutePath,\n operationIds: [],\n });\n }\n\n if (operationId) {\n handlersMap.get(handlerName)!.operationIds.push(operationId);\n }\n }\n }\n }\n\n return Array.from(handlersMap.values());\n}\n\n/**\n * Load OpenAPI specification from file.\n */\nasync function loadOpenApiSpec(\n filePath: string,\n): Promise<OpenApiDocument> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext === '.json') {\n return JSON.parse(content);\n } else if (ext === '.yaml' || ext === '.yml') {\n // Dynamic import to avoid bundling yaml if not needed\n const yaml = await import('yaml');\n return yaml.parse(content);\n } else {\n throw new Error(\n `Unsupported OpenAPI file format: ${ext}. Use .json, .yaml, or .yml`,\n );\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`OpenAPI specification file not found: ${filePath}`);\n }\n throw error;\n }\n}\n\n/**\n * Resolve handler module path, preventing path traversal attacks.\n */\nfunction resolveHandlerPath(\n basePath: string,\n relativePath: string,\n): string {\n // Normalize to prevent path traversal\n const normalized = path.normalize(relativePath);\n\n // Resolve absolute path\n const absolutePath = path.resolve(basePath, normalized);\n\n // Ensure path is within basePath (no escape via ../)\n const resolvedBase = path.resolve(basePath);\n if (!absolutePath.startsWith(resolvedBase)) {\n throw new Error(\n `Invalid handler path: \"${relativePath}\" escapes base directory`,\n );\n }\n\n return absolutePath;\n}\n"]}