adorn-api 1.0.11 → 1.0.13
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 +318 -620
- package/dist/adapter/express/auth.d.ts +5 -0
- package/dist/adapter/express/auth.d.ts.map +1 -0
- package/dist/adapter/express/bootstrap.d.ts.map +1 -1
- package/dist/adapter/express/coercion.d.ts +22 -0
- package/dist/adapter/express/coercion.d.ts.map +1 -0
- package/dist/adapter/express/index.d.ts +3 -50
- package/dist/adapter/express/index.d.ts.map +1 -1
- package/dist/adapter/express/merge.d.ts +0 -3
- package/dist/adapter/express/merge.d.ts.map +1 -1
- package/dist/adapter/express/openapi.d.ts +11 -0
- package/dist/adapter/express/openapi.d.ts.map +1 -0
- package/dist/adapter/express/router.d.ts +4 -0
- package/dist/adapter/express/router.d.ts.map +1 -0
- package/dist/adapter/express/swagger.d.ts +4 -0
- package/dist/adapter/express/swagger.d.ts.map +1 -0
- package/dist/adapter/express/types.d.ts +64 -0
- package/dist/adapter/express/types.d.ts.map +1 -0
- package/dist/adapter/express/validation.d.ts +10 -0
- package/dist/adapter/express/validation.d.ts.map +1 -0
- package/dist/cli.cjs +1003 -434
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1003 -434
- package/dist/cli.js.map +1 -1
- package/dist/compiler/analyze/scanControllers.d.ts +0 -1
- package/dist/compiler/analyze/scanControllers.d.ts.map +1 -1
- package/dist/compiler/cache/isStale.d.ts.map +1 -1
- package/dist/compiler/cache/writeCache.d.ts.map +1 -1
- package/dist/compiler/manifest/emit.d.ts.map +1 -1
- package/dist/compiler/manifest/format.d.ts +1 -1
- package/dist/compiler/manifest/format.d.ts.map +1 -1
- package/dist/compiler/schema/intersectionHandler.d.ts +7 -0
- package/dist/compiler/schema/intersectionHandler.d.ts.map +1 -0
- package/dist/compiler/schema/objectHandler.d.ts +20 -0
- package/dist/compiler/schema/objectHandler.d.ts.map +1 -0
- package/dist/compiler/schema/openapi.d.ts +1 -1
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/parameters.d.ts +18 -0
- package/dist/compiler/schema/parameters.d.ts.map +1 -0
- package/dist/compiler/schema/primitives.d.ts +10 -0
- package/dist/compiler/schema/primitives.d.ts.map +1 -0
- package/dist/compiler/schema/typeToJsonSchema.d.ts +3 -46
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +1 -1
- package/dist/compiler/schema/types.d.ts +54 -0
- package/dist/compiler/schema/types.d.ts.map +1 -0
- package/dist/compiler/schema/unionHandler.d.ts +10 -0
- package/dist/compiler/schema/unionHandler.d.ts.map +1 -0
- package/dist/decorators/index.d.ts +0 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/express.cjs +522 -502
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +522 -502
- package/dist/express.js.map +1 -1
- package/dist/http.d.ts +1 -10
- package/dist/http.d.ts.map +1 -1
- package/dist/index.cjs +3 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -34
- package/dist/index.js.map +1 -1
- package/dist/metal/applyListQuery.d.ts +27 -0
- package/dist/metal/applyListQuery.d.ts.map +1 -0
- package/dist/metal/index.cjs +61 -2
- package/dist/metal/index.cjs.map +1 -1
- package/dist/metal/index.d.ts +4 -0
- package/dist/metal/index.d.ts.map +1 -1
- package/dist/metal/index.js +57 -2
- package/dist/metal/index.js.map +1 -1
- package/dist/metal/listQuery.d.ts +7 -0
- package/dist/metal/listQuery.d.ts.map +1 -0
- package/dist/metal/queryOptions.d.ts +8 -0
- package/dist/metal/queryOptions.d.ts.map +1 -0
- package/dist/metal/registerMetalEntities.d.ts.map +1 -1
- package/dist/runtime/metadata/types.d.ts +0 -3
- package/dist/runtime/metadata/types.d.ts.map +1 -1
- package/package.json +4 -1
- package/dist/compiler/analyze/extractQueryStyle.d.ts +0 -8
- package/dist/compiler/analyze/extractQueryStyle.d.ts.map +0 -1
- package/dist/decorators/Paginated.d.ts +0 -5
- package/dist/decorators/Paginated.d.ts.map +0 -1
package/dist/express.cjs
CHANGED
|
@@ -43,10 +43,9 @@ module.exports = __toCommonJS(express_exports);
|
|
|
43
43
|
// src/runtime/polyfill.ts
|
|
44
44
|
Symbol.metadata ??= /* @__PURE__ */ Symbol("Symbol.metadata");
|
|
45
45
|
|
|
46
|
-
// src/adapter/express/
|
|
47
|
-
var
|
|
48
|
-
var
|
|
49
|
-
var import_node_path3 = require("path");
|
|
46
|
+
// src/adapter/express/router.ts
|
|
47
|
+
var import_express = require("express");
|
|
48
|
+
var import_node_path2 = require("path");
|
|
50
49
|
|
|
51
50
|
// src/runtime/metadata/key.ts
|
|
52
51
|
var ADORN_META = /* @__PURE__ */ Symbol.for("adorn-api/meta");
|
|
@@ -157,8 +156,7 @@ function computeBoundRoutes(controllers, manifest) {
|
|
|
157
156
|
responses: manifestOp.responses,
|
|
158
157
|
use: routeOp.use,
|
|
159
158
|
auth: routeOp.auth,
|
|
160
|
-
controllerUse: bucket.controllerUse
|
|
161
|
-
paginationConfig: routeOp.pagination
|
|
159
|
+
controllerUse: bucket.controllerUse
|
|
162
160
|
});
|
|
163
161
|
}
|
|
164
162
|
}
|
|
@@ -218,7 +216,7 @@ async function getMtime(filePath) {
|
|
|
218
216
|
async function loadArtifacts(options) {
|
|
219
217
|
const { outDir } = options;
|
|
220
218
|
const cacheKey = import_node_path.default.resolve(outDir);
|
|
221
|
-
|
|
219
|
+
const entry = artifactCache.get(cacheKey);
|
|
222
220
|
const openapiPath = import_node_path.default.join(outDir, "openapi.json");
|
|
223
221
|
const manifestPath = import_node_path.default.join(outDir, "manifest.json");
|
|
224
222
|
const validatorsPath = import_node_path.default.join(outDir, "validators.mjs");
|
|
@@ -267,351 +265,7 @@ async function loadArtifacts(options) {
|
|
|
267
265
|
};
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
// src/adapter/express/
|
|
271
|
-
var import_swagger_ui_express = __toESM(require("swagger-ui-express"), 1);
|
|
272
|
-
|
|
273
|
-
// src/adapter/express/bootstrap.ts
|
|
274
|
-
var import_express = __toESM(require("express"), 1);
|
|
275
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
276
|
-
function bootstrap(options) {
|
|
277
|
-
return new Promise(async (resolve2, reject) => {
|
|
278
|
-
try {
|
|
279
|
-
const {
|
|
280
|
-
controllers,
|
|
281
|
-
port: userPort,
|
|
282
|
-
host: userHost,
|
|
283
|
-
artifactsDir: userArtifactsDir = ".adorn",
|
|
284
|
-
enableSwagger = true,
|
|
285
|
-
swaggerPath = "/docs",
|
|
286
|
-
swaggerJsonPath = "/docs/openapi.json",
|
|
287
|
-
middleware,
|
|
288
|
-
auth,
|
|
289
|
-
coerce
|
|
290
|
-
} = options;
|
|
291
|
-
if (controllers.length === 0) {
|
|
292
|
-
reject(new Error("At least one controller must be provided to bootstrap()."));
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const envPort = process.env.PORT;
|
|
296
|
-
const port = userPort ?? (envPort !== void 0 ? Number(envPort) : 3e3);
|
|
297
|
-
const host = userHost ?? process.env.HOST ?? "0.0.0.0";
|
|
298
|
-
if (isNaN(port) || port < 0 || port > 65535) {
|
|
299
|
-
reject(new Error(`Invalid port: ${port}. Port must be between 0 and 65535.`));
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
const absoluteArtifactsDir = import_node_path2.default.isAbsolute(userArtifactsDir) ? userArtifactsDir : import_node_path2.default.resolve(process.cwd(), userArtifactsDir);
|
|
303
|
-
const app = (0, import_express.default)();
|
|
304
|
-
app.use(import_express.default.json());
|
|
305
|
-
const router = await createExpressRouter({
|
|
306
|
-
controllers,
|
|
307
|
-
artifactsDir: absoluteArtifactsDir,
|
|
308
|
-
middleware,
|
|
309
|
-
auth,
|
|
310
|
-
coerce
|
|
311
|
-
});
|
|
312
|
-
app.use(router);
|
|
313
|
-
if (enableSwagger) {
|
|
314
|
-
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
315
|
-
const serverUrl = `http://${displayHost}:${port}`;
|
|
316
|
-
app.use(
|
|
317
|
-
setupSwagger({
|
|
318
|
-
artifactsDir: absoluteArtifactsDir,
|
|
319
|
-
jsonPath: swaggerJsonPath,
|
|
320
|
-
uiPath: swaggerPath,
|
|
321
|
-
swaggerOptions: {
|
|
322
|
-
servers: [{ url: serverUrl }]
|
|
323
|
-
}
|
|
324
|
-
})
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
const server = app.listen(port, host, () => {
|
|
328
|
-
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
329
|
-
const serverUrl = `http://${displayHost}:${port}`;
|
|
330
|
-
console.log(`\u{1F680} Server running on ${serverUrl}`);
|
|
331
|
-
if (enableSwagger) {
|
|
332
|
-
console.log(`\u{1F4DA} Swagger UI: ${serverUrl}${swaggerPath}`);
|
|
333
|
-
}
|
|
334
|
-
const result = {
|
|
335
|
-
server,
|
|
336
|
-
app,
|
|
337
|
-
url: serverUrl,
|
|
338
|
-
port,
|
|
339
|
-
host,
|
|
340
|
-
close: () => new Promise((closeResolve) => {
|
|
341
|
-
server.close(() => {
|
|
342
|
-
console.log("Server closed gracefully");
|
|
343
|
-
closeResolve();
|
|
344
|
-
});
|
|
345
|
-
})
|
|
346
|
-
};
|
|
347
|
-
resolve2(result);
|
|
348
|
-
});
|
|
349
|
-
server.on("error", (error) => {
|
|
350
|
-
if (error.code === "EADDRINUSE") {
|
|
351
|
-
reject(new Error(`Port ${port} already in use. Please choose a different port.`));
|
|
352
|
-
} else if (error.code === "EACCES") {
|
|
353
|
-
reject(new Error(`Permission denied for port ${port}. Ports below 1024 require root privileges.`));
|
|
354
|
-
} else {
|
|
355
|
-
reject(new Error(`Failed to start server: ${error.message}`));
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
} catch (error) {
|
|
359
|
-
reject(error);
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// src/adapter/express/index.ts
|
|
365
|
-
function normalizeCoerceOptions(coerce) {
|
|
366
|
-
return {
|
|
367
|
-
body: coerce?.body ?? false,
|
|
368
|
-
query: coerce?.query ?? false,
|
|
369
|
-
path: coerce?.path ?? false,
|
|
370
|
-
header: coerce?.header ?? false,
|
|
371
|
-
cookie: coerce?.cookie ?? false,
|
|
372
|
-
dateTime: coerce?.dateTime ?? false,
|
|
373
|
-
date: coerce?.date ?? false
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
async function createExpressRouter(options) {
|
|
377
|
-
const { controllers, artifactsDir = ".adorn", middleware = {} } = options;
|
|
378
|
-
let manifest;
|
|
379
|
-
let openapi;
|
|
380
|
-
let precompiledValidators = null;
|
|
381
|
-
if (options.manifest && options.openapi) {
|
|
382
|
-
manifest = options.manifest;
|
|
383
|
-
openapi = options.openapi;
|
|
384
|
-
if (manifest.validation.mode === "precompiled" && manifest.validation.precompiledModule) {
|
|
385
|
-
try {
|
|
386
|
-
const validatorPath = (0, import_node_path3.join)(artifactsDir, manifest.validation.precompiledModule);
|
|
387
|
-
precompiledValidators = require(validatorPath).validators;
|
|
388
|
-
} catch (err) {
|
|
389
|
-
console.warn(`Failed to load precompiled validators: ${err}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
const artifacts = await loadArtifacts({ outDir: artifactsDir });
|
|
394
|
-
manifest = artifacts.manifest;
|
|
395
|
-
openapi = artifacts.openapi;
|
|
396
|
-
precompiledValidators = artifacts.validators?.validators ?? null;
|
|
397
|
-
}
|
|
398
|
-
const routes = bindRoutes({ controllers, manifest });
|
|
399
|
-
const validator = precompiledValidators ? null : createValidator();
|
|
400
|
-
const router = (0, import_express2.Router)();
|
|
401
|
-
const instanceCache = /* @__PURE__ */ new Map();
|
|
402
|
-
const coerce = normalizeCoerceOptions(options.coerce);
|
|
403
|
-
function getInstance(Ctor) {
|
|
404
|
-
if (!instanceCache.has(Ctor)) {
|
|
405
|
-
instanceCache.set(Ctor, new Ctor());
|
|
406
|
-
}
|
|
407
|
-
return instanceCache.get(Ctor);
|
|
408
|
-
}
|
|
409
|
-
function resolveMiddleware(items, named = {}) {
|
|
410
|
-
return items.map((item) => {
|
|
411
|
-
if (typeof item === "string") {
|
|
412
|
-
const fn = named[item];
|
|
413
|
-
if (!fn) {
|
|
414
|
-
throw new Error(`Named middleware "${item}" not found in middleware registry`);
|
|
415
|
-
}
|
|
416
|
-
return fn;
|
|
417
|
-
}
|
|
418
|
-
return item;
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
function createAuthMiddleware(authConfig, routeAuth, globalSecurity) {
|
|
422
|
-
return async (req, res, next) => {
|
|
423
|
-
const isPublic = routeAuth === "public";
|
|
424
|
-
const hasAuthDecorator = routeAuth && routeAuth !== "public";
|
|
425
|
-
const hasGlobalSecurity = globalSecurity && globalSecurity.length > 0;
|
|
426
|
-
if (!hasAuthDecorator && !hasGlobalSecurity) {
|
|
427
|
-
return next();
|
|
428
|
-
}
|
|
429
|
-
if (isPublic) {
|
|
430
|
-
return next();
|
|
431
|
-
}
|
|
432
|
-
const authMeta = routeAuth;
|
|
433
|
-
const scheme = authMeta.scheme;
|
|
434
|
-
const requiredScopes = authMeta.scopes || [];
|
|
435
|
-
const isOptional = authMeta.optional ?? false;
|
|
436
|
-
const authRuntime = authConfig.schemes[scheme];
|
|
437
|
-
if (!authRuntime) {
|
|
438
|
-
throw new Error(`Auth scheme "${scheme}" not found in auth configuration`);
|
|
439
|
-
}
|
|
440
|
-
const result = await authRuntime.authenticate(req);
|
|
441
|
-
if (!result) {
|
|
442
|
-
if (isOptional) {
|
|
443
|
-
req.auth = null;
|
|
444
|
-
return next();
|
|
445
|
-
}
|
|
446
|
-
return authRuntime.challenge(res);
|
|
447
|
-
}
|
|
448
|
-
req.auth = result.principal;
|
|
449
|
-
if (authRuntime.authorize && requiredScopes.length > 0) {
|
|
450
|
-
if (!authRuntime.authorize(result, requiredScopes)) {
|
|
451
|
-
res.status(403).json({ error: "Forbidden", message: "Insufficient scopes" });
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
next();
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
function getSchemaByRef(ref) {
|
|
459
|
-
if (!ref.startsWith("#/components/schemas/")) return null;
|
|
460
|
-
const schemaName = ref.replace("#/components/schemas/", "");
|
|
461
|
-
return openapi.components.schemas[schemaName] || null;
|
|
462
|
-
}
|
|
463
|
-
for (const route of routes) {
|
|
464
|
-
const method = route.httpMethod.toLowerCase();
|
|
465
|
-
const openapiOperation = getOpenApiOperation(openapi, route);
|
|
466
|
-
const paramSchemaIndex = buildParamSchemaIndex(openapiOperation);
|
|
467
|
-
const bodySchema = getRequestBodySchema(openapiOperation, route.args.body?.contentType) ?? (route.args.body ? getSchemaByRef(route.args.body.schemaRef) : null);
|
|
468
|
-
const coerceBodyDates = getDateCoercionOptions(coerce, "body");
|
|
469
|
-
const coerceQueryDates = getDateCoercionOptions(coerce, "query");
|
|
470
|
-
const coercePathDates = getDateCoercionOptions(coerce, "path");
|
|
471
|
-
const coerceHeaderDates = getDateCoercionOptions(coerce, "header");
|
|
472
|
-
const coerceCookieDates = getDateCoercionOptions(coerce, "cookie");
|
|
473
|
-
const middlewareChain = [];
|
|
474
|
-
if (middleware.global) {
|
|
475
|
-
middlewareChain.push(...resolveMiddleware(middleware.global, middleware.named || {}));
|
|
476
|
-
}
|
|
477
|
-
if (route.controllerUse) {
|
|
478
|
-
middlewareChain.push(...resolveMiddleware(route.controllerUse, middleware.named || {}));
|
|
479
|
-
}
|
|
480
|
-
if (route.use) {
|
|
481
|
-
middlewareChain.push(...resolveMiddleware(route.use, middleware.named || {}));
|
|
482
|
-
}
|
|
483
|
-
if (options.auth) {
|
|
484
|
-
const authMw = createAuthMiddleware(options.auth, route.auth, openapi.security || []);
|
|
485
|
-
middlewareChain.push(authMw);
|
|
486
|
-
}
|
|
487
|
-
router[method](route.fullPath, ...middlewareChain, async (req, res, next) => {
|
|
488
|
-
try {
|
|
489
|
-
const validationErrors = precompiledValidators ? validateRequestWithPrecompiled(route, req, precompiledValidators) : validateRequest(route, req, openapi, validator);
|
|
490
|
-
if (validationErrors) {
|
|
491
|
-
return res.status(400).json(formatValidationErrors(validationErrors));
|
|
492
|
-
}
|
|
493
|
-
const instance = getInstance(route.controllerCtor);
|
|
494
|
-
const handler = instance[route.methodName];
|
|
495
|
-
if (typeof handler !== "function") {
|
|
496
|
-
throw new Error(`Method ${route.methodName} not found on controller`);
|
|
497
|
-
}
|
|
498
|
-
const args = [];
|
|
499
|
-
if (route.args.body) {
|
|
500
|
-
const coercedBody = (coerceBodyDates.date || coerceBodyDates.dateTime) && bodySchema ? coerceDatesWithSchema(req.body, bodySchema, coerceBodyDates, openapi.components.schemas) : req.body;
|
|
501
|
-
args[route.args.body.index] = coercedBody;
|
|
502
|
-
}
|
|
503
|
-
for (const pathArg of route.args.path) {
|
|
504
|
-
const rawValue = req.params[pathArg.name];
|
|
505
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", pathArg.name) ?? (pathArg.schemaRef ? getSchemaByRef(pathArg.schemaRef) : null) ?? schemaFromType(pathArg.schemaType);
|
|
506
|
-
const coerced = coerceParamValue(rawValue, paramSchema, coercePathDates, openapi.components.schemas);
|
|
507
|
-
args[pathArg.index] = coerced;
|
|
508
|
-
}
|
|
509
|
-
if (route.args.query.length > 0) {
|
|
510
|
-
const deepObjectArgs = route.args.query.filter((q) => q.serialization?.style === "deepObject");
|
|
511
|
-
const standardArgs = route.args.query.filter((q) => q.serialization?.style !== "deepObject");
|
|
512
|
-
if (deepObjectArgs.length > 0) {
|
|
513
|
-
const rawQuery = getRawQueryString(req);
|
|
514
|
-
const names = new Set(deepObjectArgs.map((q) => q.name));
|
|
515
|
-
const parsedDeep = parseDeepObjectParams(rawQuery, names);
|
|
516
|
-
for (const q of deepObjectArgs) {
|
|
517
|
-
const rawValue = parsedDeep[q.name];
|
|
518
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
519
|
-
const baseValue = rawValue === void 0 ? {} : rawValue;
|
|
520
|
-
const coerced = coerceParamValue(baseValue, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
521
|
-
args[q.index] = coerced;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
if (standardArgs.length > 0) {
|
|
525
|
-
const firstQueryIndex = standardArgs[0].index;
|
|
526
|
-
const allSameIndex = standardArgs.every((q) => q.index === firstQueryIndex);
|
|
527
|
-
if (allSameIndex) {
|
|
528
|
-
args[firstQueryIndex] = {};
|
|
529
|
-
for (const q of standardArgs) {
|
|
530
|
-
const parsed = parseQueryValue(req.query[q.name], q);
|
|
531
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
532
|
-
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
533
|
-
args[firstQueryIndex][q.name] = coerced;
|
|
534
|
-
}
|
|
535
|
-
} else {
|
|
536
|
-
for (const q of standardArgs) {
|
|
537
|
-
const parsed = parseQueryValue(req.query[q.name], q);
|
|
538
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
539
|
-
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
540
|
-
args[q.index] = coerced;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (route.args.paginationParamIndex !== null) {
|
|
546
|
-
const pageStr = req.query.page;
|
|
547
|
-
const pageSizeStr = req.query.pageSize;
|
|
548
|
-
const defaultPageSize = route.paginationConfig?.defaultPageSize ?? 10;
|
|
549
|
-
const page = pageStr ? parseInt(pageStr, 10) : 1;
|
|
550
|
-
const pageSize = pageSizeStr ? parseInt(pageSizeStr, 10) : defaultPageSize;
|
|
551
|
-
args[route.args.paginationParamIndex] = { page, pageSize };
|
|
552
|
-
}
|
|
553
|
-
if (route.args.headers.length > 0) {
|
|
554
|
-
const firstHeaderIndex = route.args.headers[0].index;
|
|
555
|
-
const allSameIndex = route.args.headers.every((h) => h.index === firstHeaderIndex);
|
|
556
|
-
if (allSameIndex) {
|
|
557
|
-
args[firstHeaderIndex] = {};
|
|
558
|
-
for (const h of route.args.headers) {
|
|
559
|
-
const headerValue = req.headers[h.name.toLowerCase()];
|
|
560
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
561
|
-
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
562
|
-
args[firstHeaderIndex][h.name] = coerced ?? void 0;
|
|
563
|
-
}
|
|
564
|
-
} else {
|
|
565
|
-
for (const h of route.args.headers) {
|
|
566
|
-
const headerValue = req.headers[h.name.toLowerCase()];
|
|
567
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
568
|
-
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
569
|
-
args[h.index] = coerced ?? void 0;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (route.args.cookies.length > 0) {
|
|
574
|
-
const firstCookieIndex = route.args.cookies[0].index;
|
|
575
|
-
const allSameIndex = route.args.cookies.every((c) => c.index === firstCookieIndex);
|
|
576
|
-
const cookies = parseCookies(req.headers.cookie);
|
|
577
|
-
if (allSameIndex) {
|
|
578
|
-
args[firstCookieIndex] = {};
|
|
579
|
-
for (const c of route.args.cookies) {
|
|
580
|
-
const cookieValue = cookies[c.name];
|
|
581
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
|
|
582
|
-
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
583
|
-
args[firstCookieIndex][c.name] = coerced;
|
|
584
|
-
}
|
|
585
|
-
} else {
|
|
586
|
-
for (const c of route.args.cookies) {
|
|
587
|
-
const cookieValue = cookies[c.name];
|
|
588
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
|
|
589
|
-
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
590
|
-
args[c.index] = coerced;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
if (args.length === 0) {
|
|
595
|
-
args.push(req);
|
|
596
|
-
}
|
|
597
|
-
const result = await handler.apply(instance, args);
|
|
598
|
-
const primaryResponse = route.responses[0];
|
|
599
|
-
const status = primaryResponse?.status ?? 200;
|
|
600
|
-
res.status(status).json(result);
|
|
601
|
-
} catch (error) {
|
|
602
|
-
next(error);
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
return router;
|
|
607
|
-
}
|
|
608
|
-
function getDateCoercionOptions(coerce, location) {
|
|
609
|
-
const enabled = coerce[location];
|
|
610
|
-
return {
|
|
611
|
-
dateTime: enabled && coerce.dateTime,
|
|
612
|
-
date: enabled && coerce.date
|
|
613
|
-
};
|
|
614
|
-
}
|
|
268
|
+
// src/adapter/express/openapi.ts
|
|
615
269
|
function toOpenApiPath(path3) {
|
|
616
270
|
return path3.replace(/:([^/]+)/g, "{$1}");
|
|
617
271
|
}
|
|
@@ -621,7 +275,7 @@ function getOpenApiOperation(openapi, route) {
|
|
|
621
275
|
if (!pathItem) return null;
|
|
622
276
|
return pathItem[route.httpMethod.toLowerCase()] ?? null;
|
|
623
277
|
}
|
|
624
|
-
function
|
|
278
|
+
function getParamSchemaIndex(operation) {
|
|
625
279
|
const index = /* @__PURE__ */ new Map();
|
|
626
280
|
const params = operation?.parameters ?? [];
|
|
627
281
|
for (const param of params) {
|
|
@@ -648,155 +302,6 @@ function schemaFromType(schemaType) {
|
|
|
648
302
|
if (!schemaType) return null;
|
|
649
303
|
return { type: schemaType };
|
|
650
304
|
}
|
|
651
|
-
function validateRequestWithPrecompiled(route, req, validators) {
|
|
652
|
-
const errors = [];
|
|
653
|
-
const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
|
|
654
|
-
const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
|
|
655
|
-
if (route.args.body) {
|
|
656
|
-
const validator = validators[route.operationId]?.body;
|
|
657
|
-
if (validator) {
|
|
658
|
-
const valid = validator(req.body);
|
|
659
|
-
if (!valid) {
|
|
660
|
-
const v = validators[route.operationId].body;
|
|
661
|
-
for (const err of v.errors || []) {
|
|
662
|
-
errors.push({
|
|
663
|
-
path: `#/body${err.instancePath}`,
|
|
664
|
-
message: err.message || "Invalid value",
|
|
665
|
-
keyword: err.keyword,
|
|
666
|
-
params: err.params
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
for (const q of route.args.query) {
|
|
673
|
-
const value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
|
|
674
|
-
if (value === void 0) continue;
|
|
675
|
-
const schema = schemaFromType(q.schemaType) ?? {};
|
|
676
|
-
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
|
|
677
|
-
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
678
|
-
errors.push({
|
|
679
|
-
path: `#/query/${q.name}`,
|
|
680
|
-
message: `Schema validation not supported for query params in precompiled mode`,
|
|
681
|
-
keyword: "notSupported",
|
|
682
|
-
params: {}
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
for (const p of route.args.path) {
|
|
687
|
-
const value = req.params[p.name];
|
|
688
|
-
if (value === void 0) continue;
|
|
689
|
-
const schema = schemaFromType(p.schemaType) ?? {};
|
|
690
|
-
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
|
|
691
|
-
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
692
|
-
errors.push({
|
|
693
|
-
path: `#/path/${p.name}`,
|
|
694
|
-
message: `Schema validation not supported for path params in precompiled mode`,
|
|
695
|
-
keyword: "notSupported",
|
|
696
|
-
params: {}
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
return errors.length > 0 ? errors : null;
|
|
701
|
-
}
|
|
702
|
-
function validateRequest(route, req, openapi, validator) {
|
|
703
|
-
function getSchemaByRef(ref) {
|
|
704
|
-
if (!ref.startsWith("#/components/schemas/")) return null;
|
|
705
|
-
const schemaName = ref.replace("#/components/schemas/", "");
|
|
706
|
-
return openapi.components.schemas[schemaName] || null;
|
|
707
|
-
}
|
|
708
|
-
const openapiOperation = getOpenApiOperation(openapi, route);
|
|
709
|
-
const paramSchemaIndex = buildParamSchemaIndex(openapiOperation);
|
|
710
|
-
const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
|
|
711
|
-
const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
|
|
712
|
-
const errors = [];
|
|
713
|
-
if (route.args.body) {
|
|
714
|
-
const bodySchema = getSchemaByRef(route.args.body.schemaRef);
|
|
715
|
-
if (bodySchema) {
|
|
716
|
-
const validate = validator.compile(bodySchema);
|
|
717
|
-
const valid = validate(req.body);
|
|
718
|
-
if (!valid) {
|
|
719
|
-
for (const err of validate.errors || []) {
|
|
720
|
-
errors.push({
|
|
721
|
-
path: `#/body${err.instancePath}`,
|
|
722
|
-
message: err.message || "Invalid value",
|
|
723
|
-
keyword: err.keyword,
|
|
724
|
-
params: err.params
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
for (const q of route.args.query) {
|
|
731
|
-
const value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
|
|
732
|
-
const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name);
|
|
733
|
-
let schema = {};
|
|
734
|
-
if (openapiSchema) {
|
|
735
|
-
schema = resolveSchema(openapiSchema, openapi.components.schemas);
|
|
736
|
-
} else {
|
|
737
|
-
if (q.schemaType) {
|
|
738
|
-
const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
|
|
739
|
-
schema.type = type;
|
|
740
|
-
}
|
|
741
|
-
if (q.schemaRef && q.schemaRef.includes("Inline")) {
|
|
742
|
-
const inlineSchema = getSchemaByRef(q.schemaRef);
|
|
743
|
-
if (inlineSchema) {
|
|
744
|
-
Object.assign(schema, inlineSchema);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
|
|
749
|
-
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
750
|
-
const validate = validator.compile(schema);
|
|
751
|
-
const valid = validate(coerced);
|
|
752
|
-
if (!valid) {
|
|
753
|
-
for (const err of validate.errors || []) {
|
|
754
|
-
errors.push({
|
|
755
|
-
path: `#/query/${q.name}`,
|
|
756
|
-
message: err.message || "Invalid value",
|
|
757
|
-
keyword: err.keyword,
|
|
758
|
-
params: err.params
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
for (const p of route.args.path) {
|
|
765
|
-
const value = req.params[p.name];
|
|
766
|
-
const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", p.name);
|
|
767
|
-
let schema = {};
|
|
768
|
-
if (openapiSchema) {
|
|
769
|
-
schema = resolveSchema(openapiSchema, openapi.components.schemas);
|
|
770
|
-
} else {
|
|
771
|
-
if (p.schemaType) {
|
|
772
|
-
const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
|
|
773
|
-
schema.type = type;
|
|
774
|
-
}
|
|
775
|
-
if (p.schemaRef && p.schemaRef.includes("Inline")) {
|
|
776
|
-
const inlineSchema = getSchemaByRef(p.schemaRef);
|
|
777
|
-
if (inlineSchema) {
|
|
778
|
-
Object.assign(schema, inlineSchema);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
|
|
783
|
-
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
784
|
-
const validate = validator.compile(schema);
|
|
785
|
-
const valid = validate(coerced);
|
|
786
|
-
if (!valid) {
|
|
787
|
-
for (const err of validate.errors || []) {
|
|
788
|
-
errors.push({
|
|
789
|
-
path: `#/path/${p.name}`,
|
|
790
|
-
message: err.message || "Invalid value",
|
|
791
|
-
keyword: err.keyword,
|
|
792
|
-
params: err.params
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
return errors.length > 0 ? errors : null;
|
|
799
|
-
}
|
|
800
305
|
function resolveSchema(schema, components, seen = /* @__PURE__ */ new Set()) {
|
|
801
306
|
const ref = schema.$ref;
|
|
802
307
|
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
@@ -809,6 +314,31 @@ function resolveSchema(schema, components, seen = /* @__PURE__ */ new Set()) {
|
|
|
809
314
|
seen.add(name);
|
|
810
315
|
return resolveSchema(next, components, seen);
|
|
811
316
|
}
|
|
317
|
+
function getSchemaByRef(openapi, ref) {
|
|
318
|
+
if (!ref.startsWith("#/components/schemas/")) return null;
|
|
319
|
+
const schemaName = ref.replace("#/components/schemas/", "");
|
|
320
|
+
return openapi.components.schemas[schemaName] || null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/adapter/express/coercion.ts
|
|
324
|
+
function normalizeCoerceOptions(coerce) {
|
|
325
|
+
return {
|
|
326
|
+
body: coerce?.body ?? false,
|
|
327
|
+
query: coerce?.query ?? false,
|
|
328
|
+
path: coerce?.path ?? false,
|
|
329
|
+
header: coerce?.header ?? false,
|
|
330
|
+
cookie: coerce?.cookie ?? false,
|
|
331
|
+
dateTime: coerce?.dateTime ?? false,
|
|
332
|
+
date: coerce?.date ?? false
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function getDateCoercionOptions(coerce, location) {
|
|
336
|
+
const enabled = coerce[location];
|
|
337
|
+
return {
|
|
338
|
+
dateTime: enabled && coerce.dateTime,
|
|
339
|
+
date: enabled && coerce.date
|
|
340
|
+
};
|
|
341
|
+
}
|
|
812
342
|
function coerceDatesWithSchema(value, schema, dateCoercion, components) {
|
|
813
343
|
if (!schema || !dateCoercion.date && !dateCoercion.dateTime) return value;
|
|
814
344
|
return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: false });
|
|
@@ -1016,6 +546,408 @@ function parseCookies(cookieHeader) {
|
|
|
1016
546
|
}
|
|
1017
547
|
return cookies;
|
|
1018
548
|
}
|
|
549
|
+
function normalizeSort(sort) {
|
|
550
|
+
if (Array.isArray(sort)) {
|
|
551
|
+
return sort.map((s) => String(s));
|
|
552
|
+
}
|
|
553
|
+
if (typeof sort === "string") {
|
|
554
|
+
return sort.split(",").map((s) => s.trim()).filter(Boolean);
|
|
555
|
+
}
|
|
556
|
+
return [];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/adapter/express/auth.ts
|
|
560
|
+
function createAuthMiddleware(authConfig, routeAuth, globalSecurity) {
|
|
561
|
+
return async (req, res, next) => {
|
|
562
|
+
const isPublic = routeAuth === "public";
|
|
563
|
+
const hasAuthDecorator = routeAuth && routeAuth !== "public";
|
|
564
|
+
const hasGlobalSecurity = globalSecurity && globalSecurity.length > 0;
|
|
565
|
+
if (!hasAuthDecorator && !hasGlobalSecurity) {
|
|
566
|
+
return next();
|
|
567
|
+
}
|
|
568
|
+
if (isPublic) {
|
|
569
|
+
return next();
|
|
570
|
+
}
|
|
571
|
+
const authMeta = routeAuth;
|
|
572
|
+
const scheme = authMeta.scheme;
|
|
573
|
+
const requiredScopes = authMeta.scopes || [];
|
|
574
|
+
const isOptional = authMeta.optional ?? false;
|
|
575
|
+
const authRuntime = authConfig.schemes[scheme];
|
|
576
|
+
if (!authRuntime) {
|
|
577
|
+
throw new Error(`Auth scheme "${scheme}" not found in auth configuration`);
|
|
578
|
+
}
|
|
579
|
+
const result = await authRuntime.authenticate(req);
|
|
580
|
+
if (!result) {
|
|
581
|
+
if (isOptional) {
|
|
582
|
+
req.auth = null;
|
|
583
|
+
return next();
|
|
584
|
+
}
|
|
585
|
+
return authRuntime.challenge(res);
|
|
586
|
+
}
|
|
587
|
+
req.auth = result.principal;
|
|
588
|
+
if (authRuntime.authorize && requiredScopes.length > 0) {
|
|
589
|
+
if (!authRuntime.authorize(result, requiredScopes)) {
|
|
590
|
+
res.status(403).json({ error: "Forbidden", message: "Insufficient scopes" });
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
next();
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/adapter/express/validation.ts
|
|
599
|
+
function validateRequestWithPrecompiled(route, req, validators) {
|
|
600
|
+
const errors = [];
|
|
601
|
+
const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
|
|
602
|
+
const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
|
|
603
|
+
if (route.args.body) {
|
|
604
|
+
const validator = validators[route.operationId]?.body;
|
|
605
|
+
if (validator) {
|
|
606
|
+
const valid = validator(req.body);
|
|
607
|
+
if (!valid) {
|
|
608
|
+
const v = validators[route.operationId].body;
|
|
609
|
+
for (const err of v.errors || []) {
|
|
610
|
+
errors.push({
|
|
611
|
+
path: `#/body${err.instancePath}`,
|
|
612
|
+
message: err.message || "Invalid value",
|
|
613
|
+
keyword: err.keyword,
|
|
614
|
+
params: err.params
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
for (const q of route.args.query) {
|
|
621
|
+
let value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
|
|
622
|
+
if (q.content === "application/json" && typeof value === "string") {
|
|
623
|
+
try {
|
|
624
|
+
value = JSON.parse(value);
|
|
625
|
+
} catch {
|
|
626
|
+
errors.push({
|
|
627
|
+
path: `#/query/${q.name}`,
|
|
628
|
+
message: "Invalid JSON string",
|
|
629
|
+
keyword: "json",
|
|
630
|
+
params: {}
|
|
631
|
+
});
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return errors.length > 0 ? errors : null;
|
|
637
|
+
}
|
|
638
|
+
function validateRequest(route, req, openapi, validator) {
|
|
639
|
+
const openapiOperation = getOpenApiOperation(openapi, route);
|
|
640
|
+
const paramSchemaIndex = getParamSchemaIndex(openapiOperation);
|
|
641
|
+
const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
|
|
642
|
+
const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
|
|
643
|
+
const errors = [];
|
|
644
|
+
if (route.args.body) {
|
|
645
|
+
const bodySchema = getSchemaByRef(openapi, route.args.body.schemaRef);
|
|
646
|
+
if (bodySchema) {
|
|
647
|
+
const validate = validator.compile(bodySchema);
|
|
648
|
+
const valid = validate(req.body);
|
|
649
|
+
if (!valid) {
|
|
650
|
+
for (const err of validate.errors || []) {
|
|
651
|
+
errors.push({
|
|
652
|
+
path: `#/body${err.instancePath}`,
|
|
653
|
+
message: err.message || "Invalid value",
|
|
654
|
+
keyword: err.keyword,
|
|
655
|
+
params: err.params
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
for (const q of route.args.query) {
|
|
662
|
+
let value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
|
|
663
|
+
if (q.content === "application/json" && typeof value === "string") {
|
|
664
|
+
try {
|
|
665
|
+
value = JSON.parse(value);
|
|
666
|
+
} catch {
|
|
667
|
+
errors.push({
|
|
668
|
+
path: `#/query/${q.name}`,
|
|
669
|
+
message: "Invalid JSON string",
|
|
670
|
+
keyword: "json",
|
|
671
|
+
params: {}
|
|
672
|
+
});
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name);
|
|
677
|
+
let schema = {};
|
|
678
|
+
if (openapiSchema) {
|
|
679
|
+
schema = resolveSchema(openapiSchema, openapi.components.schemas);
|
|
680
|
+
} else {
|
|
681
|
+
if (q.schemaType) {
|
|
682
|
+
const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
|
|
683
|
+
schema.type = type;
|
|
684
|
+
}
|
|
685
|
+
if (q.schemaRef && q.schemaRef.includes("Inline")) {
|
|
686
|
+
const inlineSchema = getSchemaByRef(openapi, q.schemaRef);
|
|
687
|
+
if (inlineSchema) {
|
|
688
|
+
Object.assign(schema, inlineSchema);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
|
|
693
|
+
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
694
|
+
const validate = validator.compile(schema);
|
|
695
|
+
const valid = validate(coerced);
|
|
696
|
+
if (!valid) {
|
|
697
|
+
for (const err of validate.errors || []) {
|
|
698
|
+
errors.push({
|
|
699
|
+
path: `#/query/${q.name}`,
|
|
700
|
+
message: err.message || "Invalid value",
|
|
701
|
+
keyword: err.keyword,
|
|
702
|
+
params: err.params
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
for (const p of route.args.path) {
|
|
709
|
+
const value = req.params[p.name];
|
|
710
|
+
const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", p.name);
|
|
711
|
+
let schema = {};
|
|
712
|
+
if (openapiSchema) {
|
|
713
|
+
schema = resolveSchema(openapiSchema, openapi.components.schemas);
|
|
714
|
+
} else {
|
|
715
|
+
if (p.schemaType) {
|
|
716
|
+
const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
|
|
717
|
+
schema.type = type;
|
|
718
|
+
}
|
|
719
|
+
if (p.schemaRef && p.schemaRef.includes("Inline")) {
|
|
720
|
+
const inlineSchema = getSchemaByRef(openapi, p.schemaRef);
|
|
721
|
+
if (inlineSchema) {
|
|
722
|
+
Object.assign(schema, inlineSchema);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
|
|
727
|
+
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
728
|
+
const validate = validator.compile(schema);
|
|
729
|
+
const valid = validate(coerced);
|
|
730
|
+
if (!valid) {
|
|
731
|
+
for (const err of validate.errors || []) {
|
|
732
|
+
errors.push({
|
|
733
|
+
path: `#/path/${p.name}`,
|
|
734
|
+
message: err.message || "Invalid value",
|
|
735
|
+
keyword: err.keyword,
|
|
736
|
+
params: err.params
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return errors.length > 0 ? errors : null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/adapter/express/router.ts
|
|
746
|
+
async function createExpressRouter(options) {
|
|
747
|
+
const { controllers, artifactsDir = ".adorn", middleware = {}, defaultPageSize = 10 } = options;
|
|
748
|
+
let manifest;
|
|
749
|
+
let openapi;
|
|
750
|
+
let precompiledValidators = null;
|
|
751
|
+
if (options.manifest && options.openapi) {
|
|
752
|
+
manifest = options.manifest;
|
|
753
|
+
openapi = options.openapi;
|
|
754
|
+
if (manifest.validation.mode === "precompiled" && manifest.validation.precompiledModule) {
|
|
755
|
+
try {
|
|
756
|
+
const validatorPath = (0, import_node_path2.join)(artifactsDir, manifest.validation.precompiledModule);
|
|
757
|
+
precompiledValidators = require(validatorPath).validators;
|
|
758
|
+
} catch (err) {
|
|
759
|
+
console.warn(`Failed to load precompiled validators: ${err}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
const artifacts = await loadArtifacts({ outDir: artifactsDir });
|
|
764
|
+
manifest = artifacts.manifest;
|
|
765
|
+
openapi = artifacts.openapi;
|
|
766
|
+
precompiledValidators = artifacts.validators?.validators ?? null;
|
|
767
|
+
}
|
|
768
|
+
const routes = bindRoutes({ controllers, manifest });
|
|
769
|
+
const validator = precompiledValidators ? null : createValidator();
|
|
770
|
+
const router = (0, import_express.Router)();
|
|
771
|
+
const instanceCache = /* @__PURE__ */ new Map();
|
|
772
|
+
const coerce = normalizeCoerceOptions(options.coerce);
|
|
773
|
+
function getInstance(Ctor) {
|
|
774
|
+
if (!instanceCache.has(Ctor)) {
|
|
775
|
+
instanceCache.set(Ctor, new Ctor());
|
|
776
|
+
}
|
|
777
|
+
return instanceCache.get(Ctor);
|
|
778
|
+
}
|
|
779
|
+
function resolveMiddleware(items, named = {}) {
|
|
780
|
+
return items.map((item) => {
|
|
781
|
+
if (typeof item === "string") {
|
|
782
|
+
const fn = named[item];
|
|
783
|
+
if (!fn) {
|
|
784
|
+
throw new Error(`Named middleware "${item}" not found in middleware registry`);
|
|
785
|
+
}
|
|
786
|
+
return fn;
|
|
787
|
+
}
|
|
788
|
+
return item;
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
for (const route of routes) {
|
|
792
|
+
const method = route.httpMethod.toLowerCase();
|
|
793
|
+
const openapiOperation = getOpenApiOperation(openapi, route);
|
|
794
|
+
const paramSchemaIndex = getParamSchemaIndex(openapiOperation);
|
|
795
|
+
const bodySchema = getRequestBodySchema(openapiOperation, route.args.body?.contentType) ?? (route.args.body ? getSchemaByRef(openapi, route.args.body.schemaRef) : null);
|
|
796
|
+
const coerceBodyDates = getDateCoercionOptions(coerce, "body");
|
|
797
|
+
const coerceQueryDates = getDateCoercionOptions(coerce, "query");
|
|
798
|
+
const coercePathDates = getDateCoercionOptions(coerce, "path");
|
|
799
|
+
const coerceHeaderDates = getDateCoercionOptions(coerce, "header");
|
|
800
|
+
const coerceCookieDates = getDateCoercionOptions(coerce, "cookie");
|
|
801
|
+
const middlewareChain = [];
|
|
802
|
+
if (middleware.global) {
|
|
803
|
+
middlewareChain.push(...resolveMiddleware(middleware.global, middleware.named || {}));
|
|
804
|
+
}
|
|
805
|
+
if (route.controllerUse) {
|
|
806
|
+
middlewareChain.push(...resolveMiddleware(route.controllerUse, middleware.named || {}));
|
|
807
|
+
}
|
|
808
|
+
if (route.use) {
|
|
809
|
+
middlewareChain.push(...resolveMiddleware(route.use, middleware.named || {}));
|
|
810
|
+
}
|
|
811
|
+
if (options.auth) {
|
|
812
|
+
const authMw = createAuthMiddleware(options.auth, route.auth, openapi.security || []);
|
|
813
|
+
middlewareChain.push(authMw);
|
|
814
|
+
}
|
|
815
|
+
router[method](route.fullPath, ...middlewareChain, async (req, res, next) => {
|
|
816
|
+
try {
|
|
817
|
+
const validationErrors = precompiledValidators ? validateRequestWithPrecompiled(route, req, precompiledValidators) : validateRequest(route, req, openapi, validator);
|
|
818
|
+
if (validationErrors) {
|
|
819
|
+
return res.status(400).json(formatValidationErrors(validationErrors));
|
|
820
|
+
}
|
|
821
|
+
const instance = getInstance(route.controllerCtor);
|
|
822
|
+
const handler = instance[route.methodName];
|
|
823
|
+
if (typeof handler !== "function") {
|
|
824
|
+
throw new Error(`Method ${route.methodName} not found on controller`);
|
|
825
|
+
}
|
|
826
|
+
const args = [];
|
|
827
|
+
if (route.args.body) {
|
|
828
|
+
const coercedBody = (coerceBodyDates.date || coerceBodyDates.dateTime) && bodySchema ? coerceDatesWithSchema(req.body, bodySchema, coerceBodyDates, openapi.components.schemas) : req.body;
|
|
829
|
+
args[route.args.body.index] = coercedBody;
|
|
830
|
+
}
|
|
831
|
+
for (const pathArg of route.args.path) {
|
|
832
|
+
const rawValue = req.params[pathArg.name];
|
|
833
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", pathArg.name) ?? (pathArg.schemaRef ? getSchemaByRef(openapi, pathArg.schemaRef) : null) ?? schemaFromType(pathArg.schemaType);
|
|
834
|
+
const coerced = coerceParamValue(rawValue, paramSchema, coercePathDates, openapi.components.schemas);
|
|
835
|
+
args[pathArg.index] = coerced;
|
|
836
|
+
}
|
|
837
|
+
if (route.args.query.length > 0) {
|
|
838
|
+
const jsonArgs = route.args.query.filter((q) => q.content === "application/json");
|
|
839
|
+
const standardArgs = route.args.query.filter((q) => q.content !== "application/json");
|
|
840
|
+
const queryArgIndex = standardArgs[0]?.index;
|
|
841
|
+
if (queryArgIndex !== void 0) {
|
|
842
|
+
args[queryArgIndex] = {};
|
|
843
|
+
}
|
|
844
|
+
if (jsonArgs.length > 0) {
|
|
845
|
+
for (const q of jsonArgs) {
|
|
846
|
+
const rawValue = req.query[q.name];
|
|
847
|
+
if (rawValue === void 0 || rawValue === null) continue;
|
|
848
|
+
let parsed = rawValue;
|
|
849
|
+
if (typeof rawValue === "string" && rawValue.length > 0) {
|
|
850
|
+
try {
|
|
851
|
+
parsed = JSON.parse(rawValue);
|
|
852
|
+
} catch {
|
|
853
|
+
parsed = rawValue;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(openapi, q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
857
|
+
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
858
|
+
if (!args[q.index] || typeof args[q.index] !== "object") {
|
|
859
|
+
args[q.index] = {};
|
|
860
|
+
}
|
|
861
|
+
Object.assign(args[q.index], coerced);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (standardArgs.length > 0) {
|
|
865
|
+
for (const q of standardArgs) {
|
|
866
|
+
const rawValue = req.query[q.name];
|
|
867
|
+
if (rawValue === void 0) continue;
|
|
868
|
+
const parsed = parseQueryValue(rawValue, q);
|
|
869
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(openapi, q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
870
|
+
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
871
|
+
if (!args[q.index] || typeof args[q.index] !== "object") {
|
|
872
|
+
args[q.index] = {};
|
|
873
|
+
}
|
|
874
|
+
args[q.index][q.name] = coerced;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (queryArgIndex !== void 0 && args[queryArgIndex]) {
|
|
878
|
+
const queryObj = args[queryArgIndex];
|
|
879
|
+
if (queryObj.page === void 0) {
|
|
880
|
+
queryObj.page = 1;
|
|
881
|
+
}
|
|
882
|
+
if (queryObj.pageSize === void 0) {
|
|
883
|
+
queryObj.pageSize = defaultPageSize;
|
|
884
|
+
}
|
|
885
|
+
if (queryObj.sort) {
|
|
886
|
+
queryObj.sort = normalizeSort(queryObj.sort);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (route.args.headers.length > 0) {
|
|
891
|
+
const firstHeaderIndex = route.args.headers[0].index;
|
|
892
|
+
const allSameIndex = route.args.headers.every((h) => h.index === firstHeaderIndex);
|
|
893
|
+
if (allSameIndex) {
|
|
894
|
+
args[firstHeaderIndex] = {};
|
|
895
|
+
for (const h of route.args.headers) {
|
|
896
|
+
const headerValue = req.headers[h.name.toLowerCase()];
|
|
897
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(openapi, h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
898
|
+
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
899
|
+
args[firstHeaderIndex][h.name] = coerced ?? void 0;
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
for (const h of route.args.headers) {
|
|
903
|
+
const headerValue = req.headers[h.name.toLowerCase()];
|
|
904
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(openapi, h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
905
|
+
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
906
|
+
args[h.index] = coerced ?? void 0;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (route.args.cookies.length > 0) {
|
|
911
|
+
const firstCookieIndex = route.args.cookies[0].index;
|
|
912
|
+
const allSameIndex = route.args.cookies.every((c) => c.index === firstCookieIndex);
|
|
913
|
+
const cookies = parseCookies(req.headers.cookie);
|
|
914
|
+
if (allSameIndex) {
|
|
915
|
+
args[firstCookieIndex] = {};
|
|
916
|
+
for (const c of route.args.cookies) {
|
|
917
|
+
const cookieValue = cookies[c.name];
|
|
918
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(openapi, c.name) : null) ?? schemaFromType(c.schemaType);
|
|
919
|
+
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
920
|
+
args[firstCookieIndex][c.name] = coerced;
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
for (const c of route.args.cookies) {
|
|
924
|
+
const cookieValue = cookies[c.name];
|
|
925
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(openapi, c.name) : null) ?? schemaFromType(c.schemaType);
|
|
926
|
+
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
927
|
+
args[c.index] = coerced;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (args.length === 0) {
|
|
932
|
+
args.push(req);
|
|
933
|
+
}
|
|
934
|
+
const result = await handler.apply(instance, args);
|
|
935
|
+
const primaryResponse = route.responses[0];
|
|
936
|
+
const status = primaryResponse?.status ?? 200;
|
|
937
|
+
res.status(status).json(result);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
next(error);
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return router;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/adapter/express/swagger.ts
|
|
947
|
+
var import_express2 = require("express");
|
|
948
|
+
var import_node_fs2 = require("fs");
|
|
949
|
+
var import_node_path3 = require("path");
|
|
950
|
+
var import_swagger_ui_express = __toESM(require("swagger-ui-express"), 1);
|
|
1019
951
|
function setupSwagger(options = {}) {
|
|
1020
952
|
const {
|
|
1021
953
|
artifactsDir = ".adorn",
|
|
@@ -1038,6 +970,94 @@ function setupSwagger(options = {}) {
|
|
|
1038
970
|
}));
|
|
1039
971
|
return router;
|
|
1040
972
|
}
|
|
973
|
+
|
|
974
|
+
// src/adapter/express/bootstrap.ts
|
|
975
|
+
var import_express3 = __toESM(require("express"), 1);
|
|
976
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
977
|
+
function bootstrap(options) {
|
|
978
|
+
return new Promise((resolve2, reject) => {
|
|
979
|
+
const {
|
|
980
|
+
controllers,
|
|
981
|
+
port: userPort,
|
|
982
|
+
host: userHost,
|
|
983
|
+
artifactsDir: userArtifactsDir = ".adorn",
|
|
984
|
+
enableSwagger = true,
|
|
985
|
+
swaggerPath = "/docs",
|
|
986
|
+
swaggerJsonPath = "/docs/openapi.json",
|
|
987
|
+
middleware,
|
|
988
|
+
auth,
|
|
989
|
+
coerce
|
|
990
|
+
} = options;
|
|
991
|
+
if (controllers.length === 0) {
|
|
992
|
+
reject(new Error("At least one controller must be provided to bootstrap()."));
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const envPort = process.env.PORT;
|
|
996
|
+
const port = userPort ?? (envPort !== void 0 ? Number(envPort) : 3e3);
|
|
997
|
+
const host = userHost ?? process.env.HOST ?? "0.0.0.0";
|
|
998
|
+
if (isNaN(port) || port < 0 || port > 65535) {
|
|
999
|
+
reject(new Error(`Invalid port: ${port}. Port must be between 0 and 65535.`));
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const absoluteArtifactsDir = import_node_path4.default.isAbsolute(userArtifactsDir) ? userArtifactsDir : import_node_path4.default.resolve(process.cwd(), userArtifactsDir);
|
|
1003
|
+
const app = (0, import_express3.default)();
|
|
1004
|
+
app.use(import_express3.default.json());
|
|
1005
|
+
createExpressRouter({
|
|
1006
|
+
controllers,
|
|
1007
|
+
artifactsDir: absoluteArtifactsDir,
|
|
1008
|
+
middleware,
|
|
1009
|
+
auth,
|
|
1010
|
+
coerce
|
|
1011
|
+
}).then((router) => {
|
|
1012
|
+
app.use(router);
|
|
1013
|
+
if (enableSwagger) {
|
|
1014
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
1015
|
+
const serverUrl = `http://${displayHost}:${port}`;
|
|
1016
|
+
app.use(
|
|
1017
|
+
setupSwagger({
|
|
1018
|
+
artifactsDir: absoluteArtifactsDir,
|
|
1019
|
+
jsonPath: swaggerJsonPath,
|
|
1020
|
+
uiPath: swaggerPath,
|
|
1021
|
+
swaggerOptions: {
|
|
1022
|
+
servers: [{ url: serverUrl }]
|
|
1023
|
+
}
|
|
1024
|
+
})
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
const server = app.listen(port, host, () => {
|
|
1028
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
1029
|
+
const serverUrl = `http://${displayHost}:${port}`;
|
|
1030
|
+
console.log(`\u{1F680} Server running on ${serverUrl}`);
|
|
1031
|
+
if (enableSwagger) {
|
|
1032
|
+
console.log(`\u{1F4DA} Swagger UI: ${serverUrl}${swaggerPath}`);
|
|
1033
|
+
}
|
|
1034
|
+
const result = {
|
|
1035
|
+
server,
|
|
1036
|
+
app,
|
|
1037
|
+
url: serverUrl,
|
|
1038
|
+
port,
|
|
1039
|
+
host,
|
|
1040
|
+
close: () => new Promise((closeResolve) => {
|
|
1041
|
+
server.close(() => {
|
|
1042
|
+
console.log("Server closed gracefully");
|
|
1043
|
+
closeResolve();
|
|
1044
|
+
});
|
|
1045
|
+
})
|
|
1046
|
+
};
|
|
1047
|
+
resolve2(result);
|
|
1048
|
+
});
|
|
1049
|
+
server.on("error", (error) => {
|
|
1050
|
+
if (error.code === "EADDRINUSE") {
|
|
1051
|
+
reject(new Error(`Port ${port} already in use. Please choose a different port.`));
|
|
1052
|
+
} else if (error.code === "EACCES") {
|
|
1053
|
+
reject(new Error(`Permission denied for port ${port}. Ports below 1024 require root privileges.`));
|
|
1054
|
+
} else {
|
|
1055
|
+
reject(new Error(`Failed to start server: ${error.message}`));
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}).catch(reject);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1041
1061
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1042
1062
|
0 && (module.exports = {
|
|
1043
1063
|
ValidationErrorResponse,
|