@emmett-community/emmett-expressjs-with-openapi 0.1.0 → 0.2.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.
- package/README.md +31 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/chunk-MRSGTROW.cjs.map +1 -1
- package/dist/esm-resolver-I5MW2PIQ.cjs.map +1 -1
- package/dist/handler-importer-4BVBIZX3.cjs.map +1 -1
- package/dist/index.cjs +72 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/{openapi-parser-6EGURSGG.js → openapi-parser-CCYU636U.js} +1 -1
- package/dist/openapi-parser-CCYU636U.js.map +1 -0
- package/dist/{openapi-parser-KI7BS3DC.cjs → openapi-parser-NFUD7ZGZ.cjs} +1 -1
- package/dist/openapi-parser-NFUD7ZGZ.cjs.map +1 -0
- package/package.json +14 -5
- package/dist/openapi-parser-6EGURSGG.js.map +0 -1
- package/dist/openapi-parser-KI7BS3DC.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -60,6 +60,36 @@ const app = getApplication({
|
|
|
60
60
|
- Serve the parsed spec via `serveSpec`, configure file upload limits, ignore health-check routes, or validate the spec itself.
|
|
61
61
|
- Reuse `$ref` parsing, custom formats, and file upload middleware exactly as in the upstream validator.
|
|
62
62
|
|
|
63
|
+
## Firebase Auth (optional)
|
|
64
|
+
|
|
65
|
+
If you use Firebase Authentication, install the optional peer and plug it into OpenAPI security handlers:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @my-f-startup/firebase-auth-express
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import admin from 'firebase-admin';
|
|
73
|
+
import {
|
|
74
|
+
createFirebaseAuthSecurityHandlers,
|
|
75
|
+
createOpenApiValidatorOptions,
|
|
76
|
+
getApplication,
|
|
77
|
+
} from '@emmett-community/emmett-expressjs-with-openapi';
|
|
78
|
+
|
|
79
|
+
admin.initializeApp();
|
|
80
|
+
|
|
81
|
+
const app = getApplication({
|
|
82
|
+
apis: [myApi],
|
|
83
|
+
openApiValidator: createOpenApiValidatorOptions('./openapi.yaml', {
|
|
84
|
+
validateSecurity: {
|
|
85
|
+
handlers: createFirebaseAuthSecurityHandlers(),
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`@my-f-startup/firebase-auth-express` relies on `firebase-admin`. Make sure the Admin SDK is installed, initialized, and configured for your environment (credentials or emulator).
|
|
92
|
+
|
|
63
93
|
Head to [`docs/openapi-validation.md`](docs/openapi-validation.md) for the full matrix of options, extended explanations, and complete examples. Keeping that document allows us to document advanced scenarios without bloating the README.
|
|
64
94
|
|
|
65
95
|
## Examples
|
|
@@ -71,6 +101,7 @@ Working examples live under `examples/`:
|
|
|
71
101
|
- `examples/basic` – manual routes + validation (minimal scaffolding).
|
|
72
102
|
- `examples/with-security` – standalone security handler demo.
|
|
73
103
|
- `examples/operation-handlers` – barebones `operationHandlers` showcase.
|
|
104
|
+
- `examples/firebase-auth` – Firebase Auth security handlers with operation handlers.
|
|
74
105
|
|
|
75
106
|
## Tests
|
|
76
107
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-GS7T56RP.cjs","../node_modules/tsup/assets/cjs_shims.js"],"names":[],"mappings":"AAAA;ACKA,IAAM,iBAAA,EAAmB,CAAA,EAAA,GACvB,OAAO,SAAA,IAAa,YAAA,EAChB,IAAI,GAAA,CAAI,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAA;AAK8B;ADT4B;AACA;AACA;AACA","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-GS7T56RP.cjs","sourcesContent":[null,"// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-MRSGTROW.cjs","../src/internal/esm-resolver.ts"],"names":["require"],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACA;AC6BA,gCAA8B;AAC9B,wEAAiB;AAEjB,IAAI,UAAA,EAAY,KAAA;AAChB,IAAM,YAAA,kBAAc,IAAI,GAAA,CAAiB,CAAA;AAMlC,IAAM,sBAAA,EAAwB,CAAC,UAAA,EAAoB,aAAA,EAAA,GAA6B;AACrF,EAAA,WAAA,CAAY,GAAA,CAAI,UAAA,EAAY,aAAa,CAAA;AAC3C,CAAA;AAKO,IAAM,oBAAA,EAAsB,CAAA,EAAA,GAAY;AAC7C,EAAA,GAAA,CAAI,SAAA,EAAW;AACb,IAAA,MAAA;AAAA,EACF;AAEA,EAAA,MAAMA,SAAAA,EAAU,mCAAA,+BAA6B,CAAA;AAC7C,EAAA,MAAM,OAAA,EAASA,QAAAA,CAAQ,QAAQ,CAAA;AAC/B,EAAA,MAAM,aAAA,EAAe,MAAA,CAAO,KAAA;AAE5B,EAAA,MAAA,CAAO,MAAA,EAAQ,QAAA,CAAU,OAAA,EAAiB,MAAA,EAAa,MAAA,EAAiB;AACtE,IAAA,MAAM,OAAA,kBAAS,MAAA,2BAAQ,UAAA;AACvB,IAAA,MAAM,mBAAA,kBAAqB,MAAA,6BAAQ,QAAA,mBAAS,2BAA2B,GAAA;AACvE,IAAA,MAAM,gBAAA,EAAkB,OAAA,CAAQ,QAAA,CAAS,UAAU,CAAA;AAEnD,IAAA,GAAA,CAAI,mBAAA,GAAsB,eAAA,EAAiB;AACzC,MAAA,MAAM,aAAA,EAAe,cAAA,CAAK,UAAA,CAAW,OAAO,EAAA,EACxC,QAAA,EACA,cAAA,CAAK,OAAA,CAAQ,cAAA,CAAK,OAAA,CAAQ,MAAM,CAAA,EAAG,OAAO,CAAA;AAG9C,MAAA,GAAA,CAAI,WAAA,CAAY,GAAA,CAAI,YAAY,CAAA,EAAG;AACjC,QAAA,OAAO,WAAA,CAAY,GAAA,CAAI,YAAY,CAAA;AAAA,MACrC;AAEA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,+BAAA,EAAkC,YAAY,CAAA,gEAAA;AAAA,MAEhD,CAAA;AAAA,IACF;AAEA,IAAA,OAAO,YAAA,CAAa,IAAA,CAAK,IAAA,EAAM,OAAA,EAAS,MAAA,EAAQ,MAAM,CAAA;AAAA,EACxD,CAAA;AAEA,EAAA,UAAA,EAAY,IAAA;AACd,CAAA;ADhDA;AACA;AACE;AACA;AACF,yGAAC","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-MRSGTROW.cjs","sourcesContent":[null,"/**\n * ESM Resolver for express-openapi-validator operation handlers.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * PROBLEM:\n * When using TypeScript runtime (tsx) with express-openapi-validator's operationHandlers:\n * - Our code uses `import()` to load handlers (ESM)\n * - express-openapi-validator uses `require()` to load handlers (CJS)\n * - Node.js maintains separate module caches for ESM and CJS\n * - This creates TWO separate instances of the same handler module\n * - Dependencies injected via module-level variables in one instance don't reach the other\n *\n * SOLUTION:\n * This module monkey-patches Module.prototype.require to intercept when\n * express-openapi-validator loads operation handlers and forces it to use\n * dynamic import() instead of require(). This ensures both sides share the\n * same ESM module instance, allowing module-level variables to work correctly.\n *\n * USAGE:\n * This module is automatically activated by getApplication() when operationHandlers\n * are configured. Applications don't need to import or configure anything.\n *\n * LIMITATIONS:\n * - Relies on heuristic detection (caller path includes 'express-openapi-validator')\n * - May break if express-openapi-validator changes its internal loading mechanism\n * - Adds \"magic\" behavior that may not be immediately obvious to developers\n *\n * FUTURE:\n * If express-openapi-validator migrates to native ESM, this resolver becomes\n * unnecessary and will safely become a no-op.\n */\n\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nlet isPatched = false;\nconst moduleCache = new Map<string, any>();\n\n/**\n * Registers a pre-loaded ESM module so it can be returned synchronously\n * when express-openapi-validator tries to require() it.\n */\nexport const registerHandlerModule = (modulePath: string, moduleExports: any): void => {\n moduleCache.set(modulePath, moduleExports);\n};\n\n/**\n * Activates the ESM resolver for express-openapi-validator handler loading.\n */\nexport const activateESMResolver = (): void => {\n if (isPatched) {\n return;\n }\n\n const require = createRequire(import.meta.url);\n const Module = require('module');\n const originalLoad = Module._load;\n\n Module._load = function (request: string, parent: any, isMain: boolean) {\n const caller = parent?.filename;\n const isValidatorLoading = caller?.includes('express-openapi-validator');\n const isHandlerModule = request.includes('handlers');\n\n if (isValidatorLoading && isHandlerModule) {\n const absolutePath = path.isAbsolute(request)\n ? request\n : path.resolve(path.dirname(caller), request);\n\n // Check if we have this module pre-registered\n if (moduleCache.has(absolutePath)) {\n return moduleCache.get(absolutePath);\n }\n\n throw new Error(\n `Handler module not registered: ${absolutePath}. ` +\n `Make sure initializeHandlers imports and registers the module.`\n );\n }\n\n return originalLoad.call(this, request, parent, isMain);\n };\n\n isPatched = true;\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/esm-resolver-I5MW2PIQ.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACF,6IAAC","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/esm-resolver-I5MW2PIQ.cjs"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -68,7 +68,7 @@ var getApplication = async (options) => {
|
|
|
68
68
|
activateESMResolver();
|
|
69
69
|
const handlersBasePath = typeof openApiValidator.operationHandlers === "string" ? openApiValidator.operationHandlers : openApiValidator.operationHandlers.basePath;
|
|
70
70
|
if (handlersBasePath) {
|
|
71
|
-
const { extractHandlerModules } = await Promise.resolve().then(() => _interopRequireWildcard(require("./openapi-parser-
|
|
71
|
+
const { extractHandlerModules } = await Promise.resolve().then(() => _interopRequireWildcard(require("./openapi-parser-NFUD7ZGZ.cjs")));
|
|
72
72
|
const { importAndRegisterHandlers } = await Promise.resolve().then(() => _interopRequireWildcard(require("./handler-importer-4BVBIZX3.cjs")));
|
|
73
73
|
try {
|
|
74
74
|
const modules = await extractHandlerModules(
|
|
@@ -220,21 +220,79 @@ var HttpProblem = (statusCode, options) => (response) => {
|
|
|
220
220
|
sendProblem(response, statusCode, options);
|
|
221
221
|
};
|
|
222
222
|
|
|
223
|
+
// src/openapi/firebase-auth.ts
|
|
224
|
+
var loadFirebaseAuth = async () => {
|
|
225
|
+
try {
|
|
226
|
+
const mod = await Promise.resolve().then(() => _interopRequireWildcard(require("@my-f-startup/firebase-auth-express")));
|
|
227
|
+
const provider = _nullishCoalesce(mod.default, () => ( mod));
|
|
228
|
+
const firebaseAuthMiddleware = provider.firebaseAuthMiddleware;
|
|
229
|
+
if (typeof firebaseAuthMiddleware !== "function") {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"Invalid @my-f-startup/firebase-auth-express module: missing firebaseAuthMiddleware export"
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return provider;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
const message = "@my-f-startup/firebase-auth-express is required for createFirebaseAuthSecurityHandlers. Install it with: npm install @my-f-startup/firebase-auth-express";
|
|
237
|
+
throw new Error(message, { cause: error });
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var createNullResponse = () => {
|
|
241
|
+
const res = {};
|
|
242
|
+
res.status = () => res;
|
|
243
|
+
res.json = () => res;
|
|
244
|
+
res.send = () => res;
|
|
245
|
+
res.end = () => res;
|
|
246
|
+
res.set = () => res;
|
|
247
|
+
return res;
|
|
248
|
+
};
|
|
249
|
+
var runMiddleware = async (middleware, req) => {
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
let nextCalled = false;
|
|
252
|
+
const res = createNullResponse();
|
|
253
|
+
const next = () => {
|
|
254
|
+
nextCalled = true;
|
|
255
|
+
resolve(true);
|
|
256
|
+
};
|
|
257
|
+
Promise.resolve(middleware(req, res, next)).then(() => {
|
|
258
|
+
if (!nextCalled) resolve(false);
|
|
259
|
+
}).catch(() => resolve(false));
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
var createFirebaseAuthSecurityHandlers = (options = {}) => {
|
|
263
|
+
const securitySchemeName = _nullishCoalesce(options.securitySchemeName, () => ( "bearerAuth"));
|
|
264
|
+
const roleClaim = _nullishCoalesce(options.roleClaim, () => ( "roles"));
|
|
265
|
+
return {
|
|
266
|
+
[securitySchemeName]: async (req, scopes, _schema) => {
|
|
267
|
+
const { firebaseAuthMiddleware } = await loadFirebaseAuth();
|
|
268
|
+
const middleware = firebaseAuthMiddleware({
|
|
269
|
+
authClient: options.authClient
|
|
270
|
+
});
|
|
271
|
+
const isAuthenticated = await runMiddleware(middleware, req);
|
|
272
|
+
if (!isAuthenticated) return false;
|
|
273
|
+
if (!scopes.length) return true;
|
|
274
|
+
const roles = _optionalChain([req, 'optionalAccess', _ => _.auth, 'optionalAccess', _2 => _2.token, 'optionalAccess', _3 => _3[roleClaim]]);
|
|
275
|
+
if (!Array.isArray(roles)) return false;
|
|
276
|
+
return scopes.every((scope) => roles.includes(scope));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
|
|
223
281
|
// src/openapi/index.ts
|
|
224
282
|
var createOpenApiValidatorOptions = (apiSpec, options) => {
|
|
225
283
|
return {
|
|
226
284
|
apiSpec,
|
|
227
|
-
validateRequests: _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
228
|
-
validateResponses: _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
229
|
-
validateSecurity: _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
230
|
-
validateFormats: _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
231
|
-
operationHandlers: _optionalChain([options, 'optionalAccess',
|
|
232
|
-
ignorePaths: _optionalChain([options, 'optionalAccess',
|
|
233
|
-
validateApiSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
234
|
-
$refParser: _optionalChain([options, 'optionalAccess',
|
|
235
|
-
serveSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
236
|
-
fileUploader: _optionalChain([options, 'optionalAccess',
|
|
237
|
-
initializeHandlers: _optionalChain([options, 'optionalAccess',
|
|
285
|
+
validateRequests: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _4 => _4.validateRequests]), () => ( true)),
|
|
286
|
+
validateResponses: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.validateResponses]), () => ( false)),
|
|
287
|
+
validateSecurity: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _6 => _6.validateSecurity]), () => ( true)),
|
|
288
|
+
validateFormats: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _7 => _7.validateFormats]), () => ( true)),
|
|
289
|
+
operationHandlers: _optionalChain([options, 'optionalAccess', _8 => _8.operationHandlers]),
|
|
290
|
+
ignorePaths: _optionalChain([options, 'optionalAccess', _9 => _9.ignorePaths]),
|
|
291
|
+
validateApiSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _10 => _10.validateApiSpec]), () => ( true)),
|
|
292
|
+
$refParser: _optionalChain([options, 'optionalAccess', _11 => _11.$refParser]),
|
|
293
|
+
serveSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _12 => _12.serveSpec]), () => ( false)),
|
|
294
|
+
fileUploader: _optionalChain([options, 'optionalAccess', _13 => _13.fileUploader]),
|
|
295
|
+
initializeHandlers: _optionalChain([options, 'optionalAccess', _14 => _14.initializeHandlers])
|
|
238
296
|
};
|
|
239
297
|
};
|
|
240
298
|
var isOpenApiValidatorAvailable = async () => {
|
|
@@ -424,5 +482,6 @@ var ApiSpecification = {
|
|
|
424
482
|
|
|
425
483
|
|
|
426
484
|
|
|
427
|
-
|
|
485
|
+
|
|
486
|
+
exports.Accepted = Accepted; exports.ApiE2ESpecification = ApiE2ESpecification; exports.ApiSpecification = ApiSpecification; exports.BadRequest = BadRequest; exports.Conflict = Conflict; exports.Created = Created; exports.DefaultHttpProblemResponseOptions = DefaultHttpProblemResponseOptions; exports.DefaultHttpResponseOptions = DefaultHttpResponseOptions; exports.ETagErrors = ETagErrors; exports.Forbidden = Forbidden; exports.HeaderNames = HeaderNames; exports.HttpProblem = HttpProblem; exports.HttpResponse = HttpResponse; exports.NoContent = NoContent; exports.NotFound = NotFound; exports.OK = OK; exports.PreconditionFailed = PreconditionFailed; exports.WeakETagRegex = WeakETagRegex; exports.createFirebaseAuthSecurityHandlers = createFirebaseAuthSecurityHandlers; exports.createOpenApiValidatorOptions = createOpenApiValidatorOptions; exports.existingStream = existingStream; exports.expect = expect; exports.expectError = expectError; exports.expectNewEvents = expectNewEvents; exports.expectResponse = expectResponse; exports.getApplication = getApplication; exports.getETagFromIfMatch = getETagFromIfMatch; exports.getETagFromIfNotMatch = getETagFromIfNotMatch; exports.getETagValueFromIfMatch = getETagValueFromIfMatch; exports.getWeakETagValue = getWeakETagValue; exports.isOpenApiValidatorAvailable = isOpenApiValidatorAvailable; exports.isWeakETag = isWeakETag; exports.on = on; exports.registerHandlerModule = _chunkMRSGTROWcjs.registerHandlerModule; exports.send = send; exports.sendAccepted = sendAccepted; exports.sendCreated = sendCreated; exports.sendProblem = sendProblem; exports.setETag = setETag; exports.startAPI = startAPI; exports.toWeakETag = toWeakETag;
|
|
428
487
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/axcosta/Git/pessoal/oss/emmett-community/emmett-expressjs-with-openapi/dist/index.cjs","../src/index.ts","../src/application.ts","../src/middlewares/problemDetailsMiddleware.ts","../src/etag.ts","../src/handler.ts","../src/openapi/index.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"names":["require","ETagErrors"],"mappings":"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","file":"/Users/axcosta/Git/pessoal/oss/emmett-community/emmett-expressjs-with-openapi/dist/index.cjs","sourcesContent":[null,"import 'express-async-errors';\n\nexport * from './application';\nexport * from './etag';\nexport * from './handler';\nexport * from './openapi';\nexport * from './responses';\nexport * from './testing';\nexport { registerHandlerModule } from './internal/esm-resolver';\n","import express, {\n Router,\n type Application,\n type RequestHandler,\n} from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { createRequire } from 'node:module';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { OpenApiValidatorOptions } from './openapi';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis?: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n /**\n * Optional OpenAPI validator configuration.\n * When provided, enables request/response validation against an OpenAPI specification.\n * Requires the 'express-openapi-validator' package to be installed.\n *\n * @see https://github.com/cdimascio/express-openapi-validator\n * @example\n * ```typescript\n * import { getApplication, createOpenApiValidatorOptions } from '@event-driven-io/emmett-expressjs';\n *\n * type AppDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * };\n *\n * const app = await getApplication({\n * openApiValidator: createOpenApiValidatorOptions<AppDeps>('./openapi.yaml', {\n * validateResponses: true,\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(deps.eventStore, deps.messageBus);\n * }\n * })\n * });\n * ```\n */\n openApiValidator?: OpenApiValidatorOptions;\n};\n\nexport const getApplication = async (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n openApiValidator,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n // add OpenAPI validator middleware if configured\n if (openApiValidator) {\n // Activate ESM resolver if operationHandlers are configured\n // This ensures handler modules are loaded via ESM import() instead of CJS require(),\n // preventing dual module loading issues when using TypeScript runtimes (tsx, ts-node)\n if (openApiValidator.operationHandlers) {\n const { activateESMResolver } = await import(\n './internal/esm-resolver.js'\n );\n activateESMResolver();\n\n // NEW: Auto-discover and import handler modules from OpenAPI spec\n const handlersBasePath =\n typeof openApiValidator.operationHandlers === 'string'\n ? openApiValidator.operationHandlers\n : openApiValidator.operationHandlers.basePath;\n\n if (handlersBasePath) {\n const { extractHandlerModules } = await import(\n './internal/openapi-parser.js'\n );\n const { importAndRegisterHandlers } = await import(\n './internal/handler-importer.js'\n );\n\n try {\n // Parse OpenAPI spec to find handler modules\n const modules = await extractHandlerModules(\n openApiValidator.apiSpec,\n handlersBasePath,\n );\n\n // Dynamically import and register all handler modules\n const importedHandlers = await importAndRegisterHandlers(modules);\n\n // Call user's initializeHandlers callback with imported modules\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers(importedHandlers);\n }\n } catch (error) {\n console.error('Failed to auto-import handler modules:', error);\n throw error;\n }\n }\n } else {\n // No operationHandlers, just call initializeHandlers if provided\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers();\n }\n }\n\n try {\n const require = createRequire(import.meta.url);\n // express-openapi-validator exports a default with .middleware (ESM/CJS compatibility)\n const mod = require('express-openapi-validator') as Record<\n string,\n unknown\n >;\n const provider = (mod.default ?? mod) as Record<string, unknown>;\n\n if (typeof provider.middleware !== 'function') {\n throw new Error(\n 'Invalid express-openapi-validator module: missing middleware export',\n );\n }\n\n // Serve OpenAPI spec if configured\n if (openApiValidator.serveSpec) {\n if (typeof openApiValidator.apiSpec === 'string') {\n // If apiSpec is a file path, serve it as a static file\n app.use(\n openApiValidator.serveSpec,\n express.static(openApiValidator.apiSpec),\n );\n } else {\n // If apiSpec is an object, serve it as JSON\n app.get(openApiValidator.serveSpec, (_req, res) => {\n res.json(openApiValidator.apiSpec);\n });\n }\n }\n\n const factory = provider.middleware as (\n opts: OpenApiValidatorOptions,\n ) => RequestHandler | RequestHandler[];\n const middleware = factory(openApiValidator);\n if (Array.isArray(middleware)) {\n for (const m of middleware) app.use(m);\n } else {\n app.use(middleware);\n }\n } catch {\n console.warn(\n 'OpenAPI validator configuration provided but express-openapi-validator package is not installed. ' +\n 'Install it with: npm install express-openapi-validator',\n );\n throw new Error(\n 'express-openapi-validator package is required when openApiValidator option is used',\n );\n }\n }\n\n // Register API routes if provided\n if (apis) {\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n }\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n // Prefer standard `status` code if present (e.g., express-openapi-validator)\n const errObj = error as unknown as Record<string, unknown>;\n const maybeStatus = errObj['status'];\n if (\n typeof maybeStatus === 'number' &&\n maybeStatus >= 100 &&\n maybeStatus < 600\n ) {\n statusCode = maybeStatus;\n }\n\n const maybeErrorCode = errObj['errorCode'];\n if (\n typeof maybeErrorCode === 'number' &&\n maybeErrorCode >= 100 &&\n maybeErrorCode < 600\n ) {\n statusCode = maybeErrorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import { type Brand } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag as string);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag as string);\n if (result === null || result.length === 0) {\n throw new Error(ETagErrors.WRONG_WEAK_ETAG_FORMAT);\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag as string);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue)\n ? getWeakETagValue(eTagValue)\n : (eTagValue as string);\n};\n","import { type NextFunction, type Request, type Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","/**\n * OpenAPI v3 Document type (to avoid requiring express-openapi-validator types directly)\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type OpenAPIV3Document = any;\n\n/**\n * Imported handler modules, keyed by module name.\n * Automatically populated by the framework when operationHandlers is configured.\n */\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Security handlers for custom authentication/authorization logic.\n * Maps security scheme names to handler functions.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n\nexport type SecurityHandlers = Record<\n string,\n (req: any, scopes: string[], schema: any) => boolean | Promise<boolean>\n>;\n\n/**\n * Configuration options for express-openapi-validator middleware.\n * This allows optional validation of API requests and responses against an OpenAPI specification.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/\n */\nexport type OpenApiValidatorOptions = {\n /**\n * Path to the OpenAPI specification file (JSON or YAML)\n * or an OpenAPI specification object.\n */\n apiSpec: string | OpenAPIV3Document;\n\n /**\n * Determines whether the validator should validate requests.\n * Can be a boolean or an object with detailed request validation options.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-requests/\n */\n validateRequests?:\n | boolean\n | {\n /**\n * Allow unknown query parameters (not defined in the spec).\n * @default false\n */\n allowUnknownQueryParameters?: boolean;\n /**\n * Coerce types in request parameters.\n * @default true\n */\n coerceTypes?: boolean | 'array';\n /**\n * Remove additional properties not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n };\n\n /**\n * Determines whether the validator should validate responses.\n * Can be a boolean or an object with detailed response validation options.\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-responses/\n */\n validateResponses?:\n | boolean\n | {\n /**\n * Remove additional properties from responses not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n /**\n * Coerce types in responses.\n * @default true\n */\n coerceTypes?: boolean;\n /**\n * Callback to handle response validation errors.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onError?: (error: any, body: any, req: any) => void;\n };\n\n /**\n * Determines whether the validator should validate security.\n * Can be a boolean or an object with security handlers.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n validateSecurity?:\n | boolean\n | {\n /**\n * Custom security handlers for authentication/authorization.\n */\n handlers?: SecurityHandlers;\n };\n\n /**\n * Defines how the validator should validate formats.\n * When true, uses ajv-formats for format validation.\n * When false, format validation is disabled.\n * Can also be 'fast' or 'full' for different validation modes.\n * @default true\n */\n validateFormats?: boolean | 'fast' | 'full';\n\n /**\n * The base path to the operation handlers directory.\n * When set to a path, automatically wires OpenAPI operations to handler functions\n * based on operationId or x-eov-operation-id.\n * When false, operation handlers are disabled (manual routing required).\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/guide-operation-handlers/\n */\n operationHandlers?:\n | string\n | false\n | {\n /**\n * Base path to operation handlers directory.\n */\n basePath?: string;\n /**\n * Resolver function to map operationId to handler module path.\n */\n resolver?: (\n handlersPath: string,\n route: string,\n apiDoc: OpenAPIV3Document,\n ) => string;\n };\n\n /**\n * Paths or pattern to ignore during validation.\n * @default undefined\n */\n ignorePaths?: RegExp | ((path: string) => boolean);\n\n /**\n * Validate the OpenAPI specification itself.\n * @default true\n */\n validateApiSpec?: boolean;\n\n /**\n * $ref parser configuration for handling OpenAPI references.\n * @default undefined\n */\n $refParser?: {\n mode: 'bundle' | 'dereference';\n };\n\n /**\n * Serve the OpenAPI specification at a specific path.\n * When set to a string, the spec will be served at that path.\n * When false, the spec will not be served.\n * @default false\n * @example '/api-docs/openapi.json'\n */\n serveSpec?: string | false;\n\n /**\n * File upload configuration options.\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-file-uploads/\n */\n fileUploader?:\n | boolean\n | {\n /**\n * Destination directory for uploaded files.\n */\n dest?: string;\n /**\n * File size limit in bytes.\n */\n limits?: {\n fileSize?: number;\n files?: number;\n };\n };\n\n /**\n * Optional callback to initialize operation handlers with dependencies.\n * Called before the OpenAPI validator middleware is configured.\n *\n * The framework automatically imports handler modules referenced in your\n * OpenAPI spec (via x-eov-operation-handler) and passes them as the first parameter.\n *\n * @param handlers - Auto-imported handler modules, keyed by module name\n * @returns void or a Promise that resolves when initialization is complete\n *\n * @example\n * ```typescript\n * // With automatic import (recommended)\n * initializeHandlers: async (handlers) => {\n * handlers.shoppingCarts.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n *\n * // Manual import (still supported for backward compatibility)\n * import * as handlersModule from './handlers/shoppingCarts';\n * import { registerHandlerModule } from '@emmett-community/emmett-expressjs-with-openapi';\n * initializeHandlers: () => {\n * const handlersPath = path.join(__dirname, './handlers/shoppingCarts');\n * registerHandlerModule(handlersPath, handlersModule);\n * handlersModule.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n * ```\n */\n initializeHandlers?: (\n handlers?: ImportedHandlerModules,\n ) => void | Promise<void>;\n};\n\n/**\n * Helper function to create OpenAPI validator configuration with sensible defaults.\n *\n * @param apiSpec - Path to OpenAPI spec file or OpenAPI document object\n * @param options - Additional validator options\n * @returns Complete OpenApiValidatorOptions configuration\n *\n * @example\n * ```typescript\n * // Basic usage with default options\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml');\n *\n * // With response validation enabled\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateResponses: true\n * });\n *\n * // With custom security handlers\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateSecurity: {\n * handlers: {\n * bearerAuth: async (req, scopes) => {\n * // Custom authentication logic\n * return true;\n * }\n * }\n * }\n * });\n *\n * // Serving the spec at /api-docs\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * serveSpec: '/api-docs/openapi.json'\n * });\n *\n * // With dependency injection for operation handlers\n * type ShoppingCartDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * getUnitPrice: (productId: string) => Promise<number>;\n * getCurrentTime: () => Date;\n * };\n *\n * const validatorOptions = createOpenApiValidatorOptions<ShoppingCartDeps>(\n * './openapi.yaml',\n * {\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(\n * deps.eventStore,\n * deps.messageBus,\n * deps.getUnitPrice,\n * deps.getCurrentTime\n * );\n * }\n * }\n * );\n *\n * const app = getApplication({\n * apis: [myApi],\n * openApiValidator: validatorOptions\n * });\n * ```\n */\nexport const createOpenApiValidatorOptions = (\n apiSpec: string | OpenAPIV3Document,\n options?: Partial<Omit<OpenApiValidatorOptions, 'apiSpec'>>,\n): OpenApiValidatorOptions => {\n return {\n apiSpec,\n validateRequests: options?.validateRequests ?? true,\n validateResponses: options?.validateResponses ?? false,\n validateSecurity: options?.validateSecurity ?? true,\n validateFormats: options?.validateFormats ?? true,\n operationHandlers: options?.operationHandlers,\n ignorePaths: options?.ignorePaths,\n validateApiSpec: options?.validateApiSpec ?? true,\n $refParser: options?.$refParser,\n serveSpec: options?.serveSpec ?? false,\n fileUploader: options?.fileUploader,\n initializeHandlers: options?.initializeHandlers,\n };\n};\n\n/**\n * Type guard to check if express-openapi-validator is available\n */\nexport const isOpenApiValidatorAvailable = async (): Promise<boolean> => {\n try {\n await import('express-openapi-validator');\n return true;\n } catch {\n return false;\n }\n};\n","import { type Request, type Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body: 'createdId' in options ? { id: options.createdId } : undefined,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport type { EventStore } from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nexport const ApiE2ESpecification = {\n for: <Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiE2ESpecification => {\n {\n return (...givenRequests: TestRequest[]) => {\n const eventStore = getEventStore();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiE2ESpecificationAssert,\n ): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n }\n },\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport { type Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nexport const ApiSpecification = {\n for: <\n EventType extends Event = Event,\n Store extends EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition> = EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition>\n >(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiSpecification<EventType> => {\n {\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const eventStore = WrapEventStore(getEventStore());\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n }\n },\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/index.cjs","../src/index.ts","../src/application.ts","../src/middlewares/problemDetailsMiddleware.ts","../src/etag.ts","../src/handler.ts","../src/openapi/firebase-auth.ts","../src/openapi/index.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"names":["require","ETagErrors"],"mappings":"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","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/index.cjs","sourcesContent":[null,"import 'express-async-errors';\n\nexport * from './application';\nexport * from './etag';\nexport * from './handler';\nexport * from './openapi';\nexport * from './responses';\nexport * from './testing';\nexport { registerHandlerModule } from './internal/esm-resolver';\n","import express, {\n Router,\n type Application,\n type RequestHandler,\n} from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { createRequire } from 'node:module';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { OpenApiValidatorOptions } from './openapi';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis?: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n /**\n * Optional OpenAPI validator configuration.\n * When provided, enables request/response validation against an OpenAPI specification.\n * Requires the 'express-openapi-validator' package to be installed.\n *\n * @see https://github.com/cdimascio/express-openapi-validator\n * @example\n * ```typescript\n * import { getApplication, createOpenApiValidatorOptions } from '@event-driven-io/emmett-expressjs';\n *\n * type AppDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * };\n *\n * const app = await getApplication({\n * openApiValidator: createOpenApiValidatorOptions<AppDeps>('./openapi.yaml', {\n * validateResponses: true,\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(deps.eventStore, deps.messageBus);\n * }\n * })\n * });\n * ```\n */\n openApiValidator?: OpenApiValidatorOptions;\n};\n\nexport const getApplication = async (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n openApiValidator,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n // add OpenAPI validator middleware if configured\n if (openApiValidator) {\n // Activate ESM resolver if operationHandlers are configured\n // This ensures handler modules are loaded via ESM import() instead of CJS require(),\n // preventing dual module loading issues when using TypeScript runtimes (tsx, ts-node)\n if (openApiValidator.operationHandlers) {\n const { activateESMResolver } = await import(\n './internal/esm-resolver.js'\n );\n activateESMResolver();\n\n // NEW: Auto-discover and import handler modules from OpenAPI spec\n const handlersBasePath =\n typeof openApiValidator.operationHandlers === 'string'\n ? openApiValidator.operationHandlers\n : openApiValidator.operationHandlers.basePath;\n\n if (handlersBasePath) {\n const { extractHandlerModules } = await import(\n './internal/openapi-parser.js'\n );\n const { importAndRegisterHandlers } = await import(\n './internal/handler-importer.js'\n );\n\n try {\n // Parse OpenAPI spec to find handler modules\n const modules = await extractHandlerModules(\n openApiValidator.apiSpec,\n handlersBasePath,\n );\n\n // Dynamically import and register all handler modules\n const importedHandlers = await importAndRegisterHandlers(modules);\n\n // Call user's initializeHandlers callback with imported modules\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers(importedHandlers);\n }\n } catch (error) {\n console.error('Failed to auto-import handler modules:', error);\n throw error;\n }\n }\n } else {\n // No operationHandlers, just call initializeHandlers if provided\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers();\n }\n }\n\n try {\n const require = createRequire(import.meta.url);\n // express-openapi-validator exports a default with .middleware (ESM/CJS compatibility)\n const mod = require('express-openapi-validator') as Record<\n string,\n unknown\n >;\n const provider = (mod.default ?? mod) as Record<string, unknown>;\n\n if (typeof provider.middleware !== 'function') {\n throw new Error(\n 'Invalid express-openapi-validator module: missing middleware export',\n );\n }\n\n // Serve OpenAPI spec if configured\n if (openApiValidator.serveSpec) {\n if (typeof openApiValidator.apiSpec === 'string') {\n // If apiSpec is a file path, serve it as a static file\n app.use(\n openApiValidator.serveSpec,\n express.static(openApiValidator.apiSpec),\n );\n } else {\n // If apiSpec is an object, serve it as JSON\n app.get(openApiValidator.serveSpec, (_req, res) => {\n res.json(openApiValidator.apiSpec);\n });\n }\n }\n\n const factory = provider.middleware as (\n opts: OpenApiValidatorOptions,\n ) => RequestHandler | RequestHandler[];\n const middleware = factory(openApiValidator);\n if (Array.isArray(middleware)) {\n for (const m of middleware) app.use(m);\n } else {\n app.use(middleware);\n }\n } catch {\n console.warn(\n 'OpenAPI validator configuration provided but express-openapi-validator package is not installed. ' +\n 'Install it with: npm install express-openapi-validator',\n );\n throw new Error(\n 'express-openapi-validator package is required when openApiValidator option is used',\n );\n }\n }\n\n // Register API routes if provided\n if (apis) {\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n }\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n // Prefer standard `status` code if present (e.g., express-openapi-validator)\n const errObj = error as unknown as Record<string, unknown>;\n const maybeStatus = errObj['status'];\n if (\n typeof maybeStatus === 'number' &&\n maybeStatus >= 100 &&\n maybeStatus < 600\n ) {\n statusCode = maybeStatus;\n }\n\n const maybeErrorCode = errObj['errorCode'];\n if (\n typeof maybeErrorCode === 'number' &&\n maybeErrorCode >= 100 &&\n maybeErrorCode < 600\n ) {\n statusCode = maybeErrorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import { type Brand } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag as string);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag as string);\n if (result === null || result.length === 0) {\n throw new Error(ETagErrors.WRONG_WEAK_ETAG_FORMAT);\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag as string);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue)\n ? getWeakETagValue(eTagValue)\n : (eTagValue as string);\n};\n","import { type NextFunction, type Request, type Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { SecurityHandlers } from './index';\n\ntype AuthClient = {\n verifyIdToken: (token: string) => Promise<unknown>;\n};\n\nexport type FirebaseAuthSecurityOptions = {\n /**\n * Name of the OpenAPI security scheme to attach the handler to.\n * Defaults to \"bearerAuth\".\n */\n securitySchemeName?: string;\n /**\n * Custom auth client for tests or alternate Firebase auth instances.\n */\n authClient?: AuthClient;\n /**\n * Token claim used for role-based checks when scopes are defined.\n * Defaults to \"roles\".\n */\n roleClaim?: string;\n};\n\ntype FirebaseAuthModule = {\n firebaseAuthMiddleware: (options?: { authClient?: AuthClient }) => (\n req: unknown,\n res: unknown,\n next: () => void,\n ) => Promise<void> | void;\n};\n\nconst loadFirebaseAuth = async (): Promise<FirebaseAuthModule> => {\n try {\n const mod = await import('@my-f-startup/firebase-auth-express');\n const provider = (mod as unknown as Record<string, unknown>).default ?? mod;\n const firebaseAuthMiddleware =\n (provider as Record<string, unknown>).firebaseAuthMiddleware;\n\n if (typeof firebaseAuthMiddleware !== 'function') {\n throw new Error(\n 'Invalid @my-f-startup/firebase-auth-express module: missing firebaseAuthMiddleware export',\n );\n }\n\n return provider as FirebaseAuthModule;\n } catch (error) {\n const message =\n '@my-f-startup/firebase-auth-express is required for createFirebaseAuthSecurityHandlers. ' +\n 'Install it with: npm install @my-f-startup/firebase-auth-express';\n throw new Error(message, { cause: error as Error });\n }\n};\n\nconst createNullResponse = () => {\n const res: Record<string, unknown> = {};\n res.status = () => res;\n res.json = () => res;\n res.send = () => res;\n res.end = () => res;\n res.set = () => res;\n return res;\n};\n\nconst runMiddleware = async (\n middleware: (req: unknown, res: unknown, next: () => void) => Promise<void> | void,\n req: unknown,\n): Promise<boolean> => {\n return new Promise((resolve) => {\n let nextCalled = false;\n const res = createNullResponse();\n const next = () => {\n nextCalled = true;\n resolve(true);\n };\n\n Promise.resolve(middleware(req, res, next))\n .then(() => {\n if (!nextCalled) resolve(false);\n })\n .catch(() => resolve(false));\n });\n};\n\nexport const createFirebaseAuthSecurityHandlers = (\n options: FirebaseAuthSecurityOptions = {},\n): SecurityHandlers => {\n const securitySchemeName = options.securitySchemeName ?? 'bearerAuth';\n const roleClaim = options.roleClaim ?? 'roles';\n\n return {\n [securitySchemeName]: async (req, scopes, _schema) => {\n const { firebaseAuthMiddleware } = await loadFirebaseAuth();\n const middleware = firebaseAuthMiddleware({\n authClient: options.authClient,\n });\n\n const isAuthenticated = await runMiddleware(middleware, req);\n if (!isAuthenticated) return false;\n\n if (!scopes.length) return true;\n\n const roles = (req as Record<string, any>)?.auth?.token?.[roleClaim];\n if (!Array.isArray(roles)) return false;\n\n return scopes.every((scope: string) => roles.includes(scope));\n },\n };\n};\n","/**\n * OpenAPI v3 Document type (to avoid requiring express-openapi-validator types directly)\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type OpenAPIV3Document = any;\n\n/**\n * Imported handler modules, keyed by module name.\n * Automatically populated by the framework when operationHandlers is configured.\n */\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Security handlers for custom authentication/authorization logic.\n * Maps security scheme names to handler functions.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n\nexport type SecurityHandlers = Record<\n string,\n (req: any, scopes: string[], schema: any) => boolean | Promise<boolean>\n>;\n\nexport * from './firebase-auth';\n\n/**\n * Configuration options for express-openapi-validator middleware.\n * This allows optional validation of API requests and responses against an OpenAPI specification.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/\n */\nexport type OpenApiValidatorOptions = {\n /**\n * Path to the OpenAPI specification file (JSON or YAML)\n * or an OpenAPI specification object.\n */\n apiSpec: string | OpenAPIV3Document;\n\n /**\n * Determines whether the validator should validate requests.\n * Can be a boolean or an object with detailed request validation options.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-requests/\n */\n validateRequests?:\n | boolean\n | {\n /**\n * Allow unknown query parameters (not defined in the spec).\n * @default false\n */\n allowUnknownQueryParameters?: boolean;\n /**\n * Coerce types in request parameters.\n * @default true\n */\n coerceTypes?: boolean | 'array';\n /**\n * Remove additional properties not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n };\n\n /**\n * Determines whether the validator should validate responses.\n * Can be a boolean or an object with detailed response validation options.\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-responses/\n */\n validateResponses?:\n | boolean\n | {\n /**\n * Remove additional properties from responses not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n /**\n * Coerce types in responses.\n * @default true\n */\n coerceTypes?: boolean;\n /**\n * Callback to handle response validation errors.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onError?: (error: any, body: any, req: any) => void;\n };\n\n /**\n * Determines whether the validator should validate security.\n * Can be a boolean or an object with security handlers.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n validateSecurity?:\n | boolean\n | {\n /**\n * Custom security handlers for authentication/authorization.\n */\n handlers?: SecurityHandlers;\n };\n\n /**\n * Defines how the validator should validate formats.\n * When true, uses ajv-formats for format validation.\n * When false, format validation is disabled.\n * Can also be 'fast' or 'full' for different validation modes.\n * @default true\n */\n validateFormats?: boolean | 'fast' | 'full';\n\n /**\n * The base path to the operation handlers directory.\n * When set to a path, automatically wires OpenAPI operations to handler functions\n * based on operationId or x-eov-operation-id.\n * When false, operation handlers are disabled (manual routing required).\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/guide-operation-handlers/\n */\n operationHandlers?:\n | string\n | false\n | {\n /**\n * Base path to operation handlers directory.\n */\n basePath?: string;\n /**\n * Resolver function to map operationId to handler module path.\n */\n resolver?: (\n handlersPath: string,\n route: string,\n apiDoc: OpenAPIV3Document,\n ) => string;\n };\n\n /**\n * Paths or pattern to ignore during validation.\n * @default undefined\n */\n ignorePaths?: RegExp | ((path: string) => boolean);\n\n /**\n * Validate the OpenAPI specification itself.\n * @default true\n */\n validateApiSpec?: boolean;\n\n /**\n * $ref parser configuration for handling OpenAPI references.\n * @default undefined\n */\n $refParser?: {\n mode: 'bundle' | 'dereference';\n };\n\n /**\n * Serve the OpenAPI specification at a specific path.\n * When set to a string, the spec will be served at that path.\n * When false, the spec will not be served.\n * @default false\n * @example '/api-docs/openapi.json'\n */\n serveSpec?: string | false;\n\n /**\n * File upload configuration options.\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-file-uploads/\n */\n fileUploader?:\n | boolean\n | {\n /**\n * Destination directory for uploaded files.\n */\n dest?: string;\n /**\n * File size limit in bytes.\n */\n limits?: {\n fileSize?: number;\n files?: number;\n };\n };\n\n /**\n * Optional callback to initialize operation handlers with dependencies.\n * Called before the OpenAPI validator middleware is configured.\n *\n * The framework automatically imports handler modules referenced in your\n * OpenAPI spec (via x-eov-operation-handler) and passes them as the first parameter.\n *\n * @param handlers - Auto-imported handler modules, keyed by module name\n * @returns void or a Promise that resolves when initialization is complete\n *\n * @example\n * ```typescript\n * // With automatic import (recommended)\n * initializeHandlers: async (handlers) => {\n * handlers.shoppingCarts.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n *\n * // Manual import (still supported for backward compatibility)\n * import * as handlersModule from './handlers/shoppingCarts';\n * import { registerHandlerModule } from '@emmett-community/emmett-expressjs-with-openapi';\n * initializeHandlers: () => {\n * const handlersPath = path.join(__dirname, './handlers/shoppingCarts');\n * registerHandlerModule(handlersPath, handlersModule);\n * handlersModule.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n * ```\n */\n initializeHandlers?: (\n handlers?: ImportedHandlerModules,\n ) => void | Promise<void>;\n};\n\n/**\n * Helper function to create OpenAPI validator configuration with sensible defaults.\n *\n * @param apiSpec - Path to OpenAPI spec file or OpenAPI document object\n * @param options - Additional validator options\n * @returns Complete OpenApiValidatorOptions configuration\n *\n * @example\n * ```typescript\n * // Basic usage with default options\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml');\n *\n * // With response validation enabled\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateResponses: true\n * });\n *\n * // With custom security handlers\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateSecurity: {\n * handlers: {\n * bearerAuth: async (req, scopes) => {\n * // Custom authentication logic\n * return true;\n * }\n * }\n * }\n * });\n *\n * // Serving the spec at /api-docs\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * serveSpec: '/api-docs/openapi.json'\n * });\n *\n * // With dependency injection for operation handlers\n * type ShoppingCartDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * getUnitPrice: (productId: string) => Promise<number>;\n * getCurrentTime: () => Date;\n * };\n *\n * const validatorOptions = createOpenApiValidatorOptions<ShoppingCartDeps>(\n * './openapi.yaml',\n * {\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(\n * deps.eventStore,\n * deps.messageBus,\n * deps.getUnitPrice,\n * deps.getCurrentTime\n * );\n * }\n * }\n * );\n *\n * const app = getApplication({\n * apis: [myApi],\n * openApiValidator: validatorOptions\n * });\n * ```\n */\nexport const createOpenApiValidatorOptions = (\n apiSpec: string | OpenAPIV3Document,\n options?: Partial<Omit<OpenApiValidatorOptions, 'apiSpec'>>,\n): OpenApiValidatorOptions => {\n return {\n apiSpec,\n validateRequests: options?.validateRequests ?? true,\n validateResponses: options?.validateResponses ?? false,\n validateSecurity: options?.validateSecurity ?? true,\n validateFormats: options?.validateFormats ?? true,\n operationHandlers: options?.operationHandlers,\n ignorePaths: options?.ignorePaths,\n validateApiSpec: options?.validateApiSpec ?? true,\n $refParser: options?.$refParser,\n serveSpec: options?.serveSpec ?? false,\n fileUploader: options?.fileUploader,\n initializeHandlers: options?.initializeHandlers,\n };\n};\n\n/**\n * Type guard to check if express-openapi-validator is available\n */\nexport const isOpenApiValidatorAvailable = async (): Promise<boolean> => {\n try {\n await import('express-openapi-validator');\n return true;\n } catch {\n return false;\n }\n};\n","import { type Request, type Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body: 'createdId' in options ? { id: options.createdId } : undefined,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport type { EventStore } from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nexport const ApiE2ESpecification = {\n for: <Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiE2ESpecification => {\n {\n return (...givenRequests: TestRequest[]) => {\n const eventStore = getEventStore();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiE2ESpecificationAssert,\n ): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n }\n },\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport { type Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nexport const ApiSpecification = {\n for: <\n EventType extends Event = Event,\n Store extends EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition> = EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition>\n >(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiSpecification<EventType> => {\n {\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const eventStore = WrapEventStore(getEventStore());\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n }\n },\n};\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,27 @@ import { Brand, Event, TestEventStream, EventStore } from '@event-driven-io/emme
|
|
|
6
6
|
import supertest, { Test, Response as Response$1 } from 'supertest';
|
|
7
7
|
import TestAgent from 'supertest/lib/agent';
|
|
8
8
|
|
|
9
|
+
type AuthClient = {
|
|
10
|
+
verifyIdToken: (token: string) => Promise<unknown>;
|
|
11
|
+
};
|
|
12
|
+
type FirebaseAuthSecurityOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* Name of the OpenAPI security scheme to attach the handler to.
|
|
15
|
+
* Defaults to "bearerAuth".
|
|
16
|
+
*/
|
|
17
|
+
securitySchemeName?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom auth client for tests or alternate Firebase auth instances.
|
|
20
|
+
*/
|
|
21
|
+
authClient?: AuthClient;
|
|
22
|
+
/**
|
|
23
|
+
* Token claim used for role-based checks when scopes are defined.
|
|
24
|
+
* Defaults to "roles".
|
|
25
|
+
*/
|
|
26
|
+
roleClaim?: string;
|
|
27
|
+
};
|
|
28
|
+
declare const createFirebaseAuthSecurityHandlers: (options?: FirebaseAuthSecurityOptions) => SecurityHandlers;
|
|
29
|
+
|
|
9
30
|
/**
|
|
10
31
|
* OpenAPI v3 Document type (to avoid requiring express-openapi-validator types directly)
|
|
11
32
|
*/
|
|
@@ -22,6 +43,7 @@ type ImportedHandlerModules = Record<string, any>;
|
|
|
22
43
|
* @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/
|
|
23
44
|
*/
|
|
24
45
|
type SecurityHandlers = Record<string, (req: any, scopes: string[], schema: any) => boolean | Promise<boolean>>;
|
|
46
|
+
|
|
25
47
|
/**
|
|
26
48
|
* Configuration options for express-openapi-validator middleware.
|
|
27
49
|
* This allows optional validation of API requests and responses against an OpenAPI specification.
|
|
@@ -437,4 +459,4 @@ declare const ApiE2ESpecification: {
|
|
|
437
459
|
*/
|
|
438
460
|
declare const registerHandlerModule: (modulePath: string, moduleExports: any) => void;
|
|
439
461
|
|
|
440
|
-
export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type OpenAPIV3Document, type OpenApiValidatorOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type WeakETag, WeakETagRegex, type WebApiSetup, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag };
|
|
462
|
+
export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, type FirebaseAuthSecurityOptions, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type OpenAPIV3Document, type OpenApiValidatorOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type WeakETag, WeakETagRegex, type WebApiSetup, createFirebaseAuthSecurityHandlers, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,27 @@ import { Brand, Event, TestEventStream, EventStore } from '@event-driven-io/emme
|
|
|
6
6
|
import supertest, { Test, Response as Response$1 } from 'supertest';
|
|
7
7
|
import TestAgent from 'supertest/lib/agent';
|
|
8
8
|
|
|
9
|
+
type AuthClient = {
|
|
10
|
+
verifyIdToken: (token: string) => Promise<unknown>;
|
|
11
|
+
};
|
|
12
|
+
type FirebaseAuthSecurityOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* Name of the OpenAPI security scheme to attach the handler to.
|
|
15
|
+
* Defaults to "bearerAuth".
|
|
16
|
+
*/
|
|
17
|
+
securitySchemeName?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom auth client for tests or alternate Firebase auth instances.
|
|
20
|
+
*/
|
|
21
|
+
authClient?: AuthClient;
|
|
22
|
+
/**
|
|
23
|
+
* Token claim used for role-based checks when scopes are defined.
|
|
24
|
+
* Defaults to "roles".
|
|
25
|
+
*/
|
|
26
|
+
roleClaim?: string;
|
|
27
|
+
};
|
|
28
|
+
declare const createFirebaseAuthSecurityHandlers: (options?: FirebaseAuthSecurityOptions) => SecurityHandlers;
|
|
29
|
+
|
|
9
30
|
/**
|
|
10
31
|
* OpenAPI v3 Document type (to avoid requiring express-openapi-validator types directly)
|
|
11
32
|
*/
|
|
@@ -22,6 +43,7 @@ type ImportedHandlerModules = Record<string, any>;
|
|
|
22
43
|
* @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/
|
|
23
44
|
*/
|
|
24
45
|
type SecurityHandlers = Record<string, (req: any, scopes: string[], schema: any) => boolean | Promise<boolean>>;
|
|
46
|
+
|
|
25
47
|
/**
|
|
26
48
|
* Configuration options for express-openapi-validator middleware.
|
|
27
49
|
* This allows optional validation of API requests and responses against an OpenAPI specification.
|
|
@@ -437,4 +459,4 @@ declare const ApiE2ESpecification: {
|
|
|
437
459
|
*/
|
|
438
460
|
declare const registerHandlerModule: (modulePath: string, moduleExports: any) => void;
|
|
439
461
|
|
|
440
|
-
export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type OpenAPIV3Document, type OpenApiValidatorOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type WeakETag, WeakETagRegex, type WebApiSetup, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag };
|
|
462
|
+
export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, type FirebaseAuthSecurityOptions, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type OpenAPIV3Document, type OpenApiValidatorOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type WeakETag, WeakETagRegex, type WebApiSetup, createFirebaseAuthSecurityHandlers, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag };
|
package/dist/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var getApplication = async (options) => {
|
|
|
65
65
|
activateESMResolver();
|
|
66
66
|
const handlersBasePath = typeof openApiValidator.operationHandlers === "string" ? openApiValidator.operationHandlers : openApiValidator.operationHandlers.basePath;
|
|
67
67
|
if (handlersBasePath) {
|
|
68
|
-
const { extractHandlerModules } = await import("./openapi-parser-
|
|
68
|
+
const { extractHandlerModules } = await import("./openapi-parser-CCYU636U.js");
|
|
69
69
|
const { importAndRegisterHandlers } = await import("./handler-importer-OJGFQON5.js");
|
|
70
70
|
try {
|
|
71
71
|
const modules = await extractHandlerModules(
|
|
@@ -217,6 +217,64 @@ var HttpProblem = (statusCode, options) => (response) => {
|
|
|
217
217
|
sendProblem(response, statusCode, options);
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
+
// src/openapi/firebase-auth.ts
|
|
221
|
+
var loadFirebaseAuth = async () => {
|
|
222
|
+
try {
|
|
223
|
+
const mod = await import("@my-f-startup/firebase-auth-express");
|
|
224
|
+
const provider = mod.default ?? mod;
|
|
225
|
+
const firebaseAuthMiddleware = provider.firebaseAuthMiddleware;
|
|
226
|
+
if (typeof firebaseAuthMiddleware !== "function") {
|
|
227
|
+
throw new Error(
|
|
228
|
+
"Invalid @my-f-startup/firebase-auth-express module: missing firebaseAuthMiddleware export"
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return provider;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const message = "@my-f-startup/firebase-auth-express is required for createFirebaseAuthSecurityHandlers. Install it with: npm install @my-f-startup/firebase-auth-express";
|
|
234
|
+
throw new Error(message, { cause: error });
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var createNullResponse = () => {
|
|
238
|
+
const res = {};
|
|
239
|
+
res.status = () => res;
|
|
240
|
+
res.json = () => res;
|
|
241
|
+
res.send = () => res;
|
|
242
|
+
res.end = () => res;
|
|
243
|
+
res.set = () => res;
|
|
244
|
+
return res;
|
|
245
|
+
};
|
|
246
|
+
var runMiddleware = async (middleware, req) => {
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
let nextCalled = false;
|
|
249
|
+
const res = createNullResponse();
|
|
250
|
+
const next = () => {
|
|
251
|
+
nextCalled = true;
|
|
252
|
+
resolve(true);
|
|
253
|
+
};
|
|
254
|
+
Promise.resolve(middleware(req, res, next)).then(() => {
|
|
255
|
+
if (!nextCalled) resolve(false);
|
|
256
|
+
}).catch(() => resolve(false));
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
var createFirebaseAuthSecurityHandlers = (options = {}) => {
|
|
260
|
+
const securitySchemeName = options.securitySchemeName ?? "bearerAuth";
|
|
261
|
+
const roleClaim = options.roleClaim ?? "roles";
|
|
262
|
+
return {
|
|
263
|
+
[securitySchemeName]: async (req, scopes, _schema) => {
|
|
264
|
+
const { firebaseAuthMiddleware } = await loadFirebaseAuth();
|
|
265
|
+
const middleware = firebaseAuthMiddleware({
|
|
266
|
+
authClient: options.authClient
|
|
267
|
+
});
|
|
268
|
+
const isAuthenticated = await runMiddleware(middleware, req);
|
|
269
|
+
if (!isAuthenticated) return false;
|
|
270
|
+
if (!scopes.length) return true;
|
|
271
|
+
const roles = req?.auth?.token?.[roleClaim];
|
|
272
|
+
if (!Array.isArray(roles)) return false;
|
|
273
|
+
return scopes.every((scope) => roles.includes(scope));
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
|
|
220
278
|
// src/openapi/index.ts
|
|
221
279
|
var createOpenApiValidatorOptions = (apiSpec, options) => {
|
|
222
280
|
return {
|
|
@@ -399,6 +457,7 @@ export {
|
|
|
399
457
|
OK,
|
|
400
458
|
PreconditionFailed,
|
|
401
459
|
WeakETagRegex,
|
|
460
|
+
createFirebaseAuthSecurityHandlers,
|
|
402
461
|
createOpenApiValidatorOptions,
|
|
403
462
|
existingStream,
|
|
404
463
|
expect,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/application.ts","../src/middlewares/problemDetailsMiddleware.ts","../src/etag.ts","../src/handler.ts","../src/openapi/index.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"sourcesContent":["import 'express-async-errors';\n\nexport * from './application';\nexport * from './etag';\nexport * from './handler';\nexport * from './openapi';\nexport * from './responses';\nexport * from './testing';\nexport { registerHandlerModule } from './internal/esm-resolver';\n","import express, {\n Router,\n type Application,\n type RequestHandler,\n} from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { createRequire } from 'node:module';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { OpenApiValidatorOptions } from './openapi';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis?: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n /**\n * Optional OpenAPI validator configuration.\n * When provided, enables request/response validation against an OpenAPI specification.\n * Requires the 'express-openapi-validator' package to be installed.\n *\n * @see https://github.com/cdimascio/express-openapi-validator\n * @example\n * ```typescript\n * import { getApplication, createOpenApiValidatorOptions } from '@event-driven-io/emmett-expressjs';\n *\n * type AppDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * };\n *\n * const app = await getApplication({\n * openApiValidator: createOpenApiValidatorOptions<AppDeps>('./openapi.yaml', {\n * validateResponses: true,\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(deps.eventStore, deps.messageBus);\n * }\n * })\n * });\n * ```\n */\n openApiValidator?: OpenApiValidatorOptions;\n};\n\nexport const getApplication = async (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n openApiValidator,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n // add OpenAPI validator middleware if configured\n if (openApiValidator) {\n // Activate ESM resolver if operationHandlers are configured\n // This ensures handler modules are loaded via ESM import() instead of CJS require(),\n // preventing dual module loading issues when using TypeScript runtimes (tsx, ts-node)\n if (openApiValidator.operationHandlers) {\n const { activateESMResolver } = await import(\n './internal/esm-resolver.js'\n );\n activateESMResolver();\n\n // NEW: Auto-discover and import handler modules from OpenAPI spec\n const handlersBasePath =\n typeof openApiValidator.operationHandlers === 'string'\n ? openApiValidator.operationHandlers\n : openApiValidator.operationHandlers.basePath;\n\n if (handlersBasePath) {\n const { extractHandlerModules } = await import(\n './internal/openapi-parser.js'\n );\n const { importAndRegisterHandlers } = await import(\n './internal/handler-importer.js'\n );\n\n try {\n // Parse OpenAPI spec to find handler modules\n const modules = await extractHandlerModules(\n openApiValidator.apiSpec,\n handlersBasePath,\n );\n\n // Dynamically import and register all handler modules\n const importedHandlers = await importAndRegisterHandlers(modules);\n\n // Call user's initializeHandlers callback with imported modules\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers(importedHandlers);\n }\n } catch (error) {\n console.error('Failed to auto-import handler modules:', error);\n throw error;\n }\n }\n } else {\n // No operationHandlers, just call initializeHandlers if provided\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers();\n }\n }\n\n try {\n const require = createRequire(import.meta.url);\n // express-openapi-validator exports a default with .middleware (ESM/CJS compatibility)\n const mod = require('express-openapi-validator') as Record<\n string,\n unknown\n >;\n const provider = (mod.default ?? mod) as Record<string, unknown>;\n\n if (typeof provider.middleware !== 'function') {\n throw new Error(\n 'Invalid express-openapi-validator module: missing middleware export',\n );\n }\n\n // Serve OpenAPI spec if configured\n if (openApiValidator.serveSpec) {\n if (typeof openApiValidator.apiSpec === 'string') {\n // If apiSpec is a file path, serve it as a static file\n app.use(\n openApiValidator.serveSpec,\n express.static(openApiValidator.apiSpec),\n );\n } else {\n // If apiSpec is an object, serve it as JSON\n app.get(openApiValidator.serveSpec, (_req, res) => {\n res.json(openApiValidator.apiSpec);\n });\n }\n }\n\n const factory = provider.middleware as (\n opts: OpenApiValidatorOptions,\n ) => RequestHandler | RequestHandler[];\n const middleware = factory(openApiValidator);\n if (Array.isArray(middleware)) {\n for (const m of middleware) app.use(m);\n } else {\n app.use(middleware);\n }\n } catch {\n console.warn(\n 'OpenAPI validator configuration provided but express-openapi-validator package is not installed. ' +\n 'Install it with: npm install express-openapi-validator',\n );\n throw new Error(\n 'express-openapi-validator package is required when openApiValidator option is used',\n );\n }\n }\n\n // Register API routes if provided\n if (apis) {\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n }\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n // Prefer standard `status` code if present (e.g., express-openapi-validator)\n const errObj = error as unknown as Record<string, unknown>;\n const maybeStatus = errObj['status'];\n if (\n typeof maybeStatus === 'number' &&\n maybeStatus >= 100 &&\n maybeStatus < 600\n ) {\n statusCode = maybeStatus;\n }\n\n const maybeErrorCode = errObj['errorCode'];\n if (\n typeof maybeErrorCode === 'number' &&\n maybeErrorCode >= 100 &&\n maybeErrorCode < 600\n ) {\n statusCode = maybeErrorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import { type Brand } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag as string);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag as string);\n if (result === null || result.length === 0) {\n throw new Error(ETagErrors.WRONG_WEAK_ETAG_FORMAT);\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag as string);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue)\n ? getWeakETagValue(eTagValue)\n : (eTagValue as string);\n};\n","import { type NextFunction, type Request, type Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","/**\n * OpenAPI v3 Document type (to avoid requiring express-openapi-validator types directly)\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type OpenAPIV3Document = any;\n\n/**\n * Imported handler modules, keyed by module name.\n * Automatically populated by the framework when operationHandlers is configured.\n */\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Security handlers for custom authentication/authorization logic.\n * Maps security scheme names to handler functions.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n\nexport type SecurityHandlers = Record<\n string,\n (req: any, scopes: string[], schema: any) => boolean | Promise<boolean>\n>;\n\n/**\n * Configuration options for express-openapi-validator middleware.\n * This allows optional validation of API requests and responses against an OpenAPI specification.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/\n */\nexport type OpenApiValidatorOptions = {\n /**\n * Path to the OpenAPI specification file (JSON or YAML)\n * or an OpenAPI specification object.\n */\n apiSpec: string | OpenAPIV3Document;\n\n /**\n * Determines whether the validator should validate requests.\n * Can be a boolean or an object with detailed request validation options.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-requests/\n */\n validateRequests?:\n | boolean\n | {\n /**\n * Allow unknown query parameters (not defined in the spec).\n * @default false\n */\n allowUnknownQueryParameters?: boolean;\n /**\n * Coerce types in request parameters.\n * @default true\n */\n coerceTypes?: boolean | 'array';\n /**\n * Remove additional properties not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n };\n\n /**\n * Determines whether the validator should validate responses.\n * Can be a boolean or an object with detailed response validation options.\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-responses/\n */\n validateResponses?:\n | boolean\n | {\n /**\n * Remove additional properties from responses not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n /**\n * Coerce types in responses.\n * @default true\n */\n coerceTypes?: boolean;\n /**\n * Callback to handle response validation errors.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onError?: (error: any, body: any, req: any) => void;\n };\n\n /**\n * Determines whether the validator should validate security.\n * Can be a boolean or an object with security handlers.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n validateSecurity?:\n | boolean\n | {\n /**\n * Custom security handlers for authentication/authorization.\n */\n handlers?: SecurityHandlers;\n };\n\n /**\n * Defines how the validator should validate formats.\n * When true, uses ajv-formats for format validation.\n * When false, format validation is disabled.\n * Can also be 'fast' or 'full' for different validation modes.\n * @default true\n */\n validateFormats?: boolean | 'fast' | 'full';\n\n /**\n * The base path to the operation handlers directory.\n * When set to a path, automatically wires OpenAPI operations to handler functions\n * based on operationId or x-eov-operation-id.\n * When false, operation handlers are disabled (manual routing required).\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/guide-operation-handlers/\n */\n operationHandlers?:\n | string\n | false\n | {\n /**\n * Base path to operation handlers directory.\n */\n basePath?: string;\n /**\n * Resolver function to map operationId to handler module path.\n */\n resolver?: (\n handlersPath: string,\n route: string,\n apiDoc: OpenAPIV3Document,\n ) => string;\n };\n\n /**\n * Paths or pattern to ignore during validation.\n * @default undefined\n */\n ignorePaths?: RegExp | ((path: string) => boolean);\n\n /**\n * Validate the OpenAPI specification itself.\n * @default true\n */\n validateApiSpec?: boolean;\n\n /**\n * $ref parser configuration for handling OpenAPI references.\n * @default undefined\n */\n $refParser?: {\n mode: 'bundle' | 'dereference';\n };\n\n /**\n * Serve the OpenAPI specification at a specific path.\n * When set to a string, the spec will be served at that path.\n * When false, the spec will not be served.\n * @default false\n * @example '/api-docs/openapi.json'\n */\n serveSpec?: string | false;\n\n /**\n * File upload configuration options.\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-file-uploads/\n */\n fileUploader?:\n | boolean\n | {\n /**\n * Destination directory for uploaded files.\n */\n dest?: string;\n /**\n * File size limit in bytes.\n */\n limits?: {\n fileSize?: number;\n files?: number;\n };\n };\n\n /**\n * Optional callback to initialize operation handlers with dependencies.\n * Called before the OpenAPI validator middleware is configured.\n *\n * The framework automatically imports handler modules referenced in your\n * OpenAPI spec (via x-eov-operation-handler) and passes them as the first parameter.\n *\n * @param handlers - Auto-imported handler modules, keyed by module name\n * @returns void or a Promise that resolves when initialization is complete\n *\n * @example\n * ```typescript\n * // With automatic import (recommended)\n * initializeHandlers: async (handlers) => {\n * handlers.shoppingCarts.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n *\n * // Manual import (still supported for backward compatibility)\n * import * as handlersModule from './handlers/shoppingCarts';\n * import { registerHandlerModule } from '@emmett-community/emmett-expressjs-with-openapi';\n * initializeHandlers: () => {\n * const handlersPath = path.join(__dirname, './handlers/shoppingCarts');\n * registerHandlerModule(handlersPath, handlersModule);\n * handlersModule.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n * ```\n */\n initializeHandlers?: (\n handlers?: ImportedHandlerModules,\n ) => void | Promise<void>;\n};\n\n/**\n * Helper function to create OpenAPI validator configuration with sensible defaults.\n *\n * @param apiSpec - Path to OpenAPI spec file or OpenAPI document object\n * @param options - Additional validator options\n * @returns Complete OpenApiValidatorOptions configuration\n *\n * @example\n * ```typescript\n * // Basic usage with default options\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml');\n *\n * // With response validation enabled\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateResponses: true\n * });\n *\n * // With custom security handlers\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateSecurity: {\n * handlers: {\n * bearerAuth: async (req, scopes) => {\n * // Custom authentication logic\n * return true;\n * }\n * }\n * }\n * });\n *\n * // Serving the spec at /api-docs\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * serveSpec: '/api-docs/openapi.json'\n * });\n *\n * // With dependency injection for operation handlers\n * type ShoppingCartDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * getUnitPrice: (productId: string) => Promise<number>;\n * getCurrentTime: () => Date;\n * };\n *\n * const validatorOptions = createOpenApiValidatorOptions<ShoppingCartDeps>(\n * './openapi.yaml',\n * {\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(\n * deps.eventStore,\n * deps.messageBus,\n * deps.getUnitPrice,\n * deps.getCurrentTime\n * );\n * }\n * }\n * );\n *\n * const app = getApplication({\n * apis: [myApi],\n * openApiValidator: validatorOptions\n * });\n * ```\n */\nexport const createOpenApiValidatorOptions = (\n apiSpec: string | OpenAPIV3Document,\n options?: Partial<Omit<OpenApiValidatorOptions, 'apiSpec'>>,\n): OpenApiValidatorOptions => {\n return {\n apiSpec,\n validateRequests: options?.validateRequests ?? true,\n validateResponses: options?.validateResponses ?? false,\n validateSecurity: options?.validateSecurity ?? true,\n validateFormats: options?.validateFormats ?? true,\n operationHandlers: options?.operationHandlers,\n ignorePaths: options?.ignorePaths,\n validateApiSpec: options?.validateApiSpec ?? true,\n $refParser: options?.$refParser,\n serveSpec: options?.serveSpec ?? false,\n fileUploader: options?.fileUploader,\n initializeHandlers: options?.initializeHandlers,\n };\n};\n\n/**\n * Type guard to check if express-openapi-validator is available\n */\nexport const isOpenApiValidatorAvailable = async (): Promise<boolean> => {\n try {\n await import('express-openapi-validator');\n return true;\n } catch {\n return false;\n }\n};\n","import { type Request, type Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body: 'createdId' in options ? { id: options.createdId } : undefined,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport type { EventStore } from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nexport const ApiE2ESpecification = {\n for: <Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiE2ESpecification => {\n {\n return (...givenRequests: TestRequest[]) => {\n const eventStore = getEventStore();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiE2ESpecificationAssert,\n ): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n }\n },\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport { type Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nexport const ApiSpecification = {\n for: <\n EventType extends Event = Event,\n Store extends EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition> = EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition>\n >(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiSpecification<EventType> => {\n {\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const eventStore = WrapEventStore(getEventStore());\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n }\n },\n};\n"],"mappings":"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","names":["require","ETagErrors","ProblemDocument","ProblemDocument","getApplication","supertest","getApplication"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/application.ts","../src/middlewares/problemDetailsMiddleware.ts","../src/etag.ts","../src/handler.ts","../src/openapi/firebase-auth.ts","../src/openapi/index.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"sourcesContent":["import 'express-async-errors';\n\nexport * from './application';\nexport * from './etag';\nexport * from './handler';\nexport * from './openapi';\nexport * from './responses';\nexport * from './testing';\nexport { registerHandlerModule } from './internal/esm-resolver';\n","import express, {\n Router,\n type Application,\n type RequestHandler,\n} from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { createRequire } from 'node:module';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { OpenApiValidatorOptions } from './openapi';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis?: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n /**\n * Optional OpenAPI validator configuration.\n * When provided, enables request/response validation against an OpenAPI specification.\n * Requires the 'express-openapi-validator' package to be installed.\n *\n * @see https://github.com/cdimascio/express-openapi-validator\n * @example\n * ```typescript\n * import { getApplication, createOpenApiValidatorOptions } from '@event-driven-io/emmett-expressjs';\n *\n * type AppDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * };\n *\n * const app = await getApplication({\n * openApiValidator: createOpenApiValidatorOptions<AppDeps>('./openapi.yaml', {\n * validateResponses: true,\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(deps.eventStore, deps.messageBus);\n * }\n * })\n * });\n * ```\n */\n openApiValidator?: OpenApiValidatorOptions;\n};\n\nexport const getApplication = async (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n openApiValidator,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n // add OpenAPI validator middleware if configured\n if (openApiValidator) {\n // Activate ESM resolver if operationHandlers are configured\n // This ensures handler modules are loaded via ESM import() instead of CJS require(),\n // preventing dual module loading issues when using TypeScript runtimes (tsx, ts-node)\n if (openApiValidator.operationHandlers) {\n const { activateESMResolver } = await import(\n './internal/esm-resolver.js'\n );\n activateESMResolver();\n\n // NEW: Auto-discover and import handler modules from OpenAPI spec\n const handlersBasePath =\n typeof openApiValidator.operationHandlers === 'string'\n ? openApiValidator.operationHandlers\n : openApiValidator.operationHandlers.basePath;\n\n if (handlersBasePath) {\n const { extractHandlerModules } = await import(\n './internal/openapi-parser.js'\n );\n const { importAndRegisterHandlers } = await import(\n './internal/handler-importer.js'\n );\n\n try {\n // Parse OpenAPI spec to find handler modules\n const modules = await extractHandlerModules(\n openApiValidator.apiSpec,\n handlersBasePath,\n );\n\n // Dynamically import and register all handler modules\n const importedHandlers = await importAndRegisterHandlers(modules);\n\n // Call user's initializeHandlers callback with imported modules\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers(importedHandlers);\n }\n } catch (error) {\n console.error('Failed to auto-import handler modules:', error);\n throw error;\n }\n }\n } else {\n // No operationHandlers, just call initializeHandlers if provided\n if (openApiValidator.initializeHandlers) {\n await openApiValidator.initializeHandlers();\n }\n }\n\n try {\n const require = createRequire(import.meta.url);\n // express-openapi-validator exports a default with .middleware (ESM/CJS compatibility)\n const mod = require('express-openapi-validator') as Record<\n string,\n unknown\n >;\n const provider = (mod.default ?? mod) as Record<string, unknown>;\n\n if (typeof provider.middleware !== 'function') {\n throw new Error(\n 'Invalid express-openapi-validator module: missing middleware export',\n );\n }\n\n // Serve OpenAPI spec if configured\n if (openApiValidator.serveSpec) {\n if (typeof openApiValidator.apiSpec === 'string') {\n // If apiSpec is a file path, serve it as a static file\n app.use(\n openApiValidator.serveSpec,\n express.static(openApiValidator.apiSpec),\n );\n } else {\n // If apiSpec is an object, serve it as JSON\n app.get(openApiValidator.serveSpec, (_req, res) => {\n res.json(openApiValidator.apiSpec);\n });\n }\n }\n\n const factory = provider.middleware as (\n opts: OpenApiValidatorOptions,\n ) => RequestHandler | RequestHandler[];\n const middleware = factory(openApiValidator);\n if (Array.isArray(middleware)) {\n for (const m of middleware) app.use(m);\n } else {\n app.use(middleware);\n }\n } catch {\n console.warn(\n 'OpenAPI validator configuration provided but express-openapi-validator package is not installed. ' +\n 'Install it with: npm install express-openapi-validator',\n );\n throw new Error(\n 'express-openapi-validator package is required when openApiValidator option is used',\n );\n }\n }\n\n // Register API routes if provided\n if (apis) {\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n }\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n // Prefer standard `status` code if present (e.g., express-openapi-validator)\n const errObj = error as unknown as Record<string, unknown>;\n const maybeStatus = errObj['status'];\n if (\n typeof maybeStatus === 'number' &&\n maybeStatus >= 100 &&\n maybeStatus < 600\n ) {\n statusCode = maybeStatus;\n }\n\n const maybeErrorCode = errObj['errorCode'];\n if (\n typeof maybeErrorCode === 'number' &&\n maybeErrorCode >= 100 &&\n maybeErrorCode < 600\n ) {\n statusCode = maybeErrorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import { type Brand } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag as string);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag as string);\n if (result === null || result.length === 0) {\n throw new Error(ETagErrors.WRONG_WEAK_ETAG_FORMAT);\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new Error(ETagErrors.MISSING_IF_MATCH_HEADER);\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag as string);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue)\n ? getWeakETagValue(eTagValue)\n : (eTagValue as string);\n};\n","import { type NextFunction, type Request, type Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { SecurityHandlers } from './index';\n\ntype AuthClient = {\n verifyIdToken: (token: string) => Promise<unknown>;\n};\n\nexport type FirebaseAuthSecurityOptions = {\n /**\n * Name of the OpenAPI security scheme to attach the handler to.\n * Defaults to \"bearerAuth\".\n */\n securitySchemeName?: string;\n /**\n * Custom auth client for tests or alternate Firebase auth instances.\n */\n authClient?: AuthClient;\n /**\n * Token claim used for role-based checks when scopes are defined.\n * Defaults to \"roles\".\n */\n roleClaim?: string;\n};\n\ntype FirebaseAuthModule = {\n firebaseAuthMiddleware: (options?: { authClient?: AuthClient }) => (\n req: unknown,\n res: unknown,\n next: () => void,\n ) => Promise<void> | void;\n};\n\nconst loadFirebaseAuth = async (): Promise<FirebaseAuthModule> => {\n try {\n const mod = await import('@my-f-startup/firebase-auth-express');\n const provider = (mod as unknown as Record<string, unknown>).default ?? mod;\n const firebaseAuthMiddleware =\n (provider as Record<string, unknown>).firebaseAuthMiddleware;\n\n if (typeof firebaseAuthMiddleware !== 'function') {\n throw new Error(\n 'Invalid @my-f-startup/firebase-auth-express module: missing firebaseAuthMiddleware export',\n );\n }\n\n return provider as FirebaseAuthModule;\n } catch (error) {\n const message =\n '@my-f-startup/firebase-auth-express is required for createFirebaseAuthSecurityHandlers. ' +\n 'Install it with: npm install @my-f-startup/firebase-auth-express';\n throw new Error(message, { cause: error as Error });\n }\n};\n\nconst createNullResponse = () => {\n const res: Record<string, unknown> = {};\n res.status = () => res;\n res.json = () => res;\n res.send = () => res;\n res.end = () => res;\n res.set = () => res;\n return res;\n};\n\nconst runMiddleware = async (\n middleware: (req: unknown, res: unknown, next: () => void) => Promise<void> | void,\n req: unknown,\n): Promise<boolean> => {\n return new Promise((resolve) => {\n let nextCalled = false;\n const res = createNullResponse();\n const next = () => {\n nextCalled = true;\n resolve(true);\n };\n\n Promise.resolve(middleware(req, res, next))\n .then(() => {\n if (!nextCalled) resolve(false);\n })\n .catch(() => resolve(false));\n });\n};\n\nexport const createFirebaseAuthSecurityHandlers = (\n options: FirebaseAuthSecurityOptions = {},\n): SecurityHandlers => {\n const securitySchemeName = options.securitySchemeName ?? 'bearerAuth';\n const roleClaim = options.roleClaim ?? 'roles';\n\n return {\n [securitySchemeName]: async (req, scopes, _schema) => {\n const { firebaseAuthMiddleware } = await loadFirebaseAuth();\n const middleware = firebaseAuthMiddleware({\n authClient: options.authClient,\n });\n\n const isAuthenticated = await runMiddleware(middleware, req);\n if (!isAuthenticated) return false;\n\n if (!scopes.length) return true;\n\n const roles = (req as Record<string, any>)?.auth?.token?.[roleClaim];\n if (!Array.isArray(roles)) return false;\n\n return scopes.every((scope: string) => roles.includes(scope));\n },\n };\n};\n","/**\n * OpenAPI v3 Document type (to avoid requiring express-openapi-validator types directly)\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type OpenAPIV3Document = any;\n\n/**\n * Imported handler modules, keyed by module name.\n * Automatically populated by the framework when operationHandlers is configured.\n */\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Security handlers for custom authentication/authorization logic.\n * Maps security scheme names to handler functions.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n\nexport type SecurityHandlers = Record<\n string,\n (req: any, scopes: string[], schema: any) => boolean | Promise<boolean>\n>;\n\nexport * from './firebase-auth';\n\n/**\n * Configuration options for express-openapi-validator middleware.\n * This allows optional validation of API requests and responses against an OpenAPI specification.\n *\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/\n */\nexport type OpenApiValidatorOptions = {\n /**\n * Path to the OpenAPI specification file (JSON or YAML)\n * or an OpenAPI specification object.\n */\n apiSpec: string | OpenAPIV3Document;\n\n /**\n * Determines whether the validator should validate requests.\n * Can be a boolean or an object with detailed request validation options.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-requests/\n */\n validateRequests?:\n | boolean\n | {\n /**\n * Allow unknown query parameters (not defined in the spec).\n * @default false\n */\n allowUnknownQueryParameters?: boolean;\n /**\n * Coerce types in request parameters.\n * @default true\n */\n coerceTypes?: boolean | 'array';\n /**\n * Remove additional properties not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n };\n\n /**\n * Determines whether the validator should validate responses.\n * Can be a boolean or an object with detailed response validation options.\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-responses/\n */\n validateResponses?:\n | boolean\n | {\n /**\n * Remove additional properties from responses not defined in the spec.\n * @default false\n */\n removeAdditional?: boolean | 'all' | 'failing';\n /**\n * Coerce types in responses.\n * @default true\n */\n coerceTypes?: boolean;\n /**\n * Callback to handle response validation errors.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onError?: (error: any, body: any, req: any) => void;\n };\n\n /**\n * Determines whether the validator should validate security.\n * Can be a boolean or an object with security handlers.\n * @default true\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-validate-security/\n */\n validateSecurity?:\n | boolean\n | {\n /**\n * Custom security handlers for authentication/authorization.\n */\n handlers?: SecurityHandlers;\n };\n\n /**\n * Defines how the validator should validate formats.\n * When true, uses ajv-formats for format validation.\n * When false, format validation is disabled.\n * Can also be 'fast' or 'full' for different validation modes.\n * @default true\n */\n validateFormats?: boolean | 'fast' | 'full';\n\n /**\n * The base path to the operation handlers directory.\n * When set to a path, automatically wires OpenAPI operations to handler functions\n * based on operationId or x-eov-operation-id.\n * When false, operation handlers are disabled (manual routing required).\n * @default false\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/guide-operation-handlers/\n */\n operationHandlers?:\n | string\n | false\n | {\n /**\n * Base path to operation handlers directory.\n */\n basePath?: string;\n /**\n * Resolver function to map operationId to handler module path.\n */\n resolver?: (\n handlersPath: string,\n route: string,\n apiDoc: OpenAPIV3Document,\n ) => string;\n };\n\n /**\n * Paths or pattern to ignore during validation.\n * @default undefined\n */\n ignorePaths?: RegExp | ((path: string) => boolean);\n\n /**\n * Validate the OpenAPI specification itself.\n * @default true\n */\n validateApiSpec?: boolean;\n\n /**\n * $ref parser configuration for handling OpenAPI references.\n * @default undefined\n */\n $refParser?: {\n mode: 'bundle' | 'dereference';\n };\n\n /**\n * Serve the OpenAPI specification at a specific path.\n * When set to a string, the spec will be served at that path.\n * When false, the spec will not be served.\n * @default false\n * @example '/api-docs/openapi.json'\n */\n serveSpec?: string | false;\n\n /**\n * File upload configuration options.\n * @see https://cdimascio.github.io/express-openapi-validator-documentation/usage-file-uploads/\n */\n fileUploader?:\n | boolean\n | {\n /**\n * Destination directory for uploaded files.\n */\n dest?: string;\n /**\n * File size limit in bytes.\n */\n limits?: {\n fileSize?: number;\n files?: number;\n };\n };\n\n /**\n * Optional callback to initialize operation handlers with dependencies.\n * Called before the OpenAPI validator middleware is configured.\n *\n * The framework automatically imports handler modules referenced in your\n * OpenAPI spec (via x-eov-operation-handler) and passes them as the first parameter.\n *\n * @param handlers - Auto-imported handler modules, keyed by module name\n * @returns void or a Promise that resolves when initialization is complete\n *\n * @example\n * ```typescript\n * // With automatic import (recommended)\n * initializeHandlers: async (handlers) => {\n * handlers.shoppingCarts.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n *\n * // Manual import (still supported for backward compatibility)\n * import * as handlersModule from './handlers/shoppingCarts';\n * import { registerHandlerModule } from '@emmett-community/emmett-expressjs-with-openapi';\n * initializeHandlers: () => {\n * const handlersPath = path.join(__dirname, './handlers/shoppingCarts');\n * registerHandlerModule(handlersPath, handlersModule);\n * handlersModule.initializeHandlers(eventStore, messageBus, getUnitPrice, getCurrentTime);\n * }\n * ```\n */\n initializeHandlers?: (\n handlers?: ImportedHandlerModules,\n ) => void | Promise<void>;\n};\n\n/**\n * Helper function to create OpenAPI validator configuration with sensible defaults.\n *\n * @param apiSpec - Path to OpenAPI spec file or OpenAPI document object\n * @param options - Additional validator options\n * @returns Complete OpenApiValidatorOptions configuration\n *\n * @example\n * ```typescript\n * // Basic usage with default options\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml');\n *\n * // With response validation enabled\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateResponses: true\n * });\n *\n * // With custom security handlers\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * validateSecurity: {\n * handlers: {\n * bearerAuth: async (req, scopes) => {\n * // Custom authentication logic\n * return true;\n * }\n * }\n * }\n * });\n *\n * // Serving the spec at /api-docs\n * const validatorOptions = createOpenApiValidatorOptions('./openapi.yaml', {\n * serveSpec: '/api-docs/openapi.json'\n * });\n *\n * // With dependency injection for operation handlers\n * type ShoppingCartDeps = {\n * eventStore: EventStore;\n * messageBus: EventsPublisher;\n * getUnitPrice: (productId: string) => Promise<number>;\n * getCurrentTime: () => Date;\n * };\n *\n * const validatorOptions = createOpenApiValidatorOptions<ShoppingCartDeps>(\n * './openapi.yaml',\n * {\n * operationHandlers: './handlers',\n * initializeHandlers: (deps) => {\n * initializeHandlers(\n * deps.eventStore,\n * deps.messageBus,\n * deps.getUnitPrice,\n * deps.getCurrentTime\n * );\n * }\n * }\n * );\n *\n * const app = getApplication({\n * apis: [myApi],\n * openApiValidator: validatorOptions\n * });\n * ```\n */\nexport const createOpenApiValidatorOptions = (\n apiSpec: string | OpenAPIV3Document,\n options?: Partial<Omit<OpenApiValidatorOptions, 'apiSpec'>>,\n): OpenApiValidatorOptions => {\n return {\n apiSpec,\n validateRequests: options?.validateRequests ?? true,\n validateResponses: options?.validateResponses ?? false,\n validateSecurity: options?.validateSecurity ?? true,\n validateFormats: options?.validateFormats ?? true,\n operationHandlers: options?.operationHandlers,\n ignorePaths: options?.ignorePaths,\n validateApiSpec: options?.validateApiSpec ?? true,\n $refParser: options?.$refParser,\n serveSpec: options?.serveSpec ?? false,\n fileUploader: options?.fileUploader,\n initializeHandlers: options?.initializeHandlers,\n };\n};\n\n/**\n * Type guard to check if express-openapi-validator is available\n */\nexport const isOpenApiValidatorAvailable = async (): Promise<boolean> => {\n try {\n await import('express-openapi-validator');\n return true;\n } catch {\n return false;\n }\n};\n","import { type Request, type Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body: 'createdId' in options ? { id: options.createdId } : undefined,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport type { EventStore } from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nexport const ApiE2ESpecification = {\n for: <Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiE2ESpecification => {\n {\n return (...givenRequests: TestRequest[]) => {\n const eventStore = getEventStore();\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiE2ESpecificationAssert,\n ): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n }\n },\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport { type Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nexport const ApiSpecification = {\n for: <\n EventType extends Event = Event,\n Store extends EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition> = EventStore<import('@event-driven-io/emmett').ReadEventMetadataWithGlobalPosition>\n >(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application | Promise<Application>,\n ): ApiSpecification<EventType> => {\n {\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const eventStore = WrapEventStore(getEventStore());\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n const application = await Promise.resolve(getApplication(eventStore));\n\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n }\n },\n};\n"],"mappings":"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","names":["require","ETagErrors","ProblemDocument","ProblemDocument","getApplication","supertest","getApplication"]}
|
|
@@ -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';\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":[]}
|
|
@@ -0,0 +1 @@
|
|
|
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":"AAAA,2dAA6B;AAC7B;AACA;ACQA,uCAAyB;AACzB,wEAAiB;AAmBjB,MAAA,SAAsB,qBAAA,CACpB,OAAA,EACA,gBAAA,EAC8B;AAE9B,EAAA,MAAM,KAAA,EACJ,OAAO,QAAA,IAAY,SAAA,EAAW,MAAM,eAAA,CAAgB,OAAO,EAAA,EAAI,OAAA;AAGjE,EAAA,GAAA,CAAI,CAAC,IAAA,CAAK,MAAA,GAAS,OAAO,IAAA,CAAK,MAAA,IAAU,QAAA,EAAU;AACjD,IAAA,MAAM,IAAI,KAAA,CAAM,iEAAiE,CAAA;AAAA,EACnF;AAGA,EAAA,MAAM,YAAA,kBAAc,IAAI,GAAA,CAA+B,CAAA;AAEvD,EAAA,IAAA,CAAA,MAAW,CAAC,OAAA,EAAS,QAAQ,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,EAAG;AAC5D,IAAA,GAAA,CAAI,CAAC,SAAA,GAAY,OAAO,SAAA,IAAa,QAAA,EAAU,QAAA;AAE/C,IAAA,IAAA,CAAA,MAAW,CAAC,MAAA,EAAQ,SAAS,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC1D,MAAA,GAAA,CAAI,OAAA,IAAW,aAAA,GAAgB,OAAA,IAAW,SAAA,EAAW,QAAA;AACrD,MAAA,GAAA,CAAI,CAAC,UAAA,GAAa,OAAO,UAAA,IAAc,QAAA,EAAU,QAAA;AAEjD,MAAA,MAAM,YAAA,EAAe,SAAA,CAAkB,yBAAyB,CAAA;AAChE,MAAA,MAAM,YAAA,EACH,SAAA,CAAkB,oBAAoB,EAAA,GACtC,SAAA,CAAkB,WAAA;AAErB,MAAA,GAAA,CAAI,YAAA,GAAe,OAAO,YAAA,IAAgB,QAAA,EAAU;AAClD,QAAA,MAAM,aAAA,EAAe,kBAAA;AAAA,UACnB,gBAAA;AAAA,UACA;AAAA,QACF,CAAA;AAEA,QAAA,GAAA,CAAI,CAAC,WAAA,CAAY,GAAA,CAAI,WAAW,CAAA,EAAG;AACjC,UAAA,WAAA,CAAY,GAAA,CAAI,WAAA,EAAa;AAAA,YAC3B,UAAA,EAAY,WAAA;AAAA,YACZ,YAAA,EAAc,WAAA;AAAA,YACd,YAAA;AAAA,YACA,YAAA,EAAc,CAAC;AAAA,UACjB,CAAC,CAAA;AAAA,QACH;AAEA,QAAA,GAAA,CAAI,WAAA,EAAa;AACf,UAAA,WAAA,CAAY,GAAA,CAAI,WAAW,CAAA,CAAG,YAAA,CAAa,IAAA,CAAK,WAAW,CAAA;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,CAAC,CAAA;AACxC;AAKA,MAAA,SAAe,eAAA,CACb,QAAA,EAC0B;AAC1B,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,EAAU,MAAM,gCAAA,QAAS,EAAU,OAAO,CAAA;AAChD,IAAA,MAAM,IAAA,EAAM,cAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,CAAE,WAAA,CAAY,CAAA;AAE/C,IAAA,GAAA,CAAI,IAAA,IAAQ,OAAA,EAAS;AACnB,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,IAC3B,EAAA,KAAA,GAAA,CAAW,IAAA,IAAQ,QAAA,GAAW,IAAA,IAAQ,MAAA,EAAQ;AAE5C,MAAA,MAAM,KAAA,EAAO,MAAM,4DAAA,CAAO,MAAM,GAAA;AAChC,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,IAC3B,EAAA,KAAO;AACL,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,iCAAA,EAAoC,GAAG,CAAA,2BAAA;AAAA,MACzC,CAAA;AAAA,IACF;AAAA,EACF,EAAA,MAAA,CAAS,KAAA,EAAO;AACd,IAAA,GAAA,CAAK,KAAA,CAAgC,KAAA,IAAS,QAAA,EAAU;AACtD,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sCAAA,EAAyC,QAAQ,CAAA,CAAA;AACnE,IAAA;AACM,IAAA;AACR,EAAA;AACF;AAQU;AAEsC,EAAA;AAGQ,EAAA;AAGZ,EAAA;AACE,EAAA;AAChC,IAAA;AAC8B,MAAA;AACxC,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AD/DwE;AACA;AACA","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"]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emmett-community/emmett-expressjs-with-openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Emmett
|
|
6
|
+
"description": "Express.js utilities for Emmett applications that want OpenAPI 3.x validation without pulling the whole Emmett core",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsup",
|
|
9
9
|
"build:ts": "tsc",
|
|
@@ -54,15 +54,23 @@
|
|
|
54
54
|
"express": "^4.19.2",
|
|
55
55
|
"express-async-errors": "^3.1.1",
|
|
56
56
|
"express-openapi-validator": "^5.3.7",
|
|
57
|
+
"@my-f-startup/firebase-auth-express": "0.1.0",
|
|
57
58
|
"http-problem-details": "^0.1.5",
|
|
58
59
|
"supertest": "^7.0.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependenciesMeta": {
|
|
61
62
|
"express-openapi-validator": {
|
|
62
63
|
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@my-f-startup/firebase-auth-express": {
|
|
66
|
+
"optional": true
|
|
63
67
|
}
|
|
64
68
|
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"yaml": "^2.8.1"
|
|
71
|
+
},
|
|
65
72
|
"devDependencies": {
|
|
73
|
+
"@my-f-startup/firebase-auth-express": "0.1.0",
|
|
66
74
|
"@types/express": "^4.17.21",
|
|
67
75
|
"@types/supertest": "^6.0.2",
|
|
68
76
|
"express": "^4.21.2",
|
|
@@ -74,8 +82,9 @@
|
|
|
74
82
|
"supertest": "^7.0.0",
|
|
75
83
|
"tsup": "^8.2.4",
|
|
76
84
|
"tsx": "^4.17.0",
|
|
77
|
-
"typescript": "^5.5.4"
|
|
78
|
-
"yaml": "^2.8.1"
|
|
85
|
+
"typescript": "^5.5.4"
|
|
79
86
|
},
|
|
80
|
-
"publishConfig": {
|
|
87
|
+
"publishConfig": {
|
|
88
|
+
"access": "public"
|
|
89
|
+
}
|
|
81
90
|
}
|
|
@@ -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\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 | OpenAPIV3.Document,\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<OpenAPIV3.Document> {\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;AAiBjB,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,UAC6B;AAC7B,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":["/Users/axcosta/Git/pessoal/oss/emmett-community/emmett-expressjs-with-openapi/dist/openapi-parser-KI7BS3DC.cjs","../src/internal/openapi-parser.ts"],"names":[],"mappings":"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","file":"/Users/axcosta/Git/pessoal/oss/emmett-community/emmett-expressjs-with-openapi/dist/openapi-parser-KI7BS3DC.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\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 | OpenAPIV3.Document,\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<OpenAPIV3.Document> {\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"]}
|