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