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/cli.js CHANGED
@@ -86,7 +86,7 @@ function analyzeClass(node, sourceFile, checker) {
86
86
  produces
87
87
  };
88
88
  }
89
- function extractClassConsumes(node, checker) {
89
+ function extractClassConsumes(node, _checker) {
90
90
  const decorator = findDecorator(node, "Consumes");
91
91
  if (!decorator) return void 0;
92
92
  const callExpr = decorator.expression;
@@ -102,7 +102,7 @@ function extractClassConsumes(node, checker) {
102
102
  }
103
103
  return void 0;
104
104
  }
105
- function extractClassProduces(node, checker) {
105
+ function extractClassProduces(node, _checker) {
106
106
  const decorator = findDecorator(node, "Produces");
107
107
  if (!decorator) return void 0;
108
108
  const callExpr = decorator.expression;
@@ -154,7 +154,7 @@ function analyzeMethod(node, className, checker) {
154
154
  }
155
155
  const pathParamNames = extractPathParams(path4);
156
156
  const pathParamIndices = matchPathParamsToIndices(pathParamNames, parameters);
157
- const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex, paginationParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
157
+ const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
158
158
  return {
159
159
  methodName,
160
160
  httpMethod,
@@ -170,7 +170,6 @@ function analyzeMethod(node, className, checker) {
170
170
  queryObjectParamIndex,
171
171
  headerObjectParamIndex,
172
172
  cookieObjectParamIndex,
173
- paginationParamIndex,
174
173
  bodyContentType
175
174
  };
176
175
  }
@@ -196,7 +195,6 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
196
195
  let queryObjectParamIndex = null;
197
196
  let headerObjectParamIndex = null;
198
197
  let cookieObjectParamIndex = null;
199
- let paginationParamIndex = null;
200
198
  const isBodyMethod = ["POST", "PUT", "PATCH"].includes(httpMethod);
201
199
  for (let i = 0; i < parameters.length; i++) {
202
200
  const param = parameters[i];
@@ -223,11 +221,6 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
223
221
  usedIndices.add(i);
224
222
  continue;
225
223
  }
226
- if (typeStr === "PaginationParams") {
227
- paginationParamIndex = i;
228
- usedIndices.add(i);
229
- continue;
230
- }
231
224
  if (isBodyMethod && bodyParamIndex === null) {
232
225
  bodyParamIndex = i;
233
226
  usedIndices.add(i);
@@ -248,19 +241,19 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
248
241
  queryObjectParamIndex,
249
242
  headerObjectParamIndex,
250
243
  cookieObjectParamIndex,
251
- paginationParamIndex,
252
244
  bodyContentType: void 0
253
245
  };
254
246
  }
255
247
  function isObjectType(type, checker) {
256
248
  const objectFlags = (type.flags & ts2.TypeFlags.Object) !== 0;
257
- if (!objectFlags) return false;
249
+ const intersectionFlags = (type.flags & ts2.TypeFlags.Intersection) !== 0;
250
+ if (!objectFlags && !intersectionFlags) return false;
258
251
  const symbol = type.getSymbol();
259
252
  if (symbol?.getName() === "__object") return true;
260
253
  const properties = checker.getPropertiesOfType(type);
261
254
  if (properties.length > 0) return true;
262
- const callSignatures = type.getCallSignatures();
263
- if (callSignatures && callSignatures.length > 0) return false;
255
+ const callSigs = type.getCallSignatures?.();
256
+ if (callSigs && callSigs.length > 0) return false;
264
257
  return true;
265
258
  }
266
259
  function getTypeName(type) {
@@ -291,7 +284,7 @@ function extractDecoratorStringArg(decorator) {
291
284
  }
292
285
  return null;
293
286
  }
294
- function unwrapPromise(type, checker) {
287
+ function unwrapPromise(type, _checker) {
295
288
  const symbol = type.getSymbol();
296
289
  if (symbol?.getName() === "Promise") {
297
290
  const typeArgs = type.typeArguments;
@@ -312,12 +305,15 @@ function unwrapPromiseTypeNode(typeNode) {
312
305
  }
313
306
 
314
307
  // src/compiler/schema/openapi.ts
315
- import ts6 from "typescript";
308
+ import ts9 from "typescript";
316
309
 
317
310
  // src/compiler/schema/typeToJsonSchema.ts
311
+ import ts7 from "typescript";
312
+
313
+ // src/compiler/schema/primitives.ts
318
314
  import ts3 from "typescript";
319
- function typeToJsonSchema(type, ctx, typeNode) {
320
- const { checker } = ctx;
315
+ function handlePrimitiveType(type, ctx, typeNode) {
316
+ const { checker, propertyName } = ctx;
321
317
  if (type.flags & ts3.TypeFlags.Undefined) {
322
318
  return {};
323
319
  }
@@ -331,7 +327,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
331
327
  return { type: "string" };
332
328
  }
333
329
  if (type.flags & ts3.TypeFlags.Number) {
334
- return { type: "number" };
330
+ return normalizeNumericType(type, checker, typeNode, propertyName);
335
331
  }
336
332
  if (type.flags & ts3.TypeFlags.Boolean) {
337
333
  return { type: "boolean" };
@@ -355,26 +351,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
355
351
  const intrinsic = type.intrinsicName;
356
352
  return { type: "boolean", enum: [intrinsic === "true"] };
357
353
  }
358
- if (type.isUnion()) {
359
- return handleUnion(type, ctx, typeNode);
360
- }
361
- if (type.isIntersection()) {
362
- return handleIntersection(type, ctx, typeNode);
363
- }
364
- if (checker.isArrayType(type)) {
365
- const typeArgs = type.typeArguments;
366
- const itemType = typeArgs?.[0];
367
- const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
368
- return {
369
- type: "array",
370
- items,
371
- uniqueItems: isSetType(type, checker) ? true : void 0
372
- };
373
- }
374
- if (type.flags & ts3.TypeFlags.Object) {
375
- return handleObjectType(type, ctx, typeNode);
376
- }
377
- return {};
354
+ return null;
378
355
  }
379
356
  function isDateType(type, checker) {
380
357
  const symbol = type.getSymbol();
@@ -389,53 +366,51 @@ function isDateType(type, checker) {
389
366
  }
390
367
  return symbol?.getName() === "Date";
391
368
  }
392
- function isSetType(type, checker) {
393
- const symbol = type.getSymbol();
394
- if (!symbol) return false;
395
- const name = symbol.getName();
396
- if (name === "Set") return true;
397
- return false;
398
- }
399
- function getSchemaName(type, typeNode) {
400
- const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
401
- const aliasName = aliasSymbol?.getName();
402
- if (aliasName && aliasName !== "__type") {
403
- return aliasName;
369
+ function normalizeNumericType(type, checker, typeNode, propertyName) {
370
+ const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
371
+ const symbol = getEffectiveSymbol(type, checker);
372
+ const symbolName = symbol?.getName() ?? null;
373
+ if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName) || shouldBeIntegerType(propertyName ?? null)) {
374
+ return { type: "integer" };
404
375
  }
405
- const symbol = type.getSymbol();
406
- const symbolName = symbol?.getName?.();
407
- if (symbolName && symbolName !== "__type") {
408
- return symbolName;
376
+ return { type: "number" };
377
+ }
378
+ function shouldBeIntegerType(typeName) {
379
+ if (!typeName) return false;
380
+ const lower = typeName.toLowerCase();
381
+ return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk" || lower === "page" || lower === "pagesize" || lower === "totalitems" || lower === "limit" || lower === "offset";
382
+ }
383
+ function getExplicitTypeNameFromNode(typeNode) {
384
+ if (!typeNode) return null;
385
+ if (ts3.isTypeReferenceNode(typeNode)) {
386
+ if (ts3.isIdentifier(typeNode.typeName)) {
387
+ return typeNode.typeName.text;
388
+ }
409
389
  }
410
- const nodeName = getExplicitTypeNameFromNode(typeNode);
411
- if (nodeName && nodeName !== "__type") {
412
- return nodeName;
390
+ if (ts3.isTypeAliasDeclaration(typeNode.parent)) {
391
+ if (ts3.isIdentifier(typeNode.parent.name)) {
392
+ return typeNode.parent.name.text;
393
+ }
413
394
  }
414
395
  return null;
415
396
  }
416
- function buildNamedSchema(type, ctx, typeNode, build) {
417
- const name = getSchemaName(type, typeNode);
418
- if (!name) {
419
- return build();
420
- }
421
- const { components, typeStack } = ctx;
422
- if (components.has(name) || typeStack.has(type)) {
423
- return { $ref: `#/components/schemas/${name}` };
424
- }
425
- typeStack.add(type);
426
- const schema = build();
427
- typeStack.delete(type);
428
- if (!components.has(name)) {
429
- components.set(name, schema);
397
+ function getEffectiveSymbol(type, checker) {
398
+ const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
399
+ if (aliasSymbol && aliasSymbol.flags & ts3.SymbolFlags.Alias) {
400
+ return checker.getAliasedSymbol(aliasSymbol);
430
401
  }
431
- return { $ref: `#/components/schemas/${name}` };
402
+ return type.getSymbol() ?? null;
432
403
  }
404
+
405
+ // src/compiler/schema/unionHandler.ts
406
+ import ts4 from "typescript";
433
407
  function handleUnion(type, ctx, typeNode) {
434
408
  return buildNamedSchema(type, ctx, typeNode, () => {
435
409
  const types = type.types;
436
- const nullType = types.find((t) => t.flags & ts3.TypeFlags.Null);
437
- const otherTypes = types.filter((t) => !(t.flags & ts3.TypeFlags.Null) && !(t.flags & ts3.TypeFlags.Undefined));
438
- const allStringLiterals = otherTypes.every((t) => t.flags & ts3.TypeFlags.StringLiteral);
410
+ const nullType = types.find((t) => t.flags & ts4.TypeFlags.Null);
411
+ const undefinedType = types.find((t) => t.flags & ts4.TypeFlags.Undefined);
412
+ const otherTypes = types.filter((t) => !(t.flags & ts4.TypeFlags.Null) && !(t.flags & ts4.TypeFlags.Undefined));
413
+ const allStringLiterals = otherTypes.every((t) => t.flags & ts4.TypeFlags.StringLiteral);
439
414
  if (allStringLiterals && otherTypes.length > 0) {
440
415
  const enumValues = otherTypes.map((t) => t.value);
441
416
  const schema = { type: "string", enum: enumValues };
@@ -444,6 +419,14 @@ function handleUnion(type, ctx, typeNode) {
444
419
  }
445
420
  return schema;
446
421
  }
422
+ const allBooleanLiterals = otherTypes.length > 0 && otherTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
423
+ if (allBooleanLiterals) {
424
+ const schema = { type: "boolean" };
425
+ if (nullType || undefinedType) {
426
+ schema.type = ["boolean", "null"];
427
+ }
428
+ return schema;
429
+ }
447
430
  if (otherTypes.length === 1 && nullType) {
448
431
  const innerSchema = typeToJsonSchema(otherTypes[0], ctx);
449
432
  if (typeof innerSchema.type === "string") {
@@ -473,49 +456,7 @@ function handleUnion(type, ctx, typeNode) {
473
456
  return {};
474
457
  });
475
458
  }
476
- function handleIntersection(type, ctx, typeNode) {
477
- return buildNamedSchema(type, ctx, typeNode, () => {
478
- const types = type.types;
479
- const brandCollapsed = tryCollapseBrandedIntersection(types, ctx, typeNode);
480
- if (brandCollapsed) {
481
- return brandCollapsed;
482
- }
483
- const allOf = [];
484
- for (const t of types) {
485
- allOf.push(typeToJsonSchema(t, ctx));
486
- }
487
- return { allOf };
488
- });
489
- }
490
- function tryCollapseBrandedIntersection(types, ctx, typeNode) {
491
- const { checker } = ctx;
492
- const parts = [...types];
493
- const prim = parts.find(isPrimitiveLike);
494
- if (!prim) return null;
495
- const rest = parts.filter((p) => p !== prim);
496
- if (rest.every((r) => isBrandObject(checker, r, ctx))) {
497
- return typeToJsonSchema(prim, ctx);
498
- }
499
- return null;
500
- }
501
- function isPrimitiveLike(t) {
502
- return (t.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.Boolean | ts3.TypeFlags.BigInt)) !== 0 || (t.flags & ts3.TypeFlags.StringLiteral) !== 0 || (t.flags & ts3.TypeFlags.NumberLiteral) !== 0;
503
- }
504
- function isBrandObject(checker, t, ctx) {
505
- if (!(t.flags & ts3.TypeFlags.Object)) return false;
506
- const props = t.getProperties();
507
- if (props.length === 0) return false;
508
- const allowed = /* @__PURE__ */ new Set(["__brand", "__type", "__tag", "brand"]);
509
- for (const p of props) {
510
- if (!allowed.has(p.getName())) return false;
511
- }
512
- const callSigs = t.getCallSignatures?.();
513
- if (callSigs && callSigs.length > 0) return false;
514
- const constructSigs = t.getConstructSignatures?.();
515
- if (constructSigs && constructSigs.length > 0) return false;
516
- return true;
517
- }
518
- function detectDiscriminatedUnion(types, ctx, branches) {
459
+ function detectDiscriminatedUnion(types, ctx, _branches) {
519
460
  if (types.length < 2) return null;
520
461
  const candidates = findCommonPropertyNames(ctx.checker, types);
521
462
  for (const propName of candidates) {
@@ -546,10 +487,10 @@ function findCommonPropertyNames(checker, types) {
546
487
  function isRequiredProperty(checker, type, propName) {
547
488
  const sym = checker.getPropertyOfType(type, propName);
548
489
  if (!sym) return false;
549
- if (sym.flags & ts3.SymbolFlags.Optional) return false;
490
+ if (sym.flags & ts4.SymbolFlags.Optional) return false;
550
491
  const propType = checker.getTypeOfSymbol(sym);
551
492
  if (propType.isUnion?.()) {
552
- const hasUndefined = propType.types.some((t) => (t.flags & ts3.TypeFlags.Undefined) !== 0);
493
+ const hasUndefined = propType.types.some((t) => (t.flags & ts4.TypeFlags.Undefined) !== 0);
553
494
  if (hasUndefined) return false;
554
495
  }
555
496
  return true;
@@ -592,11 +533,187 @@ function getBranchSchemaName(type, ctx) {
592
533
  }
593
534
  return `Anonymous_${ctx.typeNameStack.length}`;
594
535
  }
536
+ function buildNamedSchema(type, ctx, typeNode, build) {
537
+ const name = getSchemaName(type, typeNode);
538
+ if (!name) {
539
+ return build();
540
+ }
541
+ const { components, typeStack } = ctx;
542
+ if (components.has(name) || typeStack.has(type)) {
543
+ return { $ref: `#/components/schemas/${name}` };
544
+ }
545
+ typeStack.add(type);
546
+ const schema = build();
547
+ typeStack.delete(type);
548
+ if (!components.has(name)) {
549
+ components.set(name, schema);
550
+ }
551
+ return { $ref: `#/components/schemas/${name}` };
552
+ }
553
+ function getSchemaName(type, typeNode) {
554
+ const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
555
+ const aliasName = aliasSymbol?.getName();
556
+ if (aliasName && aliasName !== "__type") {
557
+ return aliasName;
558
+ }
559
+ const symbol = type.getSymbol();
560
+ const symbolName = symbol?.getName?.();
561
+ if (symbolName && symbolName !== "__type") {
562
+ return symbolName;
563
+ }
564
+ const nodeName = getExplicitTypeNameFromNode2(typeNode);
565
+ if (nodeName && nodeName !== "__type") {
566
+ return nodeName;
567
+ }
568
+ return null;
569
+ }
570
+ function getExplicitTypeNameFromNode2(typeNode) {
571
+ if (!typeNode) return null;
572
+ if (ts4.isTypeReferenceNode(typeNode)) {
573
+ if (ts4.isIdentifier(typeNode.typeName)) {
574
+ return typeNode.typeName.text;
575
+ }
576
+ }
577
+ if (ts4.isTypeAliasDeclaration(typeNode.parent)) {
578
+ if (ts4.isIdentifier(typeNode.parent.name)) {
579
+ return typeNode.parent.name.text;
580
+ }
581
+ }
582
+ return null;
583
+ }
584
+
585
+ // src/compiler/schema/intersectionHandler.ts
586
+ import ts5 from "typescript";
587
+ function handleIntersection(type, ctx, typeNode) {
588
+ return buildNamedSchema2(type, ctx, typeNode, () => {
589
+ const types = type.types;
590
+ const brandCollapsed = tryCollapseBrandedIntersection(types, ctx, typeNode);
591
+ if (brandCollapsed) {
592
+ return brandCollapsed;
593
+ }
594
+ const allOf = [];
595
+ for (const t of types) {
596
+ const schema = typeToJsonSchema(t, ctx);
597
+ if (Object.keys(schema).length > 0) {
598
+ if (isEmptyObjectSchema(schema)) {
599
+ continue;
600
+ }
601
+ allOf.push(schema);
602
+ }
603
+ }
604
+ if (allOf.length === 0) {
605
+ return {};
606
+ }
607
+ if (allOf.length === 1) {
608
+ return allOf[0];
609
+ }
610
+ return { allOf };
611
+ });
612
+ }
613
+ function tryCollapseBrandedIntersection(types, ctx, _typeNode) {
614
+ const { checker } = ctx;
615
+ const parts = [...types];
616
+ const prim = parts.find(isPrimitiveLike);
617
+ if (!prim) return null;
618
+ const rest = parts.filter((p) => p !== prim);
619
+ if (rest.every((r) => isBrandObject(checker, r, ctx))) {
620
+ return typeToJsonSchema(prim, ctx);
621
+ }
622
+ return null;
623
+ }
624
+ function isPrimitiveLike(t) {
625
+ return (t.flags & (ts5.TypeFlags.String | ts5.TypeFlags.Number | ts5.TypeFlags.Boolean | ts5.TypeFlags.BigInt)) !== 0 || (t.flags & ts5.TypeFlags.StringLiteral) !== 0 || (t.flags & ts5.TypeFlags.NumberLiteral) !== 0;
626
+ }
627
+ function isBrandObject(checker, t, _ctx) {
628
+ if (!(t.flags & ts5.TypeFlags.Object)) return false;
629
+ const props = t.getProperties();
630
+ if (props.length === 0) return false;
631
+ const allowed = /* @__PURE__ */ new Set(["__brand", "__type", "__tag", "brand"]);
632
+ for (const p of props) {
633
+ if (!allowed.has(p.getName())) return false;
634
+ }
635
+ const callSigs = t.getCallSignatures?.();
636
+ if (callSigs && callSigs.length > 0) return false;
637
+ const constructSigs = t.getConstructSignatures?.();
638
+ if (constructSigs && constructSigs.length > 0) return false;
639
+ return true;
640
+ }
641
+ function isEmptyObjectSchema(schema) {
642
+ if (schema.type !== "object") {
643
+ return false;
644
+ }
645
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
646
+ if (!schema.additionalProperties) {
647
+ return true;
648
+ }
649
+ }
650
+ return false;
651
+ }
652
+ function buildNamedSchema2(type, ctx, typeNode, build) {
653
+ const name = getSchemaName2(type, typeNode);
654
+ if (!name) {
655
+ return build();
656
+ }
657
+ const { components, typeStack } = ctx;
658
+ if (components.has(name) || typeStack.has(type)) {
659
+ return { $ref: `#/components/schemas/${name}` };
660
+ }
661
+ typeStack.add(type);
662
+ const schema = build();
663
+ typeStack.delete(type);
664
+ if (!components.has(name)) {
665
+ components.set(name, schema);
666
+ }
667
+ return { $ref: `#/components/schemas/${name}` };
668
+ }
669
+ function getSchemaName2(type, typeNode) {
670
+ const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
671
+ const aliasName = aliasSymbol?.getName();
672
+ if (aliasName && aliasName !== "__type") {
673
+ return aliasName;
674
+ }
675
+ const symbol = type.getSymbol();
676
+ const symbolName = symbol?.getName?.();
677
+ if (symbolName && symbolName !== "__type") {
678
+ return symbolName;
679
+ }
680
+ const nodeName = getExplicitTypeNameFromNode3(typeNode);
681
+ if (nodeName && nodeName !== "__type") {
682
+ return nodeName;
683
+ }
684
+ return null;
685
+ }
686
+ function getExplicitTypeNameFromNode3(typeNode) {
687
+ if (!typeNode) return null;
688
+ if (ts5.isTypeReferenceNode(typeNode)) {
689
+ if (ts5.isIdentifier(typeNode.typeName)) {
690
+ return typeNode.typeName.text;
691
+ }
692
+ }
693
+ if (ts5.isTypeAliasDeclaration(typeNode.parent)) {
694
+ if (ts5.isIdentifier(typeNode.parent.name)) {
695
+ return typeNode.parent.name.text;
696
+ }
697
+ }
698
+ return null;
699
+ }
700
+
701
+ // src/compiler/schema/objectHandler.ts
702
+ import ts6 from "typescript";
595
703
  function handleObjectType(type, ctx, typeNode) {
596
704
  const { checker, components, typeStack } = ctx;
597
705
  const symbol = type.getSymbol();
598
706
  const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
707
+ if (isMetalOrmWrapperType(type, checker)) {
708
+ return handleMetalOrmWrapper(type, ctx);
709
+ }
599
710
  if (typeName && typeName !== "__type") {
711
+ const isMetalOrmGeneric = METAL_ORM_WRAPPER_NAMES.some(
712
+ (name) => typeName === name || typeName.endsWith("Api")
713
+ );
714
+ if (isMetalOrmGeneric) {
715
+ return {};
716
+ }
600
717
  if (components.has(typeName)) {
601
718
  return { $ref: `#/components/schemas/${typeName}` };
602
719
  }
@@ -608,7 +725,8 @@ function handleObjectType(type, ctx, typeNode) {
608
725
  const schema = buildObjectSchema(type, ctx, typeNode);
609
726
  if (typeName && typeName !== "__type") {
610
727
  typeStack.delete(type);
611
- if (!components.has(typeName)) {
728
+ const existing = components.get(typeName);
729
+ if (!existing) {
612
730
  components.set(typeName, schema);
613
731
  }
614
732
  return { $ref: `#/components/schemas/${typeName}` };
@@ -616,36 +734,26 @@ function handleObjectType(type, ctx, typeNode) {
616
734
  typeStack.delete(type);
617
735
  return schema;
618
736
  }
619
- function getExplicitTypeNameFromNode(typeNode) {
620
- if (!typeNode) return null;
621
- if (ts3.isTypeReferenceNode(typeNode)) {
622
- if (ts3.isIdentifier(typeNode.typeName)) {
623
- return typeNode.typeName.text;
624
- }
625
- }
626
- if (ts3.isTypeAliasDeclaration(typeNode.parent)) {
627
- if (ts3.isIdentifier(typeNode.parent.name)) {
628
- return typeNode.parent.name.text;
629
- }
630
- }
631
- return null;
632
- }
633
- function getTypeNameFromNode(typeNode, ctx) {
634
- const explicitName = getExplicitTypeNameFromNode(typeNode);
635
- if (explicitName) return explicitName;
636
- return `Anonymous_${ctx.typeNameStack.length}`;
637
- }
638
- function buildObjectSchema(type, ctx, typeNode) {
639
- const { checker } = ctx;
737
+ function buildObjectSchema(type, ctx, _typeNode) {
738
+ const { checker, mode } = ctx;
640
739
  const properties = {};
641
740
  const required = [];
642
741
  const props = checker.getPropertiesOfType(type);
643
742
  for (const prop of props) {
644
743
  const propName = prop.getName();
744
+ if (isIteratorOrSymbolProperty(propName)) {
745
+ continue;
746
+ }
645
747
  const propType = checker.getTypeOfSymbol(prop);
646
- const isOptional = !!(prop.flags & ts3.SymbolFlags.Optional);
647
- properties[propName] = typeToJsonSchema(propType, ctx);
648
- if (!isOptional) {
748
+ if (isMethodLike(propType)) {
749
+ continue;
750
+ }
751
+ const isOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
752
+ const isRelation = isMetalOrmWrapperType(propType, checker);
753
+ const propCtx = { ...ctx, propertyName: propName };
754
+ properties[propName] = typeToJsonSchema(propType, propCtx);
755
+ const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
756
+ if (shouldRequire) {
649
757
  required.push(propName);
650
758
  }
651
759
  }
@@ -664,14 +772,14 @@ function buildObjectSchema(type, ctx, typeNode) {
664
772
  }
665
773
  return schema;
666
774
  }
667
- function isRecordType(type, checker) {
775
+ function isRecordType(type, _checker) {
668
776
  const symbol = type.getSymbol();
669
777
  if (!symbol) return false;
670
778
  const name = symbol.getName();
671
779
  if (name === "Record") return true;
672
780
  return false;
673
781
  }
674
- function getRecordValueType(type, checker) {
782
+ function getRecordValueType(type, _checker) {
675
783
  const symbol = type.getSymbol();
676
784
  if (!symbol) return null;
677
785
  const name = symbol.getName();
@@ -684,24 +792,229 @@ function getRecordValueType(type, checker) {
684
792
  }
685
793
  return null;
686
794
  }
795
+ function isMetalOrmWrapperType(type, checker) {
796
+ return !!findMetalOrmWrapper(type, checker);
797
+ }
798
+ function isMethodLike(type) {
799
+ const callSigs = type.getCallSignatures?.();
800
+ return !!(callSigs && callSigs.length > 0);
801
+ }
802
+ function isIteratorOrSymbolProperty(propName) {
803
+ return propName.startsWith("__@") || propName.startsWith("[") || propName === Symbol.iterator.toString();
804
+ }
805
+ function getTypeNameFromNode(typeNode, _ctx) {
806
+ const explicitName = getExplicitTypeNameFromNode4(typeNode);
807
+ if (explicitName) return explicitName;
808
+ return "Anonymous_${ctx.typeNameStack.length}";
809
+ }
810
+ function getExplicitTypeNameFromNode4(typeNode) {
811
+ if (!typeNode) return null;
812
+ if (ts6.isTypeReferenceNode(typeNode)) {
813
+ if (ts6.isIdentifier(typeNode.typeName)) {
814
+ return typeNode.typeName.text;
815
+ }
816
+ }
817
+ if (ts6.isTypeAliasDeclaration(typeNode.parent)) {
818
+ if (ts6.isIdentifier(typeNode.parent.name)) {
819
+ return typeNode.parent.name.text;
820
+ }
821
+ }
822
+ return null;
823
+ }
824
+ var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
825
+ function findMetalOrmWrapper(type, checker) {
826
+ if (type.isIntersection()) {
827
+ let wrapperInfo = null;
828
+ let hasReadonlyArray = false;
829
+ for (const constituent of type.types) {
830
+ const result = findWrapperInType(constituent, checker);
831
+ if (result) {
832
+ wrapperInfo = result;
833
+ }
834
+ if (!(constituent.flags & ts6.TypeFlags.Object)) continue;
835
+ const symbol = constituent.getSymbol();
836
+ if (symbol?.getName() === "ReadonlyArray") {
837
+ hasReadonlyArray = true;
838
+ }
839
+ }
840
+ if (wrapperInfo) {
841
+ return { ...wrapperInfo, isReadonlyArray: hasReadonlyArray };
842
+ }
843
+ return null;
844
+ }
845
+ return findWrapperInType(type, checker);
846
+ }
847
+ function findWrapperInType(type, checker) {
848
+ const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
849
+ const symbol = type.getSymbol();
850
+ const effectiveSymbol = aliasSymbol && aliasSymbol.flags & ts6.SymbolFlags.Alias ? checker.getAliasedSymbol(aliasSymbol) : symbol;
851
+ if (!effectiveSymbol) return null;
852
+ const name = effectiveSymbol.getName();
853
+ if (!METAL_ORM_WRAPPER_NAMES.includes(name)) return null;
854
+ const typeRef = type;
855
+ const typeArgs = typeRef.typeArguments || [];
856
+ return {
857
+ wrapperName: name,
858
+ targetTypeArgs: typeArgs,
859
+ isReadonlyArray: false
860
+ };
861
+ }
862
+ function getWrapperTypeName(type, _checker) {
863
+ const symbol = type.getSymbol();
864
+ if (!symbol) return null;
865
+ const name = symbol.getName();
866
+ return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
867
+ }
868
+ function handleMetalOrmWrapper(type, ctx) {
869
+ const typeRef = type;
870
+ const typeArgs = typeRef.typeArguments;
871
+ const targetType = typeArgs?.[0] ?? null;
872
+ const wrapperName = getWrapperTypeName(type, ctx.checker);
873
+ if (!wrapperName) return {};
874
+ const wrapperRel = { wrapper: wrapperName };
875
+ if (wrapperName === "HasManyCollection" || wrapperName === "ManyToManyCollection") {
876
+ const items = targetType ? typeToJsonSchema(targetType, ctx) : {};
877
+ if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
878
+ wrapperRel.pivot = typeArgs[1];
879
+ }
880
+ return {
881
+ type: "array",
882
+ items,
883
+ "x-metal-orm-rel": wrapperRel
884
+ };
885
+ }
886
+ if (!targetType) {
887
+ return { "x-metal-orm-rel": wrapperRel };
888
+ }
889
+ if (wrapperName === "BelongsToReference" || wrapperName === "HasOneReference") {
890
+ return handleBelongsToReference(targetType, ctx, wrapperRel);
891
+ }
892
+ const targetSchema = typeToJsonSchema(targetType, ctx);
893
+ return {
894
+ ...targetSchema,
895
+ "x-metal-orm-rel": wrapperRel
896
+ };
897
+ }
898
+ function handleBelongsToReference(targetType, ctx, wrapperRel) {
899
+ const { components } = ctx;
900
+ const targetSymbol = targetType.getSymbol();
901
+ const typeName = targetSymbol?.getName();
902
+ if (!typeName) {
903
+ return {
904
+ type: "object",
905
+ properties: {},
906
+ "x-metal-orm-rel": wrapperRel
907
+ };
908
+ }
909
+ const refSchemaName = `${typeName}Ref`;
910
+ if (components.has(refSchemaName)) {
911
+ return {
912
+ $ref: `#/components/schemas/${refSchemaName}`,
913
+ "x-metal-orm-rel": wrapperRel
914
+ };
915
+ }
916
+ const refSchema = buildRefSchema(targetType, ctx);
917
+ components.set(refSchemaName, refSchema);
918
+ return {
919
+ $ref: `#/components/schemas/${refSchemaName}`,
920
+ "x-metal-orm-rel": wrapperRel
921
+ };
922
+ }
923
+ function buildRefSchema(type, ctx) {
924
+ const { checker } = ctx;
925
+ if (!(type.flags & ts6.TypeFlags.Object)) {
926
+ return { type: "object", properties: {} };
927
+ }
928
+ const objectType = type;
929
+ const properties = {};
930
+ const required = [];
931
+ const props = checker.getPropertiesOfType(objectType);
932
+ for (const prop of props) {
933
+ const propName = prop.getName();
934
+ if (isIteratorOrSymbolProperty(propName)) {
935
+ continue;
936
+ }
937
+ const propType = checker.getTypeOfSymbol(prop);
938
+ if (isMethodLike(propType)) {
939
+ continue;
940
+ }
941
+ const isOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
942
+ const isRelation = isMetalOrmWrapperType(propType, checker);
943
+ if (isRelation) {
944
+ continue;
945
+ }
946
+ const propCtx = { ...ctx, propertyName: propName };
947
+ properties[propName] = typeToJsonSchema(propType, propCtx);
948
+ if (!isOptional) {
949
+ required.push(propName);
950
+ }
951
+ }
952
+ const schema = {
953
+ type: "object",
954
+ properties
955
+ };
956
+ if (required.length > 0) {
957
+ schema.required = required;
958
+ }
959
+ return schema;
960
+ }
961
+
962
+ // src/compiler/schema/typeToJsonSchema.ts
963
+ function typeToJsonSchema(type, ctx, typeNode) {
964
+ const primitiveResult = handlePrimitiveType(type, ctx, typeNode);
965
+ if (primitiveResult) {
966
+ return primitiveResult;
967
+ }
968
+ if (type.isUnion()) {
969
+ return handleUnion(type, ctx, typeNode);
970
+ }
971
+ if (type.isIntersection()) {
972
+ return handleIntersection(type, ctx, typeNode);
973
+ }
974
+ if (ctx.checker.isArrayType(type)) {
975
+ const typeArgs = type.typeArguments;
976
+ const itemType = typeArgs?.[0];
977
+ const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
978
+ return {
979
+ type: "array",
980
+ items,
981
+ uniqueItems: isSetType(type, ctx.checker) ? true : void 0
982
+ };
983
+ }
984
+ if (type.flags & ts7.TypeFlags.Object) {
985
+ const objectType = type;
986
+ if (isMetalOrmWrapperType(type, ctx.checker)) {
987
+ return handleMetalOrmWrapper(objectType, ctx);
988
+ }
989
+ return handleObjectType(objectType, ctx, typeNode);
990
+ }
991
+ return {};
992
+ }
993
+ function isSetType(type, _checker) {
994
+ const symbol = type.getSymbol();
995
+ if (!symbol) return false;
996
+ const name = symbol.getName();
997
+ if (name === "Set") return true;
998
+ return false;
999
+ }
687
1000
 
688
1001
  // src/compiler/schema/extractAnnotations.ts
689
- import ts4 from "typescript";
1002
+ import ts8 from "typescript";
690
1003
  function extractPropertySchemaFragments(checker, prop) {
691
- if (!ts4.canHaveDecorators(prop)) return [];
692
- const decs = ts4.getDecorators(prop);
1004
+ if (!ts8.canHaveDecorators(prop)) return [];
1005
+ const decs = ts8.getDecorators(prop);
693
1006
  if (!decs || decs.length === 0) return [];
694
1007
  const frags = [];
695
1008
  for (const d of decs) {
696
1009
  const expr = d.expression;
697
1010
  let callee;
698
1011
  let args;
699
- if (ts4.isCallExpression(expr)) {
1012
+ if (ts8.isCallExpression(expr)) {
700
1013
  callee = expr.expression;
701
1014
  args = expr.arguments;
702
1015
  } else {
703
1016
  callee = expr;
704
- args = ts4.factory.createNodeArray([]);
1017
+ args = ts8.factory.createNodeArray([]);
705
1018
  }
706
1019
  const sym = checker.getSymbolAtLocation(callee);
707
1020
  if (!sym) continue;
@@ -710,7 +1023,7 @@ function extractPropertySchemaFragments(checker, prop) {
710
1023
  const name = resolved.name;
711
1024
  if (name === "Schema") {
712
1025
  const obj = args[0];
713
- if (obj && ts4.isObjectLiteralExpression(obj)) {
1026
+ if (obj && ts8.isObjectLiteralExpression(obj)) {
714
1027
  const frag = objectLiteralToJson(obj);
715
1028
  if (frag) frags.push(frag);
716
1029
  }
@@ -732,7 +1045,7 @@ function extractPropertySchemaFragments(checker, prop) {
732
1045
  frags.push({ format: args[0].text });
733
1046
  } else if (name === "Pattern") {
734
1047
  const arg = args[0];
735
- if (arg && ts4.isRegularExpressionLiteral(arg)) {
1048
+ if (arg && ts8.isRegularExpressionLiteral(arg)) {
736
1049
  frags.push({ pattern: extractRegexPattern(arg.text) });
737
1050
  } else if (isStringLiteral(arg)) {
738
1051
  frags.push({ pattern: arg.text });
@@ -749,11 +1062,11 @@ function extractPropertySchemaFragments(checker, prop) {
749
1062
  frags.push({ multipleOf: Number(args[0].text) });
750
1063
  } else if (name === "Example") {
751
1064
  frags.push({ example: literalToJson(args[0]) });
752
- } else if (name === "Examples" && ts4.isArrayLiteralExpression(args[0])) {
1065
+ } else if (name === "Examples" && ts8.isArrayLiteralExpression(args[0])) {
753
1066
  frags.push({ examples: args[0].elements.map((e) => literalToJson(e)) });
754
1067
  } else if (name === "Description" && isStringLiteral(args[0])) {
755
1068
  frags.push({ description: args[0].text });
756
- } else if (name === "Enum" && ts4.isArrayLiteralExpression(args[0])) {
1069
+ } else if (name === "Enum" && ts8.isArrayLiteralExpression(args[0])) {
757
1070
  frags.push({ enum: args[0].elements.map((e) => literalToJson(e)) });
758
1071
  } else if (name === "Const") {
759
1072
  frags.push({ const: literalToJson(args[0]) });
@@ -761,9 +1074,9 @@ function extractPropertySchemaFragments(checker, prop) {
761
1074
  frags.push({ default: literalToJson(args[0]) });
762
1075
  } else if (name === "AdditionalProperties") {
763
1076
  const arg = args[0];
764
- if (arg && (arg.kind === ts4.SyntaxKind.FalseKeyword || arg.kind === ts4.SyntaxKind.TrueKeyword)) {
765
- frags.push({ additionalProperties: arg.kind === ts4.SyntaxKind.TrueKeyword });
766
- } else if (arg && ts4.isObjectLiteralExpression(arg)) {
1077
+ if (arg && (arg.kind === ts8.SyntaxKind.FalseKeyword || arg.kind === ts8.SyntaxKind.TrueKeyword)) {
1078
+ frags.push({ additionalProperties: arg.kind === ts8.SyntaxKind.TrueKeyword });
1079
+ } else if (arg && ts8.isObjectLiteralExpression(arg)) {
767
1080
  const obj = objectLiteralToJson(arg);
768
1081
  if (obj) frags.push({ additionalProperties: obj });
769
1082
  }
@@ -776,7 +1089,7 @@ function extractPropertySchemaFragments(checker, prop) {
776
1089
  return frags;
777
1090
  }
778
1091
  function resolveImportedDecorator(checker, sym) {
779
- const target = sym.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
1092
+ const target = sym.flags & ts8.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
780
1093
  const name = target.getName();
781
1094
  const decl = target.declarations?.[0];
782
1095
  if (!decl) return null;
@@ -784,111 +1097,320 @@ function resolveImportedDecorator(checker, sym) {
784
1097
  if (fileName.includes("/node_modules/adorn-api/") || fileName.includes("/src/schema/")) {
785
1098
  return { module: "adorn-api/schema", name };
786
1099
  }
787
- return null;
788
- }
789
- function isNumberLiteral(node) {
790
- return !!node && ts4.isNumericLiteral(node);
1100
+ return null;
1101
+ }
1102
+ function isNumberLiteral(node) {
1103
+ return !!node && ts8.isNumericLiteral(node);
1104
+ }
1105
+ function isStringLiteral(node) {
1106
+ return !!node && ts8.isStringLiteral(node);
1107
+ }
1108
+ function extractRegexPattern(text) {
1109
+ const match = text.match(/^\/(.+)\/[gimsuy]*$/);
1110
+ return match ? match[1] : text;
1111
+ }
1112
+ function objectLiteralToJson(obj) {
1113
+ const out = {};
1114
+ for (const prop of obj.properties) {
1115
+ if (!ts8.isPropertyAssignment(prop)) continue;
1116
+ const name = prop.name;
1117
+ let key;
1118
+ if (ts8.isIdentifier(name)) {
1119
+ key = name.text;
1120
+ } else if (ts8.isStringLiteral(name)) {
1121
+ key = name.text;
1122
+ } else {
1123
+ continue;
1124
+ }
1125
+ out[key] = literalToJson(prop.initializer);
1126
+ }
1127
+ return out;
1128
+ }
1129
+ function literalToJson(node) {
1130
+ if (ts8.isStringLiteral(node)) return node.text;
1131
+ if (ts8.isNumericLiteral(node)) return Number(node.text);
1132
+ if (node.kind === ts8.SyntaxKind.TrueKeyword) return true;
1133
+ if (node.kind === ts8.SyntaxKind.FalseKeyword) return false;
1134
+ if (node.kind === ts8.SyntaxKind.NullKeyword) return null;
1135
+ if (ts8.isObjectLiteralExpression(node)) return objectLiteralToJson(node);
1136
+ if (ts8.isArrayLiteralExpression(node)) return node.elements.map((e) => literalToJson(e));
1137
+ return void 0;
1138
+ }
1139
+ function mergeFragments(base, ...frags) {
1140
+ const result = { ...base };
1141
+ for (const frag of frags) {
1142
+ Object.assign(result, frag);
1143
+ }
1144
+ return result;
1145
+ }
1146
+
1147
+ // src/compiler/schema/parameters.ts
1148
+ function buildPathParameters(operation, ctx, parameters) {
1149
+ for (const paramIndex of operation.pathParamIndices) {
1150
+ const param = operation.parameters[paramIndex];
1151
+ if (param) {
1152
+ let paramSchema = typeToJsonSchema(param.type, ctx);
1153
+ if (param.paramNode) {
1154
+ const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
1155
+ if (frags.length > 0) {
1156
+ paramSchema = mergeFragments(paramSchema, ...frags);
1157
+ }
1158
+ }
1159
+ const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
1160
+ const paramName = param.name.toLowerCase();
1161
+ const isIdParam = paramName === "id" || paramName.endsWith("id");
1162
+ if (!schema.$ref && schema.type === "number" && isIdParam) {
1163
+ schema.type = "integer";
1164
+ if (!schema.minimum) {
1165
+ schema.minimum = 1;
1166
+ }
1167
+ }
1168
+ parameters.push({
1169
+ name: param.name,
1170
+ in: "path",
1171
+ required: !param.isOptional,
1172
+ schema
1173
+ });
1174
+ }
1175
+ }
1176
+ }
1177
+ function buildQueryParameters(operation, ctx, parameters) {
1178
+ if (operation.queryObjectParamIndex !== null) {
1179
+ const queryParam = operation.parameters[operation.queryObjectParamIndex];
1180
+ if (!queryParam) return;
1181
+ const querySchema = typeToJsonSchema(queryParam.type, ctx);
1182
+ const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
1183
+ for (const [propName, propSchema] of Object.entries(queryObjProps)) {
1184
+ const isRequired = queryRequired.includes(propName);
1185
+ const isObjectLike = isObjectLikeSchema(propSchema, ctx);
1186
+ const serialization = determineQuerySerialization(propSchema.type);
1187
+ const exampleValue = generateExampleValue(propSchema, propName);
1188
+ if (isObjectLike) {
1189
+ const schemaRef = propSchema.$ref || "#/components/schemas/InlineQueryParam";
1190
+ parameters.push({
1191
+ name: propName,
1192
+ in: "query",
1193
+ required: isRequired,
1194
+ schema: { type: "string" },
1195
+ description: `JSON-encoded object. ${exampleValue}`,
1196
+ examples: {
1197
+ default: { value: parseExampleValue(exampleValue) }
1198
+ },
1199
+ "x-adorn-jsonSchemaRef": schemaRef
1200
+ });
1201
+ } else {
1202
+ const paramDef = {
1203
+ name: propName,
1204
+ in: "query",
1205
+ required: isRequired,
1206
+ schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
1207
+ };
1208
+ if (propName === "page") {
1209
+ paramDef.schema = { type: "integer", default: 1, minimum: 1 };
1210
+ } else if (propName === "pageSize") {
1211
+ paramDef.schema = { type: "integer", default: 10, minimum: 1 };
1212
+ } else if (propName === "totalItems") {
1213
+ paramDef.schema = { type: "integer", minimum: 0 };
1214
+ } else if (propName === "sort") {
1215
+ paramDef.schema = {
1216
+ oneOf: [
1217
+ { type: "string" },
1218
+ { type: "array", items: { type: "string" } }
1219
+ ]
1220
+ };
1221
+ } else if (propName === "q") {
1222
+ paramDef.schema = { type: "string" };
1223
+ } else if (propName === "hasComments") {
1224
+ paramDef.schema = { type: "boolean" };
1225
+ }
1226
+ if (Object.keys(serialization).length > 0) {
1227
+ Object.assign(paramDef, serialization);
1228
+ }
1229
+ parameters.push(paramDef);
1230
+ }
1231
+ }
1232
+ }
1233
+ for (const paramIndex of operation.queryParamIndices) {
1234
+ const param = operation.parameters[paramIndex];
1235
+ if (param) {
1236
+ let paramSchema = typeToJsonSchema(param.type, ctx);
1237
+ if (param.paramNode) {
1238
+ const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
1239
+ if (frags.length > 0) {
1240
+ paramSchema = mergeFragments(paramSchema, ...frags);
1241
+ }
1242
+ }
1243
+ const isObjectLike = isObjectLikeSchema(paramSchema, ctx);
1244
+ if (isObjectLike) {
1245
+ const schemaRef = paramSchema.$ref || "#/components/schemas/InlineQueryParam";
1246
+ const exampleValue = generateExampleValue(paramSchema, param.name);
1247
+ parameters.push({
1248
+ name: param.name,
1249
+ in: "query",
1250
+ required: !param.isOptional,
1251
+ schema: { type: "string" },
1252
+ description: `JSON-encoded object. ${exampleValue}`,
1253
+ examples: {
1254
+ default: { value: parseExampleValue(exampleValue) }
1255
+ },
1256
+ "x-adorn-jsonSchemaRef": schemaRef
1257
+ });
1258
+ } else {
1259
+ const serialization = determineQuerySerialization(paramSchema.type);
1260
+ parameters.push({
1261
+ name: param.name,
1262
+ in: "query",
1263
+ required: !param.isOptional,
1264
+ schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema,
1265
+ ...Object.keys(serialization).length > 0 ? serialization : {}
1266
+ });
1267
+ }
1268
+ }
1269
+ }
1270
+ }
1271
+ function buildHeaderParameters(operation, ctx, parameters) {
1272
+ if (operation.headerObjectParamIndex === null) return;
1273
+ const headerParam = operation.parameters[operation.headerObjectParamIndex];
1274
+ if (!headerParam) return;
1275
+ const headerSchema = typeToJsonSchema(headerParam.type, ctx);
1276
+ if (!headerSchema.properties) return;
1277
+ const headerObjProps = headerSchema.properties;
1278
+ for (const [propName, propSchema] of Object.entries(headerObjProps)) {
1279
+ const isRequired = headerSchema.required?.includes(propName) ?? false;
1280
+ parameters.push({
1281
+ name: propName,
1282
+ in: "header",
1283
+ required: isRequired,
1284
+ schema: propSchema
1285
+ });
1286
+ }
791
1287
  }
792
- function isStringLiteral(node) {
793
- return !!node && ts4.isStringLiteral(node);
1288
+ function buildCookieParameters(operation, ctx, parameters) {
1289
+ if (operation.cookieObjectParamIndex === null) return;
1290
+ const cookieParam = operation.parameters[operation.cookieObjectParamIndex];
1291
+ if (!cookieParam) return;
1292
+ const cookieSchema = typeToJsonSchema(cookieParam.type, ctx);
1293
+ if (!cookieSchema.properties) return;
1294
+ const cookieObjProps = cookieSchema.properties;
1295
+ for (const [propName, propSchema] of Object.entries(cookieObjProps)) {
1296
+ const isRequired = cookieSchema.required?.includes(propName) ?? false;
1297
+ parameters.push({
1298
+ name: propName,
1299
+ in: "cookie",
1300
+ required: isRequired,
1301
+ schema: propSchema,
1302
+ style: "form",
1303
+ explode: true
1304
+ });
1305
+ }
794
1306
  }
795
- function extractRegexPattern(text) {
796
- const match = text.match(/^\/(.+)\/[gimsuy]*$/);
797
- return match ? match[1] : text;
1307
+ function determineQuerySerialization(schemaType) {
1308
+ const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
1309
+ const isArray = typeArray.includes("array");
1310
+ if (isArray) {
1311
+ return { style: "form", explode: true };
1312
+ }
1313
+ return {};
798
1314
  }
799
- function objectLiteralToJson(obj) {
800
- const out = {};
801
- for (const prop of obj.properties) {
802
- if (!ts4.isPropertyAssignment(prop)) continue;
803
- const name = prop.name;
804
- let key;
805
- if (ts4.isIdentifier(name)) {
806
- key = name.text;
807
- } else if (ts4.isStringLiteral(name)) {
808
- key = name.text;
809
- } else {
810
- continue;
1315
+ function generateExampleValue(schema, propName) {
1316
+ const resolved = resolveSchemaRef(schema, /* @__PURE__ */ new Map());
1317
+ if (resolved.type === "object" && resolved.properties) {
1318
+ const example = {};
1319
+ for (const [key, prop] of Object.entries(resolved.properties)) {
1320
+ const propResolved = resolveSchemaRef(prop, /* @__PURE__ */ new Map());
1321
+ if (propResolved.type === "string") {
1322
+ example[key] = "value";
1323
+ } else if (propResolved.type === "number" || propResolved.type === "integer") {
1324
+ example[key] = 1;
1325
+ } else if (propResolved.type === "boolean") {
1326
+ example[key] = true;
1327
+ } else if (Array.isArray(propResolved.type) && propResolved.type.includes("null")) {
1328
+ example[key] = null;
1329
+ } else if (propResolved.enum) {
1330
+ example[key] = propResolved.enum[0];
1331
+ } else {
1332
+ example[key] = "value";
1333
+ }
811
1334
  }
812
- out[key] = literalToJson(prop.initializer);
1335
+ return `Example: ${propName}=${JSON.stringify(example)}`;
813
1336
  }
814
- return out;
815
- }
816
- function literalToJson(node) {
817
- if (ts4.isStringLiteral(node)) return node.text;
818
- if (ts4.isNumericLiteral(node)) return Number(node.text);
819
- if (node.kind === ts4.SyntaxKind.TrueKeyword) return true;
820
- if (node.kind === ts4.SyntaxKind.FalseKeyword) return false;
821
- if (node.kind === ts4.SyntaxKind.NullKeyword) return null;
822
- if (ts4.isObjectLiteralExpression(node)) return objectLiteralToJson(node);
823
- if (ts4.isArrayLiteralExpression(node)) return node.elements.map((e) => literalToJson(e));
824
- return void 0;
1337
+ return `Example: ${propName}=${JSON.stringify({ key: "value" })}`;
825
1338
  }
826
- function mergeFragments(base, ...frags) {
827
- const result = { ...base };
828
- for (const frag of frags) {
829
- Object.assign(result, frag);
1339
+ function parseExampleValue(description) {
1340
+ const match = description.match(/Example:\s*\w+=(\{[^}]+\})/);
1341
+ if (match) {
1342
+ return match[1];
830
1343
  }
831
- return result;
1344
+ return JSON.stringify({ key: "value" });
832
1345
  }
833
-
834
- // src/compiler/analyze/extractQueryStyle.ts
835
- import ts5 from "typescript";
836
- function extractQueryStyleOptions(checker, method) {
837
- if (!ts5.canHaveDecorators(method)) return null;
838
- const decorators = ts5.getDecorators(method);
839
- if (!decorators || decorators.length === 0) return null;
840
- for (const decorator of decorators) {
841
- const expr = decorator.expression;
842
- const isCall = ts5.isCallExpression(expr);
843
- const callee = isCall ? expr.expression : expr;
844
- const args = isCall ? expr.arguments : ts5.factory.createNodeArray([]);
845
- const sym = checker.getSymbolAtLocation(callee);
846
- if (!sym) continue;
847
- const resolved = sym.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
848
- const name = resolved.getName();
849
- if (name !== "QueryStyle") continue;
850
- const optsNode = args[0];
851
- if (!optsNode || !ts5.isObjectLiteralExpression(optsNode)) {
852
- return {};
1346
+ function isObjectLikeSchema(schema, ctx) {
1347
+ const resolved = resolveSchemaRef(schema, ctx.components);
1348
+ if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
1349
+ return true;
1350
+ }
1351
+ if (resolved.allOf) {
1352
+ for (const branch of resolved.allOf) {
1353
+ if (isObjectLikeSchema(branch, ctx)) {
1354
+ return true;
1355
+ }
853
1356
  }
854
- return parseQueryStyleOptions(optsNode);
855
1357
  }
856
- return null;
1358
+ if (resolved.type === "array" && resolved.items) {
1359
+ const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
1360
+ return isObjectLikeSchema(itemsSchema, ctx);
1361
+ }
1362
+ return false;
857
1363
  }
858
- function parseQueryStyleOptions(node) {
859
- const opts = {};
860
- for (const prop of node.properties) {
861
- if (!ts5.isPropertyAssignment(prop)) continue;
862
- const name = getPropName(prop.name);
863
- if (!name) continue;
864
- if (name === "style" && ts5.isStringLiteral(prop.initializer)) {
865
- const style = prop.initializer.text;
866
- opts.style = style;
867
- } else if (name === "explode" && isBooleanLiteral(prop.initializer)) {
868
- opts.explode = prop.initializer.kind === ts5.SyntaxKind.TrueKeyword;
869
- } else if (name === "allowReserved" && isBooleanLiteral(prop.initializer)) {
870
- opts.allowReserved = prop.initializer.kind === ts5.SyntaxKind.TrueKeyword;
871
- }
872
- }
873
- return opts;
874
- }
875
- function getPropName(name) {
876
- if (ts5.isIdentifier(name)) return name.text;
877
- if (ts5.isStringLiteral(name)) return name.text;
878
- return null;
1364
+ function resolveSchemaRef(schema, components) {
1365
+ const ref = schema.$ref;
1366
+ if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
1367
+ return schema;
1368
+ }
1369
+ const name = ref.replace("#/components/schemas/", "");
1370
+ const next = components.get(name);
1371
+ if (!next) return schema;
1372
+ return resolveSchemaRef(next, components);
879
1373
  }
880
- function isBooleanLiteral(node) {
881
- return node.kind === ts5.SyntaxKind.TrueKeyword || node.kind === ts5.SyntaxKind.FalseKeyword;
1374
+ function resolveAndCollectObjectProps(schema, components) {
1375
+ const resolved = resolveSchemaRef(schema, components);
1376
+ const properties = {};
1377
+ const required = [];
1378
+ const processSchema = (s) => {
1379
+ const current = resolveSchemaRef(s, components);
1380
+ if (current.properties) {
1381
+ for (const [key, val] of Object.entries(current.properties)) {
1382
+ if (!properties[key]) {
1383
+ properties[key] = val;
1384
+ }
1385
+ }
1386
+ }
1387
+ if (current.required) {
1388
+ for (const req of current.required) {
1389
+ if (!required.includes(req)) {
1390
+ required.push(req);
1391
+ }
1392
+ }
1393
+ }
1394
+ if (current.allOf) {
1395
+ for (const branch of current.allOf) {
1396
+ processSchema(branch);
1397
+ }
1398
+ }
1399
+ };
1400
+ processSchema(resolved);
1401
+ return { properties, required };
882
1402
  }
883
1403
 
884
1404
  // src/compiler/schema/openapi.ts
1405
+ var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
885
1406
  function generateOpenAPI(controllers, checker, options = {}) {
886
1407
  const components = /* @__PURE__ */ new Map();
887
1408
  const ctx = {
888
1409
  checker,
889
1410
  components,
890
1411
  typeStack: /* @__PURE__ */ new Set(),
891
- typeNameStack: []
1412
+ typeNameStack: [],
1413
+ mode: "response"
892
1414
  };
893
1415
  const paths = {};
894
1416
  for (const controller of controllers) {
@@ -901,6 +1423,8 @@ function generateOpenAPI(controllers, checker, options = {}) {
901
1423
  paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
902
1424
  }
903
1425
  }
1426
+ const schemas = Object.fromEntries(components);
1427
+ cleanupMetalOrmWrappers(schemas, paths);
904
1428
  return {
905
1429
  openapi: "3.1.0",
906
1430
  info: {
@@ -908,15 +1432,120 @@ function generateOpenAPI(controllers, checker, options = {}) {
908
1432
  version: options.version ?? "1.0.0"
909
1433
  },
910
1434
  components: {
911
- schemas: Object.fromEntries(components)
1435
+ schemas
912
1436
  },
913
1437
  paths
914
1438
  };
915
1439
  }
1440
+ function cleanupMetalOrmWrappers(schemas, paths) {
1441
+ const schemasToDelete = /* @__PURE__ */ new Set();
1442
+ for (const wrapperName of METAL_ORM_WRAPPER_NAMES2) {
1443
+ if (schemas[wrapperName]) {
1444
+ schemasToDelete.add(wrapperName);
1445
+ }
1446
+ if (schemas[`${wrapperName}Api`]) {
1447
+ schemasToDelete.add(`${wrapperName}Api`);
1448
+ }
1449
+ }
1450
+ for (const schema of Object.values(schemas)) {
1451
+ cleanupSchemaRefs(schema, schemasToDelete);
1452
+ }
1453
+ for (const pathItem of Object.values(paths)) {
1454
+ cleanupPathItemRefs(pathItem, schemasToDelete);
1455
+ }
1456
+ for (const schemaName of schemasToDelete) {
1457
+ delete schemas[schemaName];
1458
+ }
1459
+ }
1460
+ function cleanupSchemaRefs(schema, schemasToDelete) {
1461
+ if (typeof schema !== "object" || schema === null) {
1462
+ return;
1463
+ }
1464
+ if (schema.properties) {
1465
+ for (const propName of Object.keys(schema.properties)) {
1466
+ const propSchema = schema.properties[propName];
1467
+ if (propSchema.$ref && typeof propSchema.$ref === "string") {
1468
+ const refName = propSchema.$ref.replace("#/components/schemas/", "");
1469
+ if (schemasToDelete.has(refName)) {
1470
+ delete schema.properties[propName];
1471
+ if (schema.required && Array.isArray(schema.required)) {
1472
+ schema.required = schema.required.filter((r) => r !== propName);
1473
+ }
1474
+ }
1475
+ } else {
1476
+ cleanupSchemaRefs(propSchema, schemasToDelete);
1477
+ }
1478
+ }
1479
+ }
1480
+ if (schema.items) {
1481
+ cleanupSchemaRefs(schema.items, schemasToDelete);
1482
+ }
1483
+ if (schema.allOf) {
1484
+ for (const item of schema.allOf) {
1485
+ cleanupSchemaRefs(item, schemasToDelete);
1486
+ }
1487
+ }
1488
+ }
1489
+ function cleanupPathItemRefs(pathItem, schemasToDelete) {
1490
+ if (typeof pathItem !== "object" || pathItem === null) {
1491
+ return;
1492
+ }
1493
+ for (const method of Object.keys(pathItem)) {
1494
+ const operation = pathItem[method];
1495
+ if (typeof operation !== "object" || operation === null) continue;
1496
+ if (operation.requestBody) {
1497
+ cleanupRequestBodyRefs(operation.requestBody, schemasToDelete);
1498
+ }
1499
+ if (operation.responses) {
1500
+ const responses = Object.values(operation.responses);
1501
+ for (const response of responses) {
1502
+ if (response.content) {
1503
+ const contentTypes = Object.values(response.content);
1504
+ for (const contentType of contentTypes) {
1505
+ if (contentType.schema) {
1506
+ cleanupSchemaRefs(contentType.schema, schemasToDelete);
1507
+ }
1508
+ }
1509
+ }
1510
+ }
1511
+ }
1512
+ }
1513
+ }
1514
+ function cleanupRequestBodyRefs(requestBody, schemasToDelete) {
1515
+ if (typeof requestBody !== "object" || requestBody === null) return;
1516
+ if (requestBody.content) {
1517
+ const contentTypes = Object.values(requestBody.content);
1518
+ for (const contentType of contentTypes) {
1519
+ if (contentType.schema) {
1520
+ cleanupSchemaRefs(contentType.schema, schemasToDelete);
1521
+ }
1522
+ }
1523
+ }
1524
+ if (requestBody.properties) {
1525
+ for (const propName of Object.keys(requestBody.properties)) {
1526
+ const propSchema = requestBody.properties[propName];
1527
+ if (propSchema.$ref && typeof propSchema.$ref === "string") {
1528
+ const refName = propSchema.$ref.replace("#/components/schemas/", "");
1529
+ if (schemasToDelete.has(refName)) {
1530
+ delete requestBody.properties[propName];
1531
+ if (requestBody.required && Array.isArray(requestBody.required)) {
1532
+ requestBody.required = requestBody.required.filter((r) => r !== propName);
1533
+ }
1534
+ }
1535
+ } else {
1536
+ cleanupSchemaRefs(propSchema, schemasToDelete);
1537
+ }
1538
+ }
1539
+ }
1540
+ }
916
1541
  function convertToOpenApiPath(basePath, path4) {
917
1542
  const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
918
1543
  const converted = path4.replace(/:([^/]+)/g, "{$1}");
919
- return base + converted || "/";
1544
+ let fullPath = base + converted || "/";
1545
+ if (fullPath.endsWith("/") && fullPath !== "/") {
1546
+ fullPath = fullPath.slice(0, -1);
1547
+ }
1548
+ return fullPath;
920
1549
  }
921
1550
  function buildOperation(operation, ctx, controllerConsumes) {
922
1551
  const op = {
@@ -931,7 +1560,8 @@ function buildOperation(operation, ctx, controllerConsumes) {
931
1560
  if (parameters.length > 0) {
932
1561
  op.parameters = parameters;
933
1562
  }
934
- const responseSchema = typeToJsonSchema(operation.returnType, ctx, operation.returnTypeNode);
1563
+ const responseCtx = { ...ctx, mode: "response" };
1564
+ const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
935
1565
  const status = operation.httpMethod === "POST" ? 201 : 200;
936
1566
  op.responses[status] = {
937
1567
  description: status === 201 ? "Created" : "OK",
@@ -944,8 +1574,9 @@ function buildOperation(operation, ctx, controllerConsumes) {
944
1574
  if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
945
1575
  const bodyParam = operation.parameters[operation.bodyParamIndex];
946
1576
  if (bodyParam) {
947
- let bodySchema = typeToJsonSchema(bodyParam.type, ctx);
948
- bodySchema = mergeBodySchemaAnnotations(bodyParam, ctx, bodySchema);
1577
+ const requestCtx = { ...ctx, mode: "request" };
1578
+ let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
1579
+ bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
949
1580
  const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
950
1581
  const requestBody = {
951
1582
  required: !bodyParam.isOptional,
@@ -972,12 +1603,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
972
1603
  const declarations = typeSymbol.getDeclarations();
973
1604
  if (!declarations || declarations.length === 0) return schema;
974
1605
  const classDecl = declarations[0];
975
- if (!ts6.isClassDeclaration(classDecl)) return schema;
1606
+ if (!ts9.isClassDeclaration(classDecl)) return schema;
976
1607
  const result = { ...schema };
977
1608
  const props = { ...result.properties };
978
1609
  for (const member of classDecl.members) {
979
- if (!ts6.isPropertyDeclaration(member) || !member.name) continue;
980
- const propName = ts6.isIdentifier(member.name) ? member.name.text : null;
1610
+ if (!ts9.isPropertyDeclaration(member) || !member.name) continue;
1611
+ const propName = ts9.isIdentifier(member.name) ? member.name.text : null;
981
1612
  if (!propName) continue;
982
1613
  if (!props[propName]) continue;
983
1614
  const frags = extractPropertySchemaFragments(ctx.checker, member);
@@ -988,137 +1619,17 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
988
1619
  result.properties = props;
989
1620
  return result;
990
1621
  }
991
- function buildPathParameters(operation, ctx, parameters) {
992
- for (const paramIndex of operation.pathParamIndices) {
993
- const param = operation.parameters[paramIndex];
994
- if (param) {
995
- let paramSchema = typeToJsonSchema(param.type, ctx);
996
- if (param.paramNode) {
997
- const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
998
- if (frags.length > 0) {
999
- paramSchema = mergeFragments(paramSchema, ...frags);
1000
- }
1001
- }
1002
- parameters.push({
1003
- name: param.name,
1004
- in: "path",
1005
- required: !param.isOptional,
1006
- schema: paramSchema.$ref ? { type: "string", $ref: paramSchema.$ref } : paramSchema
1007
- });
1008
- }
1009
- }
1010
- }
1011
- function buildQueryParameters(operation, ctx, parameters) {
1012
- if (operation.queryObjectParamIndex !== null) {
1013
- const queryParam = operation.parameters[operation.queryObjectParamIndex];
1014
- if (!queryParam) return;
1015
- const queryStyle = extractQueryStyleOptions(ctx.checker, operation.methodDeclaration);
1016
- const querySchema = typeToJsonSchema(queryParam.type, ctx);
1017
- if (queryStyle?.style === "deepObject") {
1018
- const explode = queryStyle.explode ?? true;
1019
- const deepParam = {
1020
- name: queryParam.name,
1021
- in: "query",
1022
- required: !queryParam.isOptional,
1023
- schema: querySchema.$ref ? { $ref: querySchema.$ref } : querySchema,
1024
- style: "deepObject",
1025
- explode
1026
- };
1027
- if (queryStyle.allowReserved !== void 0) {
1028
- deepParam.allowReserved = queryStyle.allowReserved;
1029
- }
1030
- parameters.push(deepParam);
1031
- } else {
1032
- if (!querySchema.properties) return;
1033
- const queryObjProps = querySchema.properties;
1034
- for (const [propName, propSchema] of Object.entries(queryObjProps)) {
1035
- const isRequired = querySchema.required?.includes(propName) ?? false;
1036
- const serialization = determineQuerySerialization(propSchema.type);
1037
- parameters.push({
1038
- name: propName,
1039
- in: "query",
1040
- required: isRequired,
1041
- schema: propSchema,
1042
- ...Object.keys(serialization).length > 0 ? serialization : {}
1043
- });
1044
- }
1045
- }
1046
- }
1047
- for (const paramIndex of operation.queryParamIndices) {
1048
- const param = operation.parameters[paramIndex];
1049
- if (param) {
1050
- let paramSchema = typeToJsonSchema(param.type, ctx);
1051
- if (param.paramNode) {
1052
- const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
1053
- if (frags.length > 0) {
1054
- paramSchema = mergeFragments(paramSchema, ...frags);
1055
- }
1056
- }
1057
- const serialization = determineQuerySerialization(paramSchema.type);
1058
- parameters.push({
1059
- name: param.name,
1060
- in: "query",
1061
- required: !param.isOptional,
1062
- schema: paramSchema.$ref ? { type: "string", $ref: paramSchema.$ref } : paramSchema,
1063
- ...Object.keys(serialization).length > 0 ? serialization : {}
1064
- });
1065
- }
1066
- }
1067
- }
1068
- function determineQuerySerialization(schemaType) {
1069
- const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
1070
- const isArray = typeArray.includes("array");
1071
- if (isArray) {
1072
- return { style: "form", explode: true };
1073
- }
1074
- return {};
1075
- }
1076
- function buildHeaderParameters(operation, ctx, parameters) {
1077
- if (operation.headerObjectParamIndex === null) return;
1078
- const headerParam = operation.parameters[operation.headerObjectParamIndex];
1079
- if (!headerParam) return;
1080
- const headerSchema = typeToJsonSchema(headerParam.type, ctx);
1081
- if (!headerSchema.properties) return;
1082
- const headerObjProps = headerSchema.properties;
1083
- for (const [propName, propSchema] of Object.entries(headerObjProps)) {
1084
- const isRequired = headerSchema.required?.includes(propName) ?? false;
1085
- parameters.push({
1086
- name: propName,
1087
- in: "header",
1088
- required: isRequired,
1089
- schema: propSchema
1090
- });
1091
- }
1092
- }
1093
- function buildCookieParameters(operation, ctx, parameters) {
1094
- if (operation.cookieObjectParamIndex === null) return;
1095
- const cookieParam = operation.parameters[operation.cookieObjectParamIndex];
1096
- if (!cookieParam) return;
1097
- const cookieSchema = typeToJsonSchema(cookieParam.type, ctx);
1098
- if (!cookieSchema.properties) return;
1099
- const cookieObjProps = cookieSchema.properties;
1100
- for (const [propName, propSchema] of Object.entries(cookieObjProps)) {
1101
- const isRequired = cookieSchema.required?.includes(propName) ?? false;
1102
- parameters.push({
1103
- name: propName,
1104
- in: "cookie",
1105
- required: isRequired,
1106
- schema: propSchema,
1107
- style: "form",
1108
- explode: true
1109
- });
1110
- }
1111
- }
1112
1622
 
1113
1623
  // src/compiler/manifest/emit.ts
1114
- import ts7 from "typescript";
1624
+ import ts10 from "typescript";
1115
1625
  function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
1116
1626
  const components = /* @__PURE__ */ new Map();
1117
1627
  const ctx = {
1118
1628
  checker,
1119
1629
  components,
1120
1630
  typeStack: /* @__PURE__ */ new Set(),
1121
- typeNameStack: []
1631
+ typeNameStack: [],
1632
+ mode: "request"
1122
1633
  };
1123
1634
  const controllerEntries = controllers.map((ctrl) => ({
1124
1635
  controllerId: ctrl.className,
@@ -1132,7 +1643,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
1132
1643
  generator: {
1133
1644
  name: "adorn-api",
1134
1645
  version,
1135
- typescript: ts7.version
1646
+ typescript: ts10.version
1136
1647
  },
1137
1648
  schemas: {
1138
1649
  kind: "openapi-3.1",
@@ -1143,14 +1654,70 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
1143
1654
  controllers: controllerEntries
1144
1655
  };
1145
1656
  }
1657
+ function resolveSchemaRef2(schema, components) {
1658
+ const ref = schema.$ref;
1659
+ if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
1660
+ return schema;
1661
+ }
1662
+ const name = ref.replace("#/components/schemas/", "");
1663
+ const next = components.get(name);
1664
+ if (!next) return schema;
1665
+ return resolveSchemaRef2(next, components);
1666
+ }
1667
+ function resolveAndCollectObjectProps2(schema, components) {
1668
+ const resolved = resolveSchemaRef2(schema, components);
1669
+ const properties = {};
1670
+ const required = [];
1671
+ const processSchema = (s) => {
1672
+ const current = resolveSchemaRef2(s, components);
1673
+ if (current.properties) {
1674
+ for (const [key, val] of Object.entries(current.properties)) {
1675
+ if (!properties[key]) {
1676
+ properties[key] = val;
1677
+ }
1678
+ }
1679
+ }
1680
+ if (current.required) {
1681
+ for (const req of current.required) {
1682
+ if (!required.includes(req)) {
1683
+ required.push(req);
1684
+ }
1685
+ }
1686
+ }
1687
+ if (current.allOf) {
1688
+ for (const branch of current.allOf) {
1689
+ processSchema(branch);
1690
+ }
1691
+ }
1692
+ };
1693
+ processSchema(resolved);
1694
+ return { properties, required };
1695
+ }
1696
+ function isObjectLikeSchema2(schema, components) {
1697
+ const resolved = resolveSchemaRef2(schema, components);
1698
+ if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
1699
+ return true;
1700
+ }
1701
+ if (resolved.allOf) {
1702
+ for (const branch of resolved.allOf) {
1703
+ if (isObjectLikeSchema2(branch, components)) {
1704
+ return true;
1705
+ }
1706
+ }
1707
+ }
1708
+ if (resolved.type === "array" && resolved.items) {
1709
+ const itemsSchema = resolveSchemaRef2(resolved.items, components);
1710
+ return isObjectLikeSchema2(itemsSchema, components);
1711
+ }
1712
+ return false;
1713
+ }
1146
1714
  function buildOperationEntry(op, ctx) {
1147
1715
  const args = {
1148
1716
  body: null,
1149
1717
  path: [],
1150
1718
  query: [],
1151
1719
  headers: [],
1152
- cookies: [],
1153
- paginationParamIndex: op.paginationParamIndex
1720
+ cookies: []
1154
1721
  };
1155
1722
  buildPathArgs(op, ctx, args);
1156
1723
  buildQueryArgs(op, ctx, args);
@@ -1217,53 +1784,39 @@ function buildPathArgs(op, ctx, args) {
1217
1784
  function buildQueryArgs(op, ctx, args) {
1218
1785
  if (op.queryObjectParamIndex !== null) {
1219
1786
  const queryParam = op.parameters[op.queryObjectParamIndex];
1220
- if (queryParam) {
1221
- const queryStyle = extractQueryStyleOptions(ctx.checker, op.methodDeclaration);
1222
- const querySchema = typeToJsonSchema(queryParam.type, ctx);
1223
- if (queryStyle?.style === "deepObject") {
1224
- const schemaRef = querySchema.$ref ?? "#/components/schemas/InlineQueryParam";
1225
- args.query.push({
1226
- name: queryParam.name,
1227
- index: queryParam.index,
1228
- required: !queryParam.isOptional,
1229
- schemaRef,
1230
- schemaType: querySchema.type,
1231
- serialization: {
1232
- style: "deepObject",
1233
- explode: queryStyle.explode ?? true,
1234
- allowReserved: queryStyle.allowReserved
1235
- }
1236
- });
1237
- } else {
1238
- if (!querySchema.properties) return;
1239
- for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
1240
- const isRequired = querySchema.required?.includes(propName) ?? false;
1241
- let schemaRef = propSchema.$ref;
1242
- if (!schemaRef) {
1243
- schemaRef = "#/components/schemas/InlineQueryParam";
1244
- }
1245
- args.query.push({
1246
- name: propName,
1247
- index: queryParam.index,
1248
- required: !isRequired,
1249
- schemaRef,
1250
- schemaType: propSchema.type
1251
- });
1252
- }
1787
+ if (!queryParam) return;
1788
+ const querySchema = typeToJsonSchema(queryParam.type, ctx);
1789
+ const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps2(querySchema, ctx.components);
1790
+ for (const [propName, propSchema] of Object.entries(queryObjProps)) {
1791
+ const isRequired = queryRequired.includes(propName) ?? false;
1792
+ const isObjectLike = isObjectLikeSchema2(propSchema, ctx.components);
1793
+ let schemaRef = propSchema.$ref;
1794
+ if (!schemaRef) {
1795
+ schemaRef = "#/components/schemas/InlineQueryParam";
1253
1796
  }
1797
+ args.query.push({
1798
+ name: propName,
1799
+ index: queryParam.index,
1800
+ required: isRequired,
1801
+ schemaRef,
1802
+ schemaType: propSchema.type,
1803
+ content: isObjectLike ? "application/json" : void 0
1804
+ });
1254
1805
  }
1255
1806
  }
1256
1807
  for (const paramIndex of op.queryParamIndices) {
1257
1808
  const param = op.parameters[paramIndex];
1258
1809
  if (param) {
1259
1810
  const paramSchema = typeToJsonSchema(param.type, ctx);
1811
+ const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
1260
1812
  const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
1261
1813
  args.query.push({
1262
1814
  name: param.name,
1263
1815
  index: param.index,
1264
1816
  required: !param.isOptional,
1265
1817
  schemaRef,
1266
- schemaType: paramSchema.type
1818
+ schemaType: paramSchema.type,
1819
+ content: isObjectLike ? "application/json" : void 0
1267
1820
  });
1268
1821
  }
1269
1822
  }
@@ -1283,7 +1836,7 @@ function buildHeaderArgs(op, ctx, args) {
1283
1836
  args.headers.push({
1284
1837
  name: propName,
1285
1838
  index: headerParam.index,
1286
- required: !isRequired,
1839
+ required: isRequired,
1287
1840
  schemaRef,
1288
1841
  schemaType: propSchema.type
1289
1842
  });
@@ -1304,7 +1857,7 @@ function buildCookieArgs(op, ctx, args) {
1304
1857
  args.cookies.push({
1305
1858
  name: propName,
1306
1859
  index: cookieParam.index,
1307
- required: !isRequired,
1860
+ required: isRequired,
1308
1861
  schemaRef,
1309
1862
  schemaType: propSchema.type,
1310
1863
  serialization: { style: "form", explode: true }
@@ -1320,7 +1873,7 @@ import Ajv from "ajv";
1320
1873
  import addFormats from "ajv-formats";
1321
1874
  var OAS_SCHEMA_ONLY = /* @__PURE__ */ new Set(["discriminator", "xml", "externalDocs", "example"]);
1322
1875
  function sanitizeSchemaForAjv(schema) {
1323
- if (schema == null || typeof schema !== "object") return schema;
1876
+ if (schema === null || typeof schema !== "object") return schema;
1324
1877
  if (Array.isArray(schema)) return schema.map(sanitizeSchemaForAjv);
1325
1878
  const out = {};
1326
1879
  for (const [k, v] of Object.entries(schema)) {
@@ -1331,7 +1884,7 @@ function sanitizeSchemaForAjv(schema) {
1331
1884
  return out;
1332
1885
  }
1333
1886
  function rewriteComponentRefs(schema) {
1334
- if (schema == null || typeof schema !== "object") return schema;
1887
+ if (schema === null || typeof schema !== "object") return schema;
1335
1888
  if (Array.isArray(schema)) return schema.map(rewriteComponentRefs);
1336
1889
  if (typeof schema.$ref === "string") {
1337
1890
  const ref = schema.$ref;
@@ -1408,16 +1961,13 @@ async function emitPrecompiledValidators(opts) {
1408
1961
  `;
1409
1962
  cjs += ` body: ${v.body ? `exports[${JSON.stringify(v.body)}]` : "undefined"},
1410
1963
  `;
1411
- cjs += ` response: {
1412
- `;
1964
+ cjs += " response: {\n";
1413
1965
  for (const [key, id] of Object.entries(v.response)) {
1414
1966
  cjs += ` ${JSON.stringify(key)}: exports[${JSON.stringify(id)}],
1415
1967
  `;
1416
1968
  }
1417
- cjs += ` }
1418
- `;
1419
- cjs += ` },
1420
- `;
1969
+ cjs += " }\n";
1970
+ cjs += " },\n";
1421
1971
  }
1422
1972
  cjs += "};\n";
1423
1973
  fs.writeFileSync(cjsPath, cjs, "utf8");
@@ -1452,7 +2002,6 @@ export function validateResponse(operationId, status, contentType, data) {
1452
2002
  // src/compiler/cache/isStale.ts
1453
2003
  import fs2 from "fs";
1454
2004
  import path2 from "path";
1455
- import "typescript";
1456
2005
  function readJson(p) {
1457
2006
  try {
1458
2007
  return JSON.parse(fs2.readFileSync(p, "utf8"));
@@ -1509,7 +2058,7 @@ function findLockfile(startDir) {
1509
2058
  for (const n of names) {
1510
2059
  const p = path2.join(dir, n);
1511
2060
  const mt = statMtimeMs(p);
1512
- if (mt != null) return { path: p, mtimeMs: mt };
2061
+ if (mt !== null) return { path: p, mtimeMs: mt };
1513
2062
  }
1514
2063
  const parent = path2.dirname(dir);
1515
2064
  if (parent === dir) break;
@@ -1534,22 +2083,22 @@ async function isStale(params) {
1534
2083
  const chain = collectTsconfigChain(tsconfigAbs);
1535
2084
  for (const cfg of chain) {
1536
2085
  const mt = statMtimeMs(cfg);
1537
- if (mt == null) return { stale: true, reason: "config-missing", detail: cfg };
2086
+ if (mt === null) return { stale: true, reason: "config-missing", detail: cfg };
1538
2087
  const cachedMt = cache.project.configFiles[cfg];
1539
- if (cachedMt == null || Math.abs(cachedMt - mt) > 1e-4) {
2088
+ if (cachedMt === null || Math.abs(cachedMt - mt) > 1e-4) {
1540
2089
  return { stale: true, reason: "config-updated", detail: cfg };
1541
2090
  }
1542
2091
  }
1543
2092
  if (cache.project.lockfile?.path) {
1544
2093
  const mt = statMtimeMs(cache.project.lockfile.path);
1545
- if (mt == null) return { stale: true, reason: "lockfile-missing", detail: cache.project.lockfile.path };
2094
+ if (mt === null) return { stale: true, reason: "lockfile-missing", detail: cache.project.lockfile.path };
1546
2095
  if (Math.abs(cache.project.lockfile.mtimeMs - mt) > 1e-4) {
1547
2096
  return { stale: true, reason: "lockfile-updated", detail: cache.project.lockfile.path };
1548
2097
  }
1549
2098
  }
1550
2099
  for (const [file, cachedMt] of Object.entries(cache.inputs)) {
1551
2100
  const mt = statMtimeMs(file);
1552
- if (mt == null) return { stale: true, reason: "input-missing", detail: file };
2101
+ if (mt === null) return { stale: true, reason: "input-missing", detail: file };
1553
2102
  if (Math.abs(cachedMt - mt) > 1e-4) return { stale: true, reason: "input-updated", detail: file };
1554
2103
  }
1555
2104
  return { stale: false, reason: "up-to-date" };
@@ -1558,7 +2107,7 @@ async function isStale(params) {
1558
2107
  // src/compiler/cache/writeCache.ts
1559
2108
  import fs3 from "fs";
1560
2109
  import path3 from "path";
1561
- import ts9 from "typescript";
2110
+ import ts11 from "typescript";
1562
2111
  function statMtimeMs2(p) {
1563
2112
  return fs3.statSync(p).mtimeMs;
1564
2113
  }
@@ -1591,7 +2140,7 @@ function writeCache(params) {
1591
2140
  generator: {
1592
2141
  name: "adorn-api",
1593
2142
  version: params.adornVersion,
1594
- typescript: ts9.version
2143
+ typescript: ts11.version
1595
2144
  },
1596
2145
  project: {
1597
2146
  tsconfigPath: params.tsconfigAbs,
@@ -1604,7 +2153,7 @@ function writeCache(params) {
1604
2153
  }
1605
2154
 
1606
2155
  // src/cli.ts
1607
- import ts10 from "typescript";
2156
+ import ts12 from "typescript";
1608
2157
  import process from "process";
1609
2158
  var ADORN_VERSION = "0.1.0";
1610
2159
  function log(msg) {
@@ -1615,6 +2164,26 @@ function debug(...args) {
1615
2164
  console.error("[adorn-api]", ...args);
1616
2165
  }
1617
2166
  }
2167
+ function sanitizeForJson(obj) {
2168
+ if (obj === null || obj === void 0) return obj;
2169
+ if (typeof obj !== "object") return obj;
2170
+ if (Array.isArray(obj)) {
2171
+ return obj.map((item) => sanitizeForJson(item));
2172
+ }
2173
+ const result = {};
2174
+ for (const [key, value] of Object.entries(obj)) {
2175
+ if (key.startsWith("__@") || key.startsWith("[")) continue;
2176
+ if (typeof value === "function") continue;
2177
+ if (value !== null && typeof value === "object") {
2178
+ const typeName = value.constructor?.name;
2179
+ if (typeName && !["Object", "Array", "String", "Number", "Boolean", "Date", "RegExp"].includes(typeName)) {
2180
+ continue;
2181
+ }
2182
+ }
2183
+ result[key] = sanitizeForJson(value);
2184
+ }
2185
+ return result;
2186
+ }
1618
2187
  async function buildCommand(args) {
1619
2188
  const projectIndex = args.indexOf("-p");
1620
2189
  const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
@@ -1632,7 +2201,7 @@ async function buildCommand(args) {
1632
2201
  outDir: outputDir,
1633
2202
  project: projectPath,
1634
2203
  adornVersion: ADORN_VERSION,
1635
- typescriptVersion: ts10.version
2204
+ typescriptVersion: ts12.version
1636
2205
  });
1637
2206
  if (!stale.stale) {
1638
2207
  log("adorn-api: artifacts up-to-date");
@@ -1653,7 +2222,7 @@ async function buildCommand(args) {
1653
2222
  const openapi = generateOpenAPI(controllers, checker, { title: "API", version: "1.0.0" });
1654
2223
  const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
1655
2224
  mkdirSync(outputPath, { recursive: true });
1656
- writeFileSync(resolve2(outputPath, "openapi.json"), JSON.stringify(openapi, null, 2));
2225
+ writeFileSync(resolve2(outputPath, "openapi.json"), JSON.stringify(sanitizeForJson(openapi), null, 2));
1657
2226
  writeFileSync(resolve2(outputPath, "manifest.json"), JSON.stringify(manifest, null, 2));
1658
2227
  if (validationMode === "precompiled") {
1659
2228
  log("Generating precompiled validators...");