adorn-api 1.0.10 → 1.0.12

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