adorn-api 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/express/bootstrap.d.ts.map +1 -1
- package/dist/adapter/express/merge.d.ts.map +1 -1
- package/dist/adapter/express/router.d.ts.map +1 -1
- package/dist/cli.cjs +799 -409
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +799 -409
- package/dist/cli.js.map +1 -1
- package/dist/compiler/cache/isStale.d.ts.map +1 -1
- package/dist/compiler/cache/writeCache.d.ts.map +1 -1
- package/dist/compiler/schema/intersectionHandler.d.ts +7 -0
- package/dist/compiler/schema/intersectionHandler.d.ts.map +1 -0
- package/dist/compiler/schema/objectHandler.d.ts +20 -0
- package/dist/compiler/schema/objectHandler.d.ts.map +1 -0
- package/dist/compiler/schema/openapi.d.ts +1 -1
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/parameters.d.ts +18 -0
- package/dist/compiler/schema/parameters.d.ts.map +1 -0
- package/dist/compiler/schema/primitives.d.ts +10 -0
- package/dist/compiler/schema/primitives.d.ts.map +1 -0
- package/dist/compiler/schema/typeToJsonSchema.d.ts +2 -51
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +1 -1
- package/dist/compiler/schema/types.d.ts +54 -0
- package/dist/compiler/schema/types.d.ts.map +1 -0
- package/dist/compiler/schema/unionHandler.d.ts +10 -0
- package/dist/compiler/schema/unionHandler.d.ts.map +1 -0
- package/dist/express.cjs +41 -44
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +41 -44
- package/dist/express.js.map +1 -1
- package/dist/http.d.ts +1 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metal/index.cjs +2 -2
- package/dist/metal/index.cjs.map +1 -1
- package/dist/metal/index.js +2 -2
- package/dist/metal/index.js.map +1 -1
- package/dist/metal/listQuery.d.ts.map +1 -1
- package/dist/metal/queryOptions.d.ts.map +1 -1
- package/dist/metal/registerMetalEntities.d.ts.map +1 -1
- package/package.json +4 -1
package/dist/cli.cjs
CHANGED
|
@@ -103,7 +103,7 @@ function analyzeClass(node, sourceFile, checker) {
|
|
|
103
103
|
produces
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
-
function extractClassConsumes(node,
|
|
106
|
+
function extractClassConsumes(node, _checker) {
|
|
107
107
|
const decorator = findDecorator(node, "Consumes");
|
|
108
108
|
if (!decorator) return void 0;
|
|
109
109
|
const callExpr = decorator.expression;
|
|
@@ -119,7 +119,7 @@ function extractClassConsumes(node, checker) {
|
|
|
119
119
|
}
|
|
120
120
|
return void 0;
|
|
121
121
|
}
|
|
122
|
-
function extractClassProduces(node,
|
|
122
|
+
function extractClassProduces(node, _checker) {
|
|
123
123
|
const decorator = findDecorator(node, "Produces");
|
|
124
124
|
if (!decorator) return void 0;
|
|
125
125
|
const callExpr = decorator.expression;
|
|
@@ -301,7 +301,7 @@ function extractDecoratorStringArg(decorator) {
|
|
|
301
301
|
}
|
|
302
302
|
return null;
|
|
303
303
|
}
|
|
304
|
-
function unwrapPromise(type,
|
|
304
|
+
function unwrapPromise(type, _checker) {
|
|
305
305
|
const symbol = type.getSymbol();
|
|
306
306
|
if (symbol?.getName() === "Promise") {
|
|
307
307
|
const typeArgs = type.typeArguments;
|
|
@@ -322,12 +322,15 @@ function unwrapPromiseTypeNode(typeNode) {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
// src/compiler/schema/openapi.ts
|
|
325
|
-
var
|
|
325
|
+
var import_typescript9 = __toESM(require("typescript"), 1);
|
|
326
326
|
|
|
327
327
|
// src/compiler/schema/typeToJsonSchema.ts
|
|
328
|
+
var import_typescript7 = __toESM(require("typescript"), 1);
|
|
329
|
+
|
|
330
|
+
// src/compiler/schema/primitives.ts
|
|
328
331
|
var import_typescript3 = __toESM(require("typescript"), 1);
|
|
329
|
-
function
|
|
330
|
-
const { checker } = ctx;
|
|
332
|
+
function handlePrimitiveType(type, ctx, typeNode) {
|
|
333
|
+
const { checker, propertyName } = ctx;
|
|
331
334
|
if (type.flags & import_typescript3.default.TypeFlags.Undefined) {
|
|
332
335
|
return {};
|
|
333
336
|
}
|
|
@@ -341,7 +344,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
341
344
|
return { type: "string" };
|
|
342
345
|
}
|
|
343
346
|
if (type.flags & import_typescript3.default.TypeFlags.Number) {
|
|
344
|
-
return normalizeNumericType(type, checker, typeNode);
|
|
347
|
+
return normalizeNumericType(type, checker, typeNode, propertyName);
|
|
345
348
|
}
|
|
346
349
|
if (type.flags & import_typescript3.default.TypeFlags.Boolean) {
|
|
347
350
|
return { type: "boolean" };
|
|
@@ -365,26 +368,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
365
368
|
const intrinsic = type.intrinsicName;
|
|
366
369
|
return { type: "boolean", enum: [intrinsic === "true"] };
|
|
367
370
|
}
|
|
368
|
-
|
|
369
|
-
return handleUnion(type, ctx, typeNode);
|
|
370
|
-
}
|
|
371
|
-
if (type.isIntersection()) {
|
|
372
|
-
return handleIntersection(type, ctx, typeNode);
|
|
373
|
-
}
|
|
374
|
-
if (checker.isArrayType(type)) {
|
|
375
|
-
const typeArgs = type.typeArguments;
|
|
376
|
-
const itemType = typeArgs?.[0];
|
|
377
|
-
const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
|
|
378
|
-
return {
|
|
379
|
-
type: "array",
|
|
380
|
-
items,
|
|
381
|
-
uniqueItems: isSetType(type, checker) ? true : void 0
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
if (type.flags & import_typescript3.default.TypeFlags.Object) {
|
|
385
|
-
return handleObjectType(type, ctx, typeNode);
|
|
386
|
-
}
|
|
387
|
-
return {};
|
|
371
|
+
return null;
|
|
388
372
|
}
|
|
389
373
|
function isDateType(type, checker) {
|
|
390
374
|
const symbol = type.getSymbol();
|
|
@@ -399,53 +383,51 @@ function isDateType(type, checker) {
|
|
|
399
383
|
}
|
|
400
384
|
return symbol?.getName() === "Date";
|
|
401
385
|
}
|
|
402
|
-
function
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
function getSchemaName(type, typeNode) {
|
|
410
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
411
|
-
const aliasName = aliasSymbol?.getName();
|
|
412
|
-
if (aliasName && aliasName !== "__type") {
|
|
413
|
-
return aliasName;
|
|
386
|
+
function normalizeNumericType(type, checker, typeNode, propertyName) {
|
|
387
|
+
const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
|
|
388
|
+
const symbol = getEffectiveSymbol(type, checker);
|
|
389
|
+
const symbolName = symbol?.getName() ?? null;
|
|
390
|
+
if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName) || shouldBeIntegerType(propertyName ?? null)) {
|
|
391
|
+
return { type: "integer" };
|
|
414
392
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
393
|
+
return { type: "number" };
|
|
394
|
+
}
|
|
395
|
+
function shouldBeIntegerType(typeName) {
|
|
396
|
+
if (!typeName) return false;
|
|
397
|
+
const lower = typeName.toLowerCase();
|
|
398
|
+
return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk" || lower === "page" || lower === "pagesize" || lower === "totalitems" || lower === "limit" || lower === "offset";
|
|
399
|
+
}
|
|
400
|
+
function getExplicitTypeNameFromNode(typeNode) {
|
|
401
|
+
if (!typeNode) return null;
|
|
402
|
+
if (import_typescript3.default.isTypeReferenceNode(typeNode)) {
|
|
403
|
+
if (import_typescript3.default.isIdentifier(typeNode.typeName)) {
|
|
404
|
+
return typeNode.typeName.text;
|
|
405
|
+
}
|
|
419
406
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
407
|
+
if (import_typescript3.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
408
|
+
if (import_typescript3.default.isIdentifier(typeNode.parent.name)) {
|
|
409
|
+
return typeNode.parent.name.text;
|
|
410
|
+
}
|
|
423
411
|
}
|
|
424
412
|
return null;
|
|
425
413
|
}
|
|
426
|
-
function
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
return
|
|
430
|
-
}
|
|
431
|
-
const { components, typeStack } = ctx;
|
|
432
|
-
if (components.has(name) || typeStack.has(type)) {
|
|
433
|
-
return { $ref: `#/components/schemas/${name}` };
|
|
434
|
-
}
|
|
435
|
-
typeStack.add(type);
|
|
436
|
-
const schema = build();
|
|
437
|
-
typeStack.delete(type);
|
|
438
|
-
if (!components.has(name)) {
|
|
439
|
-
components.set(name, schema);
|
|
414
|
+
function getEffectiveSymbol(type, checker) {
|
|
415
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
416
|
+
if (aliasSymbol && aliasSymbol.flags & import_typescript3.default.SymbolFlags.Alias) {
|
|
417
|
+
return checker.getAliasedSymbol(aliasSymbol);
|
|
440
418
|
}
|
|
441
|
-
return
|
|
419
|
+
return type.getSymbol() ?? null;
|
|
442
420
|
}
|
|
421
|
+
|
|
422
|
+
// src/compiler/schema/unionHandler.ts
|
|
423
|
+
var import_typescript4 = __toESM(require("typescript"), 1);
|
|
443
424
|
function handleUnion(type, ctx, typeNode) {
|
|
444
425
|
return buildNamedSchema(type, ctx, typeNode, () => {
|
|
445
426
|
const types = type.types;
|
|
446
|
-
const nullType = types.find((t) => t.flags &
|
|
447
|
-
const
|
|
448
|
-
const
|
|
427
|
+
const nullType = types.find((t) => t.flags & import_typescript4.default.TypeFlags.Null);
|
|
428
|
+
const undefinedType = types.find((t) => t.flags & import_typescript4.default.TypeFlags.Undefined);
|
|
429
|
+
const otherTypes = types.filter((t) => !(t.flags & import_typescript4.default.TypeFlags.Null) && !(t.flags & import_typescript4.default.TypeFlags.Undefined));
|
|
430
|
+
const allStringLiterals = otherTypes.every((t) => t.flags & import_typescript4.default.TypeFlags.StringLiteral);
|
|
449
431
|
if (allStringLiterals && otherTypes.length > 0) {
|
|
450
432
|
const enumValues = otherTypes.map((t) => t.value);
|
|
451
433
|
const schema = { type: "string", enum: enumValues };
|
|
@@ -454,6 +436,14 @@ function handleUnion(type, ctx, typeNode) {
|
|
|
454
436
|
}
|
|
455
437
|
return schema;
|
|
456
438
|
}
|
|
439
|
+
const allBooleanLiterals = otherTypes.length > 0 && otherTypes.every((t) => t.flags & import_typescript4.default.TypeFlags.BooleanLiteral);
|
|
440
|
+
if (allBooleanLiterals) {
|
|
441
|
+
const schema = { type: "boolean" };
|
|
442
|
+
if (nullType || undefinedType) {
|
|
443
|
+
schema.type = ["boolean", "null"];
|
|
444
|
+
}
|
|
445
|
+
return schema;
|
|
446
|
+
}
|
|
457
447
|
if (otherTypes.length === 1 && nullType) {
|
|
458
448
|
const innerSchema = typeToJsonSchema(otherTypes[0], ctx);
|
|
459
449
|
if (typeof innerSchema.type === "string") {
|
|
@@ -483,49 +473,7 @@ function handleUnion(type, ctx, typeNode) {
|
|
|
483
473
|
return {};
|
|
484
474
|
});
|
|
485
475
|
}
|
|
486
|
-
function
|
|
487
|
-
return buildNamedSchema(type, ctx, typeNode, () => {
|
|
488
|
-
const types = type.types;
|
|
489
|
-
const brandCollapsed = tryCollapseBrandedIntersection(types, ctx, typeNode);
|
|
490
|
-
if (brandCollapsed) {
|
|
491
|
-
return brandCollapsed;
|
|
492
|
-
}
|
|
493
|
-
const allOf = [];
|
|
494
|
-
for (const t of types) {
|
|
495
|
-
allOf.push(typeToJsonSchema(t, ctx));
|
|
496
|
-
}
|
|
497
|
-
return { allOf };
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
function tryCollapseBrandedIntersection(types, ctx, typeNode) {
|
|
501
|
-
const { checker } = ctx;
|
|
502
|
-
const parts = [...types];
|
|
503
|
-
const prim = parts.find(isPrimitiveLike);
|
|
504
|
-
if (!prim) return null;
|
|
505
|
-
const rest = parts.filter((p) => p !== prim);
|
|
506
|
-
if (rest.every((r) => isBrandObject(checker, r, ctx))) {
|
|
507
|
-
return typeToJsonSchema(prim, ctx);
|
|
508
|
-
}
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
function isPrimitiveLike(t) {
|
|
512
|
-
return (t.flags & (import_typescript3.default.TypeFlags.String | import_typescript3.default.TypeFlags.Number | import_typescript3.default.TypeFlags.Boolean | import_typescript3.default.TypeFlags.BigInt)) !== 0 || (t.flags & import_typescript3.default.TypeFlags.StringLiteral) !== 0 || (t.flags & import_typescript3.default.TypeFlags.NumberLiteral) !== 0;
|
|
513
|
-
}
|
|
514
|
-
function isBrandObject(checker, t, ctx) {
|
|
515
|
-
if (!(t.flags & import_typescript3.default.TypeFlags.Object)) return false;
|
|
516
|
-
const props = t.getProperties();
|
|
517
|
-
if (props.length === 0) return false;
|
|
518
|
-
const allowed = /* @__PURE__ */ new Set(["__brand", "__type", "__tag", "brand"]);
|
|
519
|
-
for (const p of props) {
|
|
520
|
-
if (!allowed.has(p.getName())) return false;
|
|
521
|
-
}
|
|
522
|
-
const callSigs = t.getCallSignatures?.();
|
|
523
|
-
if (callSigs && callSigs.length > 0) return false;
|
|
524
|
-
const constructSigs = t.getConstructSignatures?.();
|
|
525
|
-
if (constructSigs && constructSigs.length > 0) return false;
|
|
526
|
-
return true;
|
|
527
|
-
}
|
|
528
|
-
function detectDiscriminatedUnion(types, ctx, branches) {
|
|
476
|
+
function detectDiscriminatedUnion(types, ctx, _branches) {
|
|
529
477
|
if (types.length < 2) return null;
|
|
530
478
|
const candidates = findCommonPropertyNames(ctx.checker, types);
|
|
531
479
|
for (const propName of candidates) {
|
|
@@ -556,10 +504,10 @@ function findCommonPropertyNames(checker, types) {
|
|
|
556
504
|
function isRequiredProperty(checker, type, propName) {
|
|
557
505
|
const sym = checker.getPropertyOfType(type, propName);
|
|
558
506
|
if (!sym) return false;
|
|
559
|
-
if (sym.flags &
|
|
507
|
+
if (sym.flags & import_typescript4.default.SymbolFlags.Optional) return false;
|
|
560
508
|
const propType = checker.getTypeOfSymbol(sym);
|
|
561
509
|
if (propType.isUnion?.()) {
|
|
562
|
-
const hasUndefined = propType.types.some((t) => (t.flags &
|
|
510
|
+
const hasUndefined = propType.types.some((t) => (t.flags & import_typescript4.default.TypeFlags.Undefined) !== 0);
|
|
563
511
|
if (hasUndefined) return false;
|
|
564
512
|
}
|
|
565
513
|
return true;
|
|
@@ -602,14 +550,187 @@ function getBranchSchemaName(type, ctx) {
|
|
|
602
550
|
}
|
|
603
551
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
604
552
|
}
|
|
553
|
+
function buildNamedSchema(type, ctx, typeNode, build) {
|
|
554
|
+
const name = getSchemaName(type, typeNode);
|
|
555
|
+
if (!name) {
|
|
556
|
+
return build();
|
|
557
|
+
}
|
|
558
|
+
const { components, typeStack } = ctx;
|
|
559
|
+
if (components.has(name) || typeStack.has(type)) {
|
|
560
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
561
|
+
}
|
|
562
|
+
typeStack.add(type);
|
|
563
|
+
const schema = build();
|
|
564
|
+
typeStack.delete(type);
|
|
565
|
+
if (!components.has(name)) {
|
|
566
|
+
components.set(name, schema);
|
|
567
|
+
}
|
|
568
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
569
|
+
}
|
|
570
|
+
function getSchemaName(type, typeNode) {
|
|
571
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
572
|
+
const aliasName = aliasSymbol?.getName();
|
|
573
|
+
if (aliasName && aliasName !== "__type") {
|
|
574
|
+
return aliasName;
|
|
575
|
+
}
|
|
576
|
+
const symbol = type.getSymbol();
|
|
577
|
+
const symbolName = symbol?.getName?.();
|
|
578
|
+
if (symbolName && symbolName !== "__type") {
|
|
579
|
+
return symbolName;
|
|
580
|
+
}
|
|
581
|
+
const nodeName = getExplicitTypeNameFromNode2(typeNode);
|
|
582
|
+
if (nodeName && nodeName !== "__type") {
|
|
583
|
+
return nodeName;
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
function getExplicitTypeNameFromNode2(typeNode) {
|
|
588
|
+
if (!typeNode) return null;
|
|
589
|
+
if (import_typescript4.default.isTypeReferenceNode(typeNode)) {
|
|
590
|
+
if (import_typescript4.default.isIdentifier(typeNode.typeName)) {
|
|
591
|
+
return typeNode.typeName.text;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (import_typescript4.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
595
|
+
if (import_typescript4.default.isIdentifier(typeNode.parent.name)) {
|
|
596
|
+
return typeNode.parent.name.text;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/compiler/schema/intersectionHandler.ts
|
|
603
|
+
var import_typescript5 = __toESM(require("typescript"), 1);
|
|
604
|
+
function handleIntersection(type, ctx, typeNode) {
|
|
605
|
+
return buildNamedSchema2(type, ctx, typeNode, () => {
|
|
606
|
+
const types = type.types;
|
|
607
|
+
const brandCollapsed = tryCollapseBrandedIntersection(types, ctx, typeNode);
|
|
608
|
+
if (brandCollapsed) {
|
|
609
|
+
return brandCollapsed;
|
|
610
|
+
}
|
|
611
|
+
const allOf = [];
|
|
612
|
+
for (const t of types) {
|
|
613
|
+
const schema = typeToJsonSchema(t, ctx);
|
|
614
|
+
if (Object.keys(schema).length > 0) {
|
|
615
|
+
if (isEmptyObjectSchema(schema)) {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
allOf.push(schema);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (allOf.length === 0) {
|
|
622
|
+
return {};
|
|
623
|
+
}
|
|
624
|
+
if (allOf.length === 1) {
|
|
625
|
+
return allOf[0];
|
|
626
|
+
}
|
|
627
|
+
return { allOf };
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
function tryCollapseBrandedIntersection(types, ctx, _typeNode) {
|
|
631
|
+
const { checker } = ctx;
|
|
632
|
+
const parts = [...types];
|
|
633
|
+
const prim = parts.find(isPrimitiveLike);
|
|
634
|
+
if (!prim) return null;
|
|
635
|
+
const rest = parts.filter((p) => p !== prim);
|
|
636
|
+
if (rest.every((r) => isBrandObject(checker, r, ctx))) {
|
|
637
|
+
return typeToJsonSchema(prim, ctx);
|
|
638
|
+
}
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
function isPrimitiveLike(t) {
|
|
642
|
+
return (t.flags & (import_typescript5.default.TypeFlags.String | import_typescript5.default.TypeFlags.Number | import_typescript5.default.TypeFlags.Boolean | import_typescript5.default.TypeFlags.BigInt)) !== 0 || (t.flags & import_typescript5.default.TypeFlags.StringLiteral) !== 0 || (t.flags & import_typescript5.default.TypeFlags.NumberLiteral) !== 0;
|
|
643
|
+
}
|
|
644
|
+
function isBrandObject(checker, t, _ctx) {
|
|
645
|
+
if (!(t.flags & import_typescript5.default.TypeFlags.Object)) return false;
|
|
646
|
+
const props = t.getProperties();
|
|
647
|
+
if (props.length === 0) return false;
|
|
648
|
+
const allowed = /* @__PURE__ */ new Set(["__brand", "__type", "__tag", "brand"]);
|
|
649
|
+
for (const p of props) {
|
|
650
|
+
if (!allowed.has(p.getName())) return false;
|
|
651
|
+
}
|
|
652
|
+
const callSigs = t.getCallSignatures?.();
|
|
653
|
+
if (callSigs && callSigs.length > 0) return false;
|
|
654
|
+
const constructSigs = t.getConstructSignatures?.();
|
|
655
|
+
if (constructSigs && constructSigs.length > 0) return false;
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
function isEmptyObjectSchema(schema) {
|
|
659
|
+
if (schema.type !== "object") {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
|
663
|
+
if (!schema.additionalProperties) {
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
function buildNamedSchema2(type, ctx, typeNode, build) {
|
|
670
|
+
const name = getSchemaName2(type, typeNode);
|
|
671
|
+
if (!name) {
|
|
672
|
+
return build();
|
|
673
|
+
}
|
|
674
|
+
const { components, typeStack } = ctx;
|
|
675
|
+
if (components.has(name) || typeStack.has(type)) {
|
|
676
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
677
|
+
}
|
|
678
|
+
typeStack.add(type);
|
|
679
|
+
const schema = build();
|
|
680
|
+
typeStack.delete(type);
|
|
681
|
+
if (!components.has(name)) {
|
|
682
|
+
components.set(name, schema);
|
|
683
|
+
}
|
|
684
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
685
|
+
}
|
|
686
|
+
function getSchemaName2(type, typeNode) {
|
|
687
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
688
|
+
const aliasName = aliasSymbol?.getName();
|
|
689
|
+
if (aliasName && aliasName !== "__type") {
|
|
690
|
+
return aliasName;
|
|
691
|
+
}
|
|
692
|
+
const symbol = type.getSymbol();
|
|
693
|
+
const symbolName = symbol?.getName?.();
|
|
694
|
+
if (symbolName && symbolName !== "__type") {
|
|
695
|
+
return symbolName;
|
|
696
|
+
}
|
|
697
|
+
const nodeName = getExplicitTypeNameFromNode3(typeNode);
|
|
698
|
+
if (nodeName && nodeName !== "__type") {
|
|
699
|
+
return nodeName;
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
function getExplicitTypeNameFromNode3(typeNode) {
|
|
704
|
+
if (!typeNode) return null;
|
|
705
|
+
if (import_typescript5.default.isTypeReferenceNode(typeNode)) {
|
|
706
|
+
if (import_typescript5.default.isIdentifier(typeNode.typeName)) {
|
|
707
|
+
return typeNode.typeName.text;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (import_typescript5.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
711
|
+
if (import_typescript5.default.isIdentifier(typeNode.parent.name)) {
|
|
712
|
+
return typeNode.parent.name.text;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/compiler/schema/objectHandler.ts
|
|
719
|
+
var import_typescript6 = __toESM(require("typescript"), 1);
|
|
605
720
|
function handleObjectType(type, ctx, typeNode) {
|
|
606
|
-
const { checker, components, typeStack
|
|
721
|
+
const { checker, components, typeStack } = ctx;
|
|
607
722
|
const symbol = type.getSymbol();
|
|
608
723
|
const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
|
|
609
724
|
if (isMetalOrmWrapperType(type, checker)) {
|
|
610
725
|
return handleMetalOrmWrapper(type, ctx);
|
|
611
726
|
}
|
|
612
727
|
if (typeName && typeName !== "__type") {
|
|
728
|
+
const isMetalOrmGeneric = METAL_ORM_WRAPPER_NAMES.some(
|
|
729
|
+
(name) => typeName === name || typeName.endsWith("Api")
|
|
730
|
+
);
|
|
731
|
+
if (isMetalOrmGeneric) {
|
|
732
|
+
return {};
|
|
733
|
+
}
|
|
613
734
|
if (components.has(typeName)) {
|
|
614
735
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
615
736
|
}
|
|
@@ -630,40 +751,7 @@ function handleObjectType(type, ctx, typeNode) {
|
|
|
630
751
|
typeStack.delete(type);
|
|
631
752
|
return schema;
|
|
632
753
|
}
|
|
633
|
-
function
|
|
634
|
-
if (!typeNode) return null;
|
|
635
|
-
if (import_typescript3.default.isTypeReferenceNode(typeNode)) {
|
|
636
|
-
if (import_typescript3.default.isIdentifier(typeNode.typeName)) {
|
|
637
|
-
return typeNode.typeName.text;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (import_typescript3.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
641
|
-
if (import_typescript3.default.isIdentifier(typeNode.parent.name)) {
|
|
642
|
-
return typeNode.parent.name.text;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return null;
|
|
646
|
-
}
|
|
647
|
-
function shouldBeIntegerType(typeName) {
|
|
648
|
-
if (!typeName) return false;
|
|
649
|
-
const lower = typeName.toLowerCase();
|
|
650
|
-
return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk";
|
|
651
|
-
}
|
|
652
|
-
function normalizeNumericType(type, checker, typeNode) {
|
|
653
|
-
const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
|
|
654
|
-
const symbol = getEffectiveSymbol(type, checker);
|
|
655
|
-
const symbolName = symbol?.getName() ?? null;
|
|
656
|
-
if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName)) {
|
|
657
|
-
return { type: "integer" };
|
|
658
|
-
}
|
|
659
|
-
return { type: "number" };
|
|
660
|
-
}
|
|
661
|
-
function getTypeNameFromNode(typeNode, ctx) {
|
|
662
|
-
const explicitName = getExplicitTypeNameFromNode(typeNode);
|
|
663
|
-
if (explicitName) return explicitName;
|
|
664
|
-
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
665
|
-
}
|
|
666
|
-
function buildObjectSchema(type, ctx, typeNode) {
|
|
754
|
+
function buildObjectSchema(type, ctx, _typeNode) {
|
|
667
755
|
const { checker, mode } = ctx;
|
|
668
756
|
const properties = {};
|
|
669
757
|
const required = [];
|
|
@@ -677,9 +765,10 @@ function buildObjectSchema(type, ctx, typeNode) {
|
|
|
677
765
|
if (isMethodLike(propType)) {
|
|
678
766
|
continue;
|
|
679
767
|
}
|
|
680
|
-
const isOptional = !!(prop.flags &
|
|
768
|
+
const isOptional = !!(prop.flags & import_typescript6.default.SymbolFlags.Optional);
|
|
681
769
|
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
682
|
-
|
|
770
|
+
const propCtx = { ...ctx, propertyName: propName };
|
|
771
|
+
properties[propName] = typeToJsonSchema(propType, propCtx);
|
|
683
772
|
const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
|
|
684
773
|
if (shouldRequire) {
|
|
685
774
|
required.push(propName);
|
|
@@ -700,14 +789,14 @@ function buildObjectSchema(type, ctx, typeNode) {
|
|
|
700
789
|
}
|
|
701
790
|
return schema;
|
|
702
791
|
}
|
|
703
|
-
function isRecordType(type,
|
|
792
|
+
function isRecordType(type, _checker) {
|
|
704
793
|
const symbol = type.getSymbol();
|
|
705
794
|
if (!symbol) return false;
|
|
706
795
|
const name = symbol.getName();
|
|
707
796
|
if (name === "Record") return true;
|
|
708
797
|
return false;
|
|
709
798
|
}
|
|
710
|
-
function getRecordValueType(type,
|
|
799
|
+
function getRecordValueType(type, _checker) {
|
|
711
800
|
const symbol = type.getSymbol();
|
|
712
801
|
if (!symbol) return null;
|
|
713
802
|
const name = symbol.getName();
|
|
@@ -720,24 +809,8 @@ function getRecordValueType(type, checker) {
|
|
|
720
809
|
}
|
|
721
810
|
return null;
|
|
722
811
|
}
|
|
723
|
-
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
724
812
|
function isMetalOrmWrapperType(type, checker) {
|
|
725
|
-
|
|
726
|
-
if (!aliasSymbol) return false;
|
|
727
|
-
return METAL_ORM_WRAPPER_NAMES.includes(aliasSymbol.getName());
|
|
728
|
-
}
|
|
729
|
-
function getWrapperTypeName(type, checker) {
|
|
730
|
-
const symbol = getEffectiveSymbol(type, checker);
|
|
731
|
-
if (!symbol) return null;
|
|
732
|
-
const name = symbol.getName();
|
|
733
|
-
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
734
|
-
}
|
|
735
|
-
function getEffectiveSymbol(type, checker) {
|
|
736
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
737
|
-
if (aliasSymbol && aliasSymbol.flags & import_typescript3.default.SymbolFlags.Alias) {
|
|
738
|
-
return checker.getAliasedSymbol(aliasSymbol);
|
|
739
|
-
}
|
|
740
|
-
return type.getSymbol() ?? null;
|
|
813
|
+
return !!findMetalOrmWrapper(type, checker);
|
|
741
814
|
}
|
|
742
815
|
function isMethodLike(type) {
|
|
743
816
|
const callSigs = type.getCallSignatures?.();
|
|
@@ -746,6 +819,69 @@ function isMethodLike(type) {
|
|
|
746
819
|
function isIteratorOrSymbolProperty(propName) {
|
|
747
820
|
return propName.startsWith("__@") || propName.startsWith("[") || propName === Symbol.iterator.toString();
|
|
748
821
|
}
|
|
822
|
+
function getTypeNameFromNode(typeNode, _ctx) {
|
|
823
|
+
const explicitName = getExplicitTypeNameFromNode4(typeNode);
|
|
824
|
+
if (explicitName) return explicitName;
|
|
825
|
+
return "Anonymous_${ctx.typeNameStack.length}";
|
|
826
|
+
}
|
|
827
|
+
function getExplicitTypeNameFromNode4(typeNode) {
|
|
828
|
+
if (!typeNode) return null;
|
|
829
|
+
if (import_typescript6.default.isTypeReferenceNode(typeNode)) {
|
|
830
|
+
if (import_typescript6.default.isIdentifier(typeNode.typeName)) {
|
|
831
|
+
return typeNode.typeName.text;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (import_typescript6.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
835
|
+
if (import_typescript6.default.isIdentifier(typeNode.parent.name)) {
|
|
836
|
+
return typeNode.parent.name.text;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
842
|
+
function findMetalOrmWrapper(type, checker) {
|
|
843
|
+
if (type.isIntersection()) {
|
|
844
|
+
let wrapperInfo = null;
|
|
845
|
+
let hasReadonlyArray = false;
|
|
846
|
+
for (const constituent of type.types) {
|
|
847
|
+
const result = findWrapperInType(constituent, checker);
|
|
848
|
+
if (result) {
|
|
849
|
+
wrapperInfo = result;
|
|
850
|
+
}
|
|
851
|
+
if (!(constituent.flags & import_typescript6.default.TypeFlags.Object)) continue;
|
|
852
|
+
const symbol = constituent.getSymbol();
|
|
853
|
+
if (symbol?.getName() === "ReadonlyArray") {
|
|
854
|
+
hasReadonlyArray = true;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (wrapperInfo) {
|
|
858
|
+
return { ...wrapperInfo, isReadonlyArray: hasReadonlyArray };
|
|
859
|
+
}
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
return findWrapperInType(type, checker);
|
|
863
|
+
}
|
|
864
|
+
function findWrapperInType(type, checker) {
|
|
865
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
866
|
+
const symbol = type.getSymbol();
|
|
867
|
+
const effectiveSymbol = aliasSymbol && aliasSymbol.flags & import_typescript6.default.SymbolFlags.Alias ? checker.getAliasedSymbol(aliasSymbol) : symbol;
|
|
868
|
+
if (!effectiveSymbol) return null;
|
|
869
|
+
const name = effectiveSymbol.getName();
|
|
870
|
+
if (!METAL_ORM_WRAPPER_NAMES.includes(name)) return null;
|
|
871
|
+
const typeRef = type;
|
|
872
|
+
const typeArgs = typeRef.typeArguments || [];
|
|
873
|
+
return {
|
|
874
|
+
wrapperName: name,
|
|
875
|
+
targetTypeArgs: typeArgs,
|
|
876
|
+
isReadonlyArray: false
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function getWrapperTypeName(type, _checker) {
|
|
880
|
+
const symbol = type.getSymbol();
|
|
881
|
+
if (!symbol) return null;
|
|
882
|
+
const name = symbol.getName();
|
|
883
|
+
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
884
|
+
}
|
|
749
885
|
function handleMetalOrmWrapper(type, ctx) {
|
|
750
886
|
const typeRef = type;
|
|
751
887
|
const typeArgs = typeRef.typeArguments;
|
|
@@ -764,30 +900,138 @@ function handleMetalOrmWrapper(type, ctx) {
|
|
|
764
900
|
"x-metal-orm-rel": wrapperRel
|
|
765
901
|
};
|
|
766
902
|
}
|
|
767
|
-
|
|
903
|
+
if (!targetType) {
|
|
904
|
+
return { "x-metal-orm-rel": wrapperRel };
|
|
905
|
+
}
|
|
906
|
+
if (wrapperName === "BelongsToReference" || wrapperName === "HasOneReference") {
|
|
907
|
+
return handleBelongsToReference(targetType, ctx, wrapperRel);
|
|
908
|
+
}
|
|
909
|
+
const targetSchema = typeToJsonSchema(targetType, ctx);
|
|
768
910
|
return {
|
|
769
911
|
...targetSchema,
|
|
770
912
|
"x-metal-orm-rel": wrapperRel
|
|
771
913
|
};
|
|
772
914
|
}
|
|
915
|
+
function handleBelongsToReference(targetType, ctx, wrapperRel) {
|
|
916
|
+
const { components } = ctx;
|
|
917
|
+
const targetSymbol = targetType.getSymbol();
|
|
918
|
+
const typeName = targetSymbol?.getName();
|
|
919
|
+
if (!typeName) {
|
|
920
|
+
return {
|
|
921
|
+
type: "object",
|
|
922
|
+
properties: {},
|
|
923
|
+
"x-metal-orm-rel": wrapperRel
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
const refSchemaName = `${typeName}Ref`;
|
|
927
|
+
if (components.has(refSchemaName)) {
|
|
928
|
+
return {
|
|
929
|
+
$ref: `#/components/schemas/${refSchemaName}`,
|
|
930
|
+
"x-metal-orm-rel": wrapperRel
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
const refSchema = buildRefSchema(targetType, ctx);
|
|
934
|
+
components.set(refSchemaName, refSchema);
|
|
935
|
+
return {
|
|
936
|
+
$ref: `#/components/schemas/${refSchemaName}`,
|
|
937
|
+
"x-metal-orm-rel": wrapperRel
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function buildRefSchema(type, ctx) {
|
|
941
|
+
const { checker } = ctx;
|
|
942
|
+
if (!(type.flags & import_typescript6.default.TypeFlags.Object)) {
|
|
943
|
+
return { type: "object", properties: {} };
|
|
944
|
+
}
|
|
945
|
+
const objectType = type;
|
|
946
|
+
const properties = {};
|
|
947
|
+
const required = [];
|
|
948
|
+
const props = checker.getPropertiesOfType(objectType);
|
|
949
|
+
for (const prop of props) {
|
|
950
|
+
const propName = prop.getName();
|
|
951
|
+
if (isIteratorOrSymbolProperty(propName)) {
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
955
|
+
if (isMethodLike(propType)) {
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const isOptional = !!(prop.flags & import_typescript6.default.SymbolFlags.Optional);
|
|
959
|
+
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
960
|
+
if (isRelation) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
const propCtx = { ...ctx, propertyName: propName };
|
|
964
|
+
properties[propName] = typeToJsonSchema(propType, propCtx);
|
|
965
|
+
if (!isOptional) {
|
|
966
|
+
required.push(propName);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const schema = {
|
|
970
|
+
type: "object",
|
|
971
|
+
properties
|
|
972
|
+
};
|
|
973
|
+
if (required.length > 0) {
|
|
974
|
+
schema.required = required;
|
|
975
|
+
}
|
|
976
|
+
return schema;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/compiler/schema/typeToJsonSchema.ts
|
|
980
|
+
function typeToJsonSchema(type, ctx, typeNode) {
|
|
981
|
+
const primitiveResult = handlePrimitiveType(type, ctx, typeNode);
|
|
982
|
+
if (primitiveResult) {
|
|
983
|
+
return primitiveResult;
|
|
984
|
+
}
|
|
985
|
+
if (type.isUnion()) {
|
|
986
|
+
return handleUnion(type, ctx, typeNode);
|
|
987
|
+
}
|
|
988
|
+
if (type.isIntersection()) {
|
|
989
|
+
return handleIntersection(type, ctx, typeNode);
|
|
990
|
+
}
|
|
991
|
+
if (ctx.checker.isArrayType(type)) {
|
|
992
|
+
const typeArgs = type.typeArguments;
|
|
993
|
+
const itemType = typeArgs?.[0];
|
|
994
|
+
const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
|
|
995
|
+
return {
|
|
996
|
+
type: "array",
|
|
997
|
+
items,
|
|
998
|
+
uniqueItems: isSetType(type, ctx.checker) ? true : void 0
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
if (type.flags & import_typescript7.default.TypeFlags.Object) {
|
|
1002
|
+
const objectType = type;
|
|
1003
|
+
if (isMetalOrmWrapperType(type, ctx.checker)) {
|
|
1004
|
+
return handleMetalOrmWrapper(objectType, ctx);
|
|
1005
|
+
}
|
|
1006
|
+
return handleObjectType(objectType, ctx, typeNode);
|
|
1007
|
+
}
|
|
1008
|
+
return {};
|
|
1009
|
+
}
|
|
1010
|
+
function isSetType(type, _checker) {
|
|
1011
|
+
const symbol = type.getSymbol();
|
|
1012
|
+
if (!symbol) return false;
|
|
1013
|
+
const name = symbol.getName();
|
|
1014
|
+
if (name === "Set") return true;
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
773
1017
|
|
|
774
1018
|
// src/compiler/schema/extractAnnotations.ts
|
|
775
|
-
var
|
|
1019
|
+
var import_typescript8 = __toESM(require("typescript"), 1);
|
|
776
1020
|
function extractPropertySchemaFragments(checker, prop) {
|
|
777
|
-
if (!
|
|
778
|
-
const decs =
|
|
1021
|
+
if (!import_typescript8.default.canHaveDecorators(prop)) return [];
|
|
1022
|
+
const decs = import_typescript8.default.getDecorators(prop);
|
|
779
1023
|
if (!decs || decs.length === 0) return [];
|
|
780
1024
|
const frags = [];
|
|
781
1025
|
for (const d of decs) {
|
|
782
1026
|
const expr = d.expression;
|
|
783
1027
|
let callee;
|
|
784
1028
|
let args;
|
|
785
|
-
if (
|
|
1029
|
+
if (import_typescript8.default.isCallExpression(expr)) {
|
|
786
1030
|
callee = expr.expression;
|
|
787
1031
|
args = expr.arguments;
|
|
788
1032
|
} else {
|
|
789
1033
|
callee = expr;
|
|
790
|
-
args =
|
|
1034
|
+
args = import_typescript8.default.factory.createNodeArray([]);
|
|
791
1035
|
}
|
|
792
1036
|
const sym = checker.getSymbolAtLocation(callee);
|
|
793
1037
|
if (!sym) continue;
|
|
@@ -796,7 +1040,7 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
796
1040
|
const name = resolved.name;
|
|
797
1041
|
if (name === "Schema") {
|
|
798
1042
|
const obj = args[0];
|
|
799
|
-
if (obj &&
|
|
1043
|
+
if (obj && import_typescript8.default.isObjectLiteralExpression(obj)) {
|
|
800
1044
|
const frag = objectLiteralToJson(obj);
|
|
801
1045
|
if (frag) frags.push(frag);
|
|
802
1046
|
}
|
|
@@ -818,7 +1062,7 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
818
1062
|
frags.push({ format: args[0].text });
|
|
819
1063
|
} else if (name === "Pattern") {
|
|
820
1064
|
const arg = args[0];
|
|
821
|
-
if (arg &&
|
|
1065
|
+
if (arg && import_typescript8.default.isRegularExpressionLiteral(arg)) {
|
|
822
1066
|
frags.push({ pattern: extractRegexPattern(arg.text) });
|
|
823
1067
|
} else if (isStringLiteral(arg)) {
|
|
824
1068
|
frags.push({ pattern: arg.text });
|
|
@@ -835,11 +1079,11 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
835
1079
|
frags.push({ multipleOf: Number(args[0].text) });
|
|
836
1080
|
} else if (name === "Example") {
|
|
837
1081
|
frags.push({ example: literalToJson(args[0]) });
|
|
838
|
-
} else if (name === "Examples" &&
|
|
1082
|
+
} else if (name === "Examples" && import_typescript8.default.isArrayLiteralExpression(args[0])) {
|
|
839
1083
|
frags.push({ examples: args[0].elements.map((e) => literalToJson(e)) });
|
|
840
1084
|
} else if (name === "Description" && isStringLiteral(args[0])) {
|
|
841
1085
|
frags.push({ description: args[0].text });
|
|
842
|
-
} else if (name === "Enum" &&
|
|
1086
|
+
} else if (name === "Enum" && import_typescript8.default.isArrayLiteralExpression(args[0])) {
|
|
843
1087
|
frags.push({ enum: args[0].elements.map((e) => literalToJson(e)) });
|
|
844
1088
|
} else if (name === "Const") {
|
|
845
1089
|
frags.push({ const: literalToJson(args[0]) });
|
|
@@ -847,9 +1091,9 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
847
1091
|
frags.push({ default: literalToJson(args[0]) });
|
|
848
1092
|
} else if (name === "AdditionalProperties") {
|
|
849
1093
|
const arg = args[0];
|
|
850
|
-
if (arg && (arg.kind ===
|
|
851
|
-
frags.push({ additionalProperties: arg.kind ===
|
|
852
|
-
} else if (arg &&
|
|
1094
|
+
if (arg && (arg.kind === import_typescript8.default.SyntaxKind.FalseKeyword || arg.kind === import_typescript8.default.SyntaxKind.TrueKeyword)) {
|
|
1095
|
+
frags.push({ additionalProperties: arg.kind === import_typescript8.default.SyntaxKind.TrueKeyword });
|
|
1096
|
+
} else if (arg && import_typescript8.default.isObjectLiteralExpression(arg)) {
|
|
853
1097
|
const obj = objectLiteralToJson(arg);
|
|
854
1098
|
if (obj) frags.push({ additionalProperties: obj });
|
|
855
1099
|
}
|
|
@@ -862,7 +1106,7 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
862
1106
|
return frags;
|
|
863
1107
|
}
|
|
864
1108
|
function resolveImportedDecorator(checker, sym) {
|
|
865
|
-
const target = sym.flags &
|
|
1109
|
+
const target = sym.flags & import_typescript8.default.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
|
|
866
1110
|
const name = target.getName();
|
|
867
1111
|
const decl = target.declarations?.[0];
|
|
868
1112
|
if (!decl) return null;
|
|
@@ -873,10 +1117,10 @@ function resolveImportedDecorator(checker, sym) {
|
|
|
873
1117
|
return null;
|
|
874
1118
|
}
|
|
875
1119
|
function isNumberLiteral(node) {
|
|
876
|
-
return !!node &&
|
|
1120
|
+
return !!node && import_typescript8.default.isNumericLiteral(node);
|
|
877
1121
|
}
|
|
878
1122
|
function isStringLiteral(node) {
|
|
879
|
-
return !!node &&
|
|
1123
|
+
return !!node && import_typescript8.default.isStringLiteral(node);
|
|
880
1124
|
}
|
|
881
1125
|
function extractRegexPattern(text) {
|
|
882
1126
|
const match = text.match(/^\/(.+)\/[gimsuy]*$/);
|
|
@@ -885,12 +1129,12 @@ function extractRegexPattern(text) {
|
|
|
885
1129
|
function objectLiteralToJson(obj) {
|
|
886
1130
|
const out = {};
|
|
887
1131
|
for (const prop of obj.properties) {
|
|
888
|
-
if (!
|
|
1132
|
+
if (!import_typescript8.default.isPropertyAssignment(prop)) continue;
|
|
889
1133
|
const name = prop.name;
|
|
890
1134
|
let key;
|
|
891
|
-
if (
|
|
1135
|
+
if (import_typescript8.default.isIdentifier(name)) {
|
|
892
1136
|
key = name.text;
|
|
893
|
-
} else if (
|
|
1137
|
+
} else if (import_typescript8.default.isStringLiteral(name)) {
|
|
894
1138
|
key = name.text;
|
|
895
1139
|
} else {
|
|
896
1140
|
continue;
|
|
@@ -900,13 +1144,13 @@ function objectLiteralToJson(obj) {
|
|
|
900
1144
|
return out;
|
|
901
1145
|
}
|
|
902
1146
|
function literalToJson(node) {
|
|
903
|
-
if (
|
|
904
|
-
if (
|
|
905
|
-
if (node.kind ===
|
|
906
|
-
if (node.kind ===
|
|
907
|
-
if (node.kind ===
|
|
908
|
-
if (
|
|
909
|
-
if (
|
|
1147
|
+
if (import_typescript8.default.isStringLiteral(node)) return node.text;
|
|
1148
|
+
if (import_typescript8.default.isNumericLiteral(node)) return Number(node.text);
|
|
1149
|
+
if (node.kind === import_typescript8.default.SyntaxKind.TrueKeyword) return true;
|
|
1150
|
+
if (node.kind === import_typescript8.default.SyntaxKind.FalseKeyword) return false;
|
|
1151
|
+
if (node.kind === import_typescript8.default.SyntaxKind.NullKeyword) return null;
|
|
1152
|
+
if (import_typescript8.default.isObjectLiteralExpression(node)) return objectLiteralToJson(node);
|
|
1153
|
+
if (import_typescript8.default.isArrayLiteralExpression(node)) return node.elements.map((e) => literalToJson(e));
|
|
910
1154
|
return void 0;
|
|
911
1155
|
}
|
|
912
1156
|
function mergeFragments(base, ...frags) {
|
|
@@ -917,120 +1161,7 @@ function mergeFragments(base, ...frags) {
|
|
|
917
1161
|
return result;
|
|
918
1162
|
}
|
|
919
1163
|
|
|
920
|
-
// src/compiler/schema/
|
|
921
|
-
function generateOpenAPI(controllers, checker, options = {}) {
|
|
922
|
-
const components = /* @__PURE__ */ new Map();
|
|
923
|
-
const ctx = {
|
|
924
|
-
checker,
|
|
925
|
-
components,
|
|
926
|
-
typeStack: /* @__PURE__ */ new Set(),
|
|
927
|
-
typeNameStack: [],
|
|
928
|
-
mode: "response"
|
|
929
|
-
};
|
|
930
|
-
const paths = {};
|
|
931
|
-
for (const controller of controllers) {
|
|
932
|
-
for (const operation of controller.operations) {
|
|
933
|
-
const fullPath = convertToOpenApiPath(controller.basePath, operation.path);
|
|
934
|
-
if (!paths[fullPath]) {
|
|
935
|
-
paths[fullPath] = {};
|
|
936
|
-
}
|
|
937
|
-
const method = operation.httpMethod.toLowerCase();
|
|
938
|
-
paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
return {
|
|
942
|
-
openapi: "3.1.0",
|
|
943
|
-
info: {
|
|
944
|
-
title: options.title ?? "API",
|
|
945
|
-
version: options.version ?? "1.0.0"
|
|
946
|
-
},
|
|
947
|
-
components: {
|
|
948
|
-
schemas: Object.fromEntries(components)
|
|
949
|
-
},
|
|
950
|
-
paths
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
function convertToOpenApiPath(basePath, path4) {
|
|
954
|
-
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
955
|
-
const converted = path4.replace(/:([^/]+)/g, "{$1}");
|
|
956
|
-
let fullPath = base + converted || "/";
|
|
957
|
-
if (fullPath.endsWith("/") && fullPath !== "/") {
|
|
958
|
-
fullPath = fullPath.slice(0, -1);
|
|
959
|
-
}
|
|
960
|
-
return fullPath;
|
|
961
|
-
}
|
|
962
|
-
function buildOperation(operation, ctx, controllerConsumes) {
|
|
963
|
-
const op = {
|
|
964
|
-
operationId: operation.operationId,
|
|
965
|
-
responses: {}
|
|
966
|
-
};
|
|
967
|
-
const parameters = [];
|
|
968
|
-
buildPathParameters(operation, ctx, parameters);
|
|
969
|
-
buildQueryParameters(operation, ctx, parameters);
|
|
970
|
-
buildHeaderParameters(operation, ctx, parameters);
|
|
971
|
-
buildCookieParameters(operation, ctx, parameters);
|
|
972
|
-
if (parameters.length > 0) {
|
|
973
|
-
op.parameters = parameters;
|
|
974
|
-
}
|
|
975
|
-
const responseCtx = { ...ctx, mode: "response" };
|
|
976
|
-
const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
977
|
-
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
978
|
-
op.responses[status] = {
|
|
979
|
-
description: status === 201 ? "Created" : "OK",
|
|
980
|
-
content: {
|
|
981
|
-
"application/json": {
|
|
982
|
-
schema: responseSchema
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
};
|
|
986
|
-
if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
|
|
987
|
-
const bodyParam = operation.parameters[operation.bodyParamIndex];
|
|
988
|
-
if (bodyParam) {
|
|
989
|
-
const requestCtx = { ...ctx, mode: "request" };
|
|
990
|
-
let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
|
|
991
|
-
bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
|
|
992
|
-
const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
|
|
993
|
-
const requestBody = {
|
|
994
|
-
required: !bodyParam.isOptional,
|
|
995
|
-
content: {}
|
|
996
|
-
};
|
|
997
|
-
if (contentType === "multipart/form-data") {
|
|
998
|
-
requestBody.content["multipart/form-data"] = {
|
|
999
|
-
schema: bodySchema
|
|
1000
|
-
};
|
|
1001
|
-
} else {
|
|
1002
|
-
requestBody.content[contentType] = {
|
|
1003
|
-
schema: bodySchema
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
op.requestBody = requestBody;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return op;
|
|
1010
|
-
}
|
|
1011
|
-
function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
1012
|
-
if (!schema.properties) return schema;
|
|
1013
|
-
const typeSymbol = bodyParam.type.getSymbol();
|
|
1014
|
-
if (!typeSymbol) return schema;
|
|
1015
|
-
const declarations = typeSymbol.getDeclarations();
|
|
1016
|
-
if (!declarations || declarations.length === 0) return schema;
|
|
1017
|
-
const classDecl = declarations[0];
|
|
1018
|
-
if (!import_typescript5.default.isClassDeclaration(classDecl)) return schema;
|
|
1019
|
-
const result = { ...schema };
|
|
1020
|
-
const props = { ...result.properties };
|
|
1021
|
-
for (const member of classDecl.members) {
|
|
1022
|
-
if (!import_typescript5.default.isPropertyDeclaration(member) || !member.name) continue;
|
|
1023
|
-
const propName = import_typescript5.default.isIdentifier(member.name) ? member.name.text : null;
|
|
1024
|
-
if (!propName) continue;
|
|
1025
|
-
if (!props[propName]) continue;
|
|
1026
|
-
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
1027
|
-
if (frags.length > 0) {
|
|
1028
|
-
props[propName] = mergeFragments(props[propName], ...frags);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
result.properties = props;
|
|
1032
|
-
return result;
|
|
1033
|
-
}
|
|
1164
|
+
// src/compiler/schema/parameters.ts
|
|
1034
1165
|
function buildPathParameters(operation, ctx, parameters) {
|
|
1035
1166
|
for (const paramIndex of operation.pathParamIndices) {
|
|
1036
1167
|
const param = operation.parameters[paramIndex];
|
|
@@ -1043,71 +1174,22 @@ function buildPathParameters(operation, ctx, parameters) {
|
|
|
1043
1174
|
}
|
|
1044
1175
|
}
|
|
1045
1176
|
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
schema
|
|
1051
|
-
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
function isObjectLikeSchema(schema, ctx) {
|
|
1056
|
-
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1057
|
-
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1058
|
-
return true;
|
|
1059
|
-
}
|
|
1060
|
-
if (resolved.allOf) {
|
|
1061
|
-
for (const branch of resolved.allOf) {
|
|
1062
|
-
if (isObjectLikeSchema(branch, ctx)) {
|
|
1063
|
-
return true;
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
if (resolved.type === "array" && resolved.items) {
|
|
1068
|
-
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1069
|
-
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1070
|
-
}
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
function resolveSchemaRef(schema, components) {
|
|
1074
|
-
const ref = schema.$ref;
|
|
1075
|
-
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1076
|
-
return schema;
|
|
1077
|
-
}
|
|
1078
|
-
const name = ref.replace("#/components/schemas/", "");
|
|
1079
|
-
const next = components.get(name);
|
|
1080
|
-
if (!next) return schema;
|
|
1081
|
-
return resolveSchemaRef(next, components);
|
|
1082
|
-
}
|
|
1083
|
-
function resolveAndCollectObjectProps(schema, components) {
|
|
1084
|
-
const resolved = resolveSchemaRef(schema, components);
|
|
1085
|
-
const properties = {};
|
|
1086
|
-
const required = [];
|
|
1087
|
-
const processSchema = (s) => {
|
|
1088
|
-
const current = resolveSchemaRef(s, components);
|
|
1089
|
-
if (current.properties) {
|
|
1090
|
-
for (const [key, val] of Object.entries(current.properties)) {
|
|
1091
|
-
if (!properties[key]) {
|
|
1092
|
-
properties[key] = val;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
if (current.required) {
|
|
1097
|
-
for (const req of current.required) {
|
|
1098
|
-
if (!required.includes(req)) {
|
|
1099
|
-
required.push(req);
|
|
1177
|
+
const paramName = param.name.toLowerCase();
|
|
1178
|
+
const isIdParam = paramName === "id" || paramName.endsWith("id");
|
|
1179
|
+
if (!schema.$ref && schema.type === "number" && isIdParam) {
|
|
1180
|
+
schema.type = "integer";
|
|
1181
|
+
if (!schema.minimum) {
|
|
1182
|
+
schema.minimum = 1;
|
|
1100
1183
|
}
|
|
1101
1184
|
}
|
|
1185
|
+
parameters.push({
|
|
1186
|
+
name: param.name,
|
|
1187
|
+
in: "path",
|
|
1188
|
+
required: !param.isOptional,
|
|
1189
|
+
schema
|
|
1190
|
+
});
|
|
1102
1191
|
}
|
|
1103
|
-
|
|
1104
|
-
for (const branch of current.allOf) {
|
|
1105
|
-
processSchema(branch);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
};
|
|
1109
|
-
processSchema(resolved);
|
|
1110
|
-
return { properties, required };
|
|
1192
|
+
}
|
|
1111
1193
|
}
|
|
1112
1194
|
function buildQueryParameters(operation, ctx, parameters) {
|
|
1113
1195
|
if (operation.queryObjectParamIndex !== null) {
|
|
@@ -1116,20 +1198,22 @@ function buildQueryParameters(operation, ctx, parameters) {
|
|
|
1116
1198
|
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1117
1199
|
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
|
|
1118
1200
|
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1119
|
-
const isRequired = queryRequired.includes(propName)
|
|
1201
|
+
const isRequired = queryRequired.includes(propName);
|
|
1120
1202
|
const isObjectLike = isObjectLikeSchema(propSchema, ctx);
|
|
1121
1203
|
const serialization = determineQuerySerialization(propSchema.type);
|
|
1204
|
+
const exampleValue = generateExampleValue(propSchema, propName);
|
|
1122
1205
|
if (isObjectLike) {
|
|
1206
|
+
const schemaRef = propSchema.$ref || "#/components/schemas/InlineQueryParam";
|
|
1123
1207
|
parameters.push({
|
|
1124
1208
|
name: propName,
|
|
1125
1209
|
in: "query",
|
|
1126
1210
|
required: isRequired,
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
}
|
|
1211
|
+
schema: { type: "string" },
|
|
1212
|
+
description: `JSON-encoded object. ${exampleValue}`,
|
|
1213
|
+
examples: {
|
|
1214
|
+
default: { value: parseExampleValue(exampleValue) }
|
|
1131
1215
|
},
|
|
1132
|
-
|
|
1216
|
+
"x-adorn-jsonSchemaRef": schemaRef
|
|
1133
1217
|
});
|
|
1134
1218
|
} else {
|
|
1135
1219
|
const paramDef = {
|
|
@@ -1175,15 +1259,18 @@ function buildQueryParameters(operation, ctx, parameters) {
|
|
|
1175
1259
|
}
|
|
1176
1260
|
const isObjectLike = isObjectLikeSchema(paramSchema, ctx);
|
|
1177
1261
|
if (isObjectLike) {
|
|
1262
|
+
const schemaRef = paramSchema.$ref || "#/components/schemas/InlineQueryParam";
|
|
1263
|
+
const exampleValue = generateExampleValue(paramSchema, param.name);
|
|
1178
1264
|
parameters.push({
|
|
1179
1265
|
name: param.name,
|
|
1180
1266
|
in: "query",
|
|
1181
1267
|
required: !param.isOptional,
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1268
|
+
schema: { type: "string" },
|
|
1269
|
+
description: `JSON-encoded object. ${exampleValue}`,
|
|
1270
|
+
examples: {
|
|
1271
|
+
default: { value: parseExampleValue(exampleValue) }
|
|
1272
|
+
},
|
|
1273
|
+
"x-adorn-jsonSchemaRef": schemaRef
|
|
1187
1274
|
});
|
|
1188
1275
|
} else {
|
|
1189
1276
|
const serialization = determineQuerySerialization(paramSchema.type);
|
|
@@ -1198,14 +1285,6 @@ function buildQueryParameters(operation, ctx, parameters) {
|
|
|
1198
1285
|
}
|
|
1199
1286
|
}
|
|
1200
1287
|
}
|
|
1201
|
-
function determineQuerySerialization(schemaType) {
|
|
1202
|
-
const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
1203
|
-
const isArray = typeArray.includes("array");
|
|
1204
|
-
if (isArray) {
|
|
1205
|
-
return { style: "form", explode: true };
|
|
1206
|
-
}
|
|
1207
|
-
return {};
|
|
1208
|
-
}
|
|
1209
1288
|
function buildHeaderParameters(operation, ctx, parameters) {
|
|
1210
1289
|
if (operation.headerObjectParamIndex === null) return;
|
|
1211
1290
|
const headerParam = operation.parameters[operation.headerObjectParamIndex];
|
|
@@ -1242,9 +1321,324 @@ function buildCookieParameters(operation, ctx, parameters) {
|
|
|
1242
1321
|
});
|
|
1243
1322
|
}
|
|
1244
1323
|
}
|
|
1324
|
+
function determineQuerySerialization(schemaType) {
|
|
1325
|
+
const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
1326
|
+
const isArray = typeArray.includes("array");
|
|
1327
|
+
if (isArray) {
|
|
1328
|
+
return { style: "form", explode: true };
|
|
1329
|
+
}
|
|
1330
|
+
return {};
|
|
1331
|
+
}
|
|
1332
|
+
function generateExampleValue(schema, propName) {
|
|
1333
|
+
const resolved = resolveSchemaRef(schema, /* @__PURE__ */ new Map());
|
|
1334
|
+
if (resolved.type === "object" && resolved.properties) {
|
|
1335
|
+
const example = {};
|
|
1336
|
+
for (const [key, prop] of Object.entries(resolved.properties)) {
|
|
1337
|
+
const propResolved = resolveSchemaRef(prop, /* @__PURE__ */ new Map());
|
|
1338
|
+
if (propResolved.type === "string") {
|
|
1339
|
+
example[key] = "value";
|
|
1340
|
+
} else if (propResolved.type === "number" || propResolved.type === "integer") {
|
|
1341
|
+
example[key] = 1;
|
|
1342
|
+
} else if (propResolved.type === "boolean") {
|
|
1343
|
+
example[key] = true;
|
|
1344
|
+
} else if (Array.isArray(propResolved.type) && propResolved.type.includes("null")) {
|
|
1345
|
+
example[key] = null;
|
|
1346
|
+
} else if (propResolved.enum) {
|
|
1347
|
+
example[key] = propResolved.enum[0];
|
|
1348
|
+
} else {
|
|
1349
|
+
example[key] = "value";
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return `Example: ${propName}=${JSON.stringify(example)}`;
|
|
1353
|
+
}
|
|
1354
|
+
return `Example: ${propName}=${JSON.stringify({ key: "value" })}`;
|
|
1355
|
+
}
|
|
1356
|
+
function parseExampleValue(description) {
|
|
1357
|
+
const match = description.match(/Example:\s*\w+=(\{[^}]+\})/);
|
|
1358
|
+
if (match) {
|
|
1359
|
+
return match[1];
|
|
1360
|
+
}
|
|
1361
|
+
return JSON.stringify({ key: "value" });
|
|
1362
|
+
}
|
|
1363
|
+
function isObjectLikeSchema(schema, ctx) {
|
|
1364
|
+
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1365
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
if (resolved.allOf) {
|
|
1369
|
+
for (const branch of resolved.allOf) {
|
|
1370
|
+
if (isObjectLikeSchema(branch, ctx)) {
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (resolved.type === "array" && resolved.items) {
|
|
1376
|
+
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1377
|
+
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1378
|
+
}
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
function resolveSchemaRef(schema, components) {
|
|
1382
|
+
const ref = schema.$ref;
|
|
1383
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1384
|
+
return schema;
|
|
1385
|
+
}
|
|
1386
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
1387
|
+
const next = components.get(name);
|
|
1388
|
+
if (!next) return schema;
|
|
1389
|
+
return resolveSchemaRef(next, components);
|
|
1390
|
+
}
|
|
1391
|
+
function resolveAndCollectObjectProps(schema, components) {
|
|
1392
|
+
const resolved = resolveSchemaRef(schema, components);
|
|
1393
|
+
const properties = {};
|
|
1394
|
+
const required = [];
|
|
1395
|
+
const processSchema = (s) => {
|
|
1396
|
+
const current = resolveSchemaRef(s, components);
|
|
1397
|
+
if (current.properties) {
|
|
1398
|
+
for (const [key, val] of Object.entries(current.properties)) {
|
|
1399
|
+
if (!properties[key]) {
|
|
1400
|
+
properties[key] = val;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (current.required) {
|
|
1405
|
+
for (const req of current.required) {
|
|
1406
|
+
if (!required.includes(req)) {
|
|
1407
|
+
required.push(req);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (current.allOf) {
|
|
1412
|
+
for (const branch of current.allOf) {
|
|
1413
|
+
processSchema(branch);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
processSchema(resolved);
|
|
1418
|
+
return { properties, required };
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/compiler/schema/openapi.ts
|
|
1422
|
+
var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
|
|
1423
|
+
function generateOpenAPI(controllers, checker, options = {}) {
|
|
1424
|
+
const components = /* @__PURE__ */ new Map();
|
|
1425
|
+
const ctx = {
|
|
1426
|
+
checker,
|
|
1427
|
+
components,
|
|
1428
|
+
typeStack: /* @__PURE__ */ new Set(),
|
|
1429
|
+
typeNameStack: [],
|
|
1430
|
+
mode: "response"
|
|
1431
|
+
};
|
|
1432
|
+
const paths = {};
|
|
1433
|
+
for (const controller of controllers) {
|
|
1434
|
+
for (const operation of controller.operations) {
|
|
1435
|
+
const fullPath = convertToOpenApiPath(controller.basePath, operation.path);
|
|
1436
|
+
if (!paths[fullPath]) {
|
|
1437
|
+
paths[fullPath] = {};
|
|
1438
|
+
}
|
|
1439
|
+
const method = operation.httpMethod.toLowerCase();
|
|
1440
|
+
paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const schemas = Object.fromEntries(components);
|
|
1444
|
+
cleanupMetalOrmWrappers(schemas, paths);
|
|
1445
|
+
return {
|
|
1446
|
+
openapi: "3.1.0",
|
|
1447
|
+
info: {
|
|
1448
|
+
title: options.title ?? "API",
|
|
1449
|
+
version: options.version ?? "1.0.0"
|
|
1450
|
+
},
|
|
1451
|
+
components: {
|
|
1452
|
+
schemas
|
|
1453
|
+
},
|
|
1454
|
+
paths
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function cleanupMetalOrmWrappers(schemas, paths) {
|
|
1458
|
+
const schemasToDelete = /* @__PURE__ */ new Set();
|
|
1459
|
+
for (const wrapperName of METAL_ORM_WRAPPER_NAMES2) {
|
|
1460
|
+
if (schemas[wrapperName]) {
|
|
1461
|
+
schemasToDelete.add(wrapperName);
|
|
1462
|
+
}
|
|
1463
|
+
if (schemas[`${wrapperName}Api`]) {
|
|
1464
|
+
schemasToDelete.add(`${wrapperName}Api`);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
for (const schema of Object.values(schemas)) {
|
|
1468
|
+
cleanupSchemaRefs(schema, schemasToDelete);
|
|
1469
|
+
}
|
|
1470
|
+
for (const pathItem of Object.values(paths)) {
|
|
1471
|
+
cleanupPathItemRefs(pathItem, schemasToDelete);
|
|
1472
|
+
}
|
|
1473
|
+
for (const schemaName of schemasToDelete) {
|
|
1474
|
+
delete schemas[schemaName];
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function cleanupSchemaRefs(schema, schemasToDelete) {
|
|
1478
|
+
if (typeof schema !== "object" || schema === null) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (schema.properties) {
|
|
1482
|
+
for (const propName of Object.keys(schema.properties)) {
|
|
1483
|
+
const propSchema = schema.properties[propName];
|
|
1484
|
+
if (propSchema.$ref && typeof propSchema.$ref === "string") {
|
|
1485
|
+
const refName = propSchema.$ref.replace("#/components/schemas/", "");
|
|
1486
|
+
if (schemasToDelete.has(refName)) {
|
|
1487
|
+
delete schema.properties[propName];
|
|
1488
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
1489
|
+
schema.required = schema.required.filter((r) => r !== propName);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
} else {
|
|
1493
|
+
cleanupSchemaRefs(propSchema, schemasToDelete);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
if (schema.items) {
|
|
1498
|
+
cleanupSchemaRefs(schema.items, schemasToDelete);
|
|
1499
|
+
}
|
|
1500
|
+
if (schema.allOf) {
|
|
1501
|
+
for (const item of schema.allOf) {
|
|
1502
|
+
cleanupSchemaRefs(item, schemasToDelete);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
function cleanupPathItemRefs(pathItem, schemasToDelete) {
|
|
1507
|
+
if (typeof pathItem !== "object" || pathItem === null) {
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
for (const method of Object.keys(pathItem)) {
|
|
1511
|
+
const operation = pathItem[method];
|
|
1512
|
+
if (typeof operation !== "object" || operation === null) continue;
|
|
1513
|
+
if (operation.requestBody) {
|
|
1514
|
+
cleanupRequestBodyRefs(operation.requestBody, schemasToDelete);
|
|
1515
|
+
}
|
|
1516
|
+
if (operation.responses) {
|
|
1517
|
+
const responses = Object.values(operation.responses);
|
|
1518
|
+
for (const response of responses) {
|
|
1519
|
+
if (response.content) {
|
|
1520
|
+
const contentTypes = Object.values(response.content);
|
|
1521
|
+
for (const contentType of contentTypes) {
|
|
1522
|
+
if (contentType.schema) {
|
|
1523
|
+
cleanupSchemaRefs(contentType.schema, schemasToDelete);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
function cleanupRequestBodyRefs(requestBody, schemasToDelete) {
|
|
1532
|
+
if (typeof requestBody !== "object" || requestBody === null) return;
|
|
1533
|
+
if (requestBody.content) {
|
|
1534
|
+
const contentTypes = Object.values(requestBody.content);
|
|
1535
|
+
for (const contentType of contentTypes) {
|
|
1536
|
+
if (contentType.schema) {
|
|
1537
|
+
cleanupSchemaRefs(contentType.schema, schemasToDelete);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (requestBody.properties) {
|
|
1542
|
+
for (const propName of Object.keys(requestBody.properties)) {
|
|
1543
|
+
const propSchema = requestBody.properties[propName];
|
|
1544
|
+
if (propSchema.$ref && typeof propSchema.$ref === "string") {
|
|
1545
|
+
const refName = propSchema.$ref.replace("#/components/schemas/", "");
|
|
1546
|
+
if (schemasToDelete.has(refName)) {
|
|
1547
|
+
delete requestBody.properties[propName];
|
|
1548
|
+
if (requestBody.required && Array.isArray(requestBody.required)) {
|
|
1549
|
+
requestBody.required = requestBody.required.filter((r) => r !== propName);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
} else {
|
|
1553
|
+
cleanupSchemaRefs(propSchema, schemasToDelete);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function convertToOpenApiPath(basePath, path4) {
|
|
1559
|
+
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
1560
|
+
const converted = path4.replace(/:([^/]+)/g, "{$1}");
|
|
1561
|
+
let fullPath = base + converted || "/";
|
|
1562
|
+
if (fullPath.endsWith("/") && fullPath !== "/") {
|
|
1563
|
+
fullPath = fullPath.slice(0, -1);
|
|
1564
|
+
}
|
|
1565
|
+
return fullPath;
|
|
1566
|
+
}
|
|
1567
|
+
function buildOperation(operation, ctx, controllerConsumes) {
|
|
1568
|
+
const op = {
|
|
1569
|
+
operationId: operation.operationId,
|
|
1570
|
+
responses: {}
|
|
1571
|
+
};
|
|
1572
|
+
const parameters = [];
|
|
1573
|
+
buildPathParameters(operation, ctx, parameters);
|
|
1574
|
+
buildQueryParameters(operation, ctx, parameters);
|
|
1575
|
+
buildHeaderParameters(operation, ctx, parameters);
|
|
1576
|
+
buildCookieParameters(operation, ctx, parameters);
|
|
1577
|
+
if (parameters.length > 0) {
|
|
1578
|
+
op.parameters = parameters;
|
|
1579
|
+
}
|
|
1580
|
+
const responseCtx = { ...ctx, mode: "response" };
|
|
1581
|
+
const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
1582
|
+
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
1583
|
+
op.responses[status] = {
|
|
1584
|
+
description: status === 201 ? "Created" : "OK",
|
|
1585
|
+
content: {
|
|
1586
|
+
"application/json": {
|
|
1587
|
+
schema: responseSchema
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
|
|
1592
|
+
const bodyParam = operation.parameters[operation.bodyParamIndex];
|
|
1593
|
+
if (bodyParam) {
|
|
1594
|
+
const requestCtx = { ...ctx, mode: "request" };
|
|
1595
|
+
let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
|
|
1596
|
+
bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
|
|
1597
|
+
const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
|
|
1598
|
+
const requestBody = {
|
|
1599
|
+
required: !bodyParam.isOptional,
|
|
1600
|
+
content: {}
|
|
1601
|
+
};
|
|
1602
|
+
if (contentType === "multipart/form-data") {
|
|
1603
|
+
requestBody.content["multipart/form-data"] = {
|
|
1604
|
+
schema: bodySchema
|
|
1605
|
+
};
|
|
1606
|
+
} else {
|
|
1607
|
+
requestBody.content[contentType] = {
|
|
1608
|
+
schema: bodySchema
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
op.requestBody = requestBody;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return op;
|
|
1615
|
+
}
|
|
1616
|
+
function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
1617
|
+
if (!schema.properties) return schema;
|
|
1618
|
+
const typeSymbol = bodyParam.type.getSymbol();
|
|
1619
|
+
if (!typeSymbol) return schema;
|
|
1620
|
+
const declarations = typeSymbol.getDeclarations();
|
|
1621
|
+
if (!declarations || declarations.length === 0) return schema;
|
|
1622
|
+
const classDecl = declarations[0];
|
|
1623
|
+
if (!import_typescript9.default.isClassDeclaration(classDecl)) return schema;
|
|
1624
|
+
const result = { ...schema };
|
|
1625
|
+
const props = { ...result.properties };
|
|
1626
|
+
for (const member of classDecl.members) {
|
|
1627
|
+
if (!import_typescript9.default.isPropertyDeclaration(member) || !member.name) continue;
|
|
1628
|
+
const propName = import_typescript9.default.isIdentifier(member.name) ? member.name.text : null;
|
|
1629
|
+
if (!propName) continue;
|
|
1630
|
+
if (!props[propName]) continue;
|
|
1631
|
+
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
1632
|
+
if (frags.length > 0) {
|
|
1633
|
+
props[propName] = mergeFragments(props[propName], ...frags);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
result.properties = props;
|
|
1637
|
+
return result;
|
|
1638
|
+
}
|
|
1245
1639
|
|
|
1246
1640
|
// src/compiler/manifest/emit.ts
|
|
1247
|
-
var
|
|
1641
|
+
var import_typescript10 = __toESM(require("typescript"), 1);
|
|
1248
1642
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
1249
1643
|
const components = /* @__PURE__ */ new Map();
|
|
1250
1644
|
const ctx = {
|
|
@@ -1266,7 +1660,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1266
1660
|
generator: {
|
|
1267
1661
|
name: "adorn-api",
|
|
1268
1662
|
version,
|
|
1269
|
-
typescript:
|
|
1663
|
+
typescript: import_typescript10.default.version
|
|
1270
1664
|
},
|
|
1271
1665
|
schemas: {
|
|
1272
1666
|
kind: "openapi-3.1",
|
|
@@ -1420,7 +1814,7 @@ function buildQueryArgs(op, ctx, args) {
|
|
|
1420
1814
|
args.query.push({
|
|
1421
1815
|
name: propName,
|
|
1422
1816
|
index: queryParam.index,
|
|
1423
|
-
required:
|
|
1817
|
+
required: isRequired,
|
|
1424
1818
|
schemaRef,
|
|
1425
1819
|
schemaType: propSchema.type,
|
|
1426
1820
|
content: isObjectLike ? "application/json" : void 0
|
|
@@ -1459,7 +1853,7 @@ function buildHeaderArgs(op, ctx, args) {
|
|
|
1459
1853
|
args.headers.push({
|
|
1460
1854
|
name: propName,
|
|
1461
1855
|
index: headerParam.index,
|
|
1462
|
-
required:
|
|
1856
|
+
required: isRequired,
|
|
1463
1857
|
schemaRef,
|
|
1464
1858
|
schemaType: propSchema.type
|
|
1465
1859
|
});
|
|
@@ -1480,7 +1874,7 @@ function buildCookieArgs(op, ctx, args) {
|
|
|
1480
1874
|
args.cookies.push({
|
|
1481
1875
|
name: propName,
|
|
1482
1876
|
index: cookieParam.index,
|
|
1483
|
-
required:
|
|
1877
|
+
required: isRequired,
|
|
1484
1878
|
schemaRef,
|
|
1485
1879
|
schemaType: propSchema.type,
|
|
1486
1880
|
serialization: { style: "form", explode: true }
|
|
@@ -1496,7 +1890,7 @@ var import_ajv = __toESM(require("ajv"), 1);
|
|
|
1496
1890
|
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
|
1497
1891
|
var OAS_SCHEMA_ONLY = /* @__PURE__ */ new Set(["discriminator", "xml", "externalDocs", "example"]);
|
|
1498
1892
|
function sanitizeSchemaForAjv(schema) {
|
|
1499
|
-
if (schema
|
|
1893
|
+
if (schema === null || typeof schema !== "object") return schema;
|
|
1500
1894
|
if (Array.isArray(schema)) return schema.map(sanitizeSchemaForAjv);
|
|
1501
1895
|
const out = {};
|
|
1502
1896
|
for (const [k, v] of Object.entries(schema)) {
|
|
@@ -1507,7 +1901,7 @@ function sanitizeSchemaForAjv(schema) {
|
|
|
1507
1901
|
return out;
|
|
1508
1902
|
}
|
|
1509
1903
|
function rewriteComponentRefs(schema) {
|
|
1510
|
-
if (schema
|
|
1904
|
+
if (schema === null || typeof schema !== "object") return schema;
|
|
1511
1905
|
if (Array.isArray(schema)) return schema.map(rewriteComponentRefs);
|
|
1512
1906
|
if (typeof schema.$ref === "string") {
|
|
1513
1907
|
const ref = schema.$ref;
|
|
@@ -1584,16 +1978,13 @@ async function emitPrecompiledValidators(opts) {
|
|
|
1584
1978
|
`;
|
|
1585
1979
|
cjs += ` body: ${v.body ? `exports[${JSON.stringify(v.body)}]` : "undefined"},
|
|
1586
1980
|
`;
|
|
1587
|
-
cjs +=
|
|
1588
|
-
`;
|
|
1981
|
+
cjs += " response: {\n";
|
|
1589
1982
|
for (const [key, id] of Object.entries(v.response)) {
|
|
1590
1983
|
cjs += ` ${JSON.stringify(key)}: exports[${JSON.stringify(id)}],
|
|
1591
1984
|
`;
|
|
1592
1985
|
}
|
|
1593
|
-
cjs +=
|
|
1594
|
-
|
|
1595
|
-
cjs += ` },
|
|
1596
|
-
`;
|
|
1986
|
+
cjs += " }\n";
|
|
1987
|
+
cjs += " },\n";
|
|
1597
1988
|
}
|
|
1598
1989
|
cjs += "};\n";
|
|
1599
1990
|
import_node_fs2.default.writeFileSync(cjsPath, cjs, "utf8");
|
|
@@ -1628,7 +2019,6 @@ export function validateResponse(operationId, status, contentType, data) {
|
|
|
1628
2019
|
// src/compiler/cache/isStale.ts
|
|
1629
2020
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
1630
2021
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
1631
|
-
var import_typescript7 = require("typescript");
|
|
1632
2022
|
var import_meta = {};
|
|
1633
2023
|
function readJson(p) {
|
|
1634
2024
|
try {
|
|
@@ -1686,7 +2076,7 @@ function findLockfile(startDir) {
|
|
|
1686
2076
|
for (const n of names) {
|
|
1687
2077
|
const p = import_node_path3.default.join(dir, n);
|
|
1688
2078
|
const mt = statMtimeMs(p);
|
|
1689
|
-
if (mt
|
|
2079
|
+
if (mt !== null) return { path: p, mtimeMs: mt };
|
|
1690
2080
|
}
|
|
1691
2081
|
const parent = import_node_path3.default.dirname(dir);
|
|
1692
2082
|
if (parent === dir) break;
|
|
@@ -1711,22 +2101,22 @@ async function isStale(params) {
|
|
|
1711
2101
|
const chain = collectTsconfigChain(tsconfigAbs);
|
|
1712
2102
|
for (const cfg of chain) {
|
|
1713
2103
|
const mt = statMtimeMs(cfg);
|
|
1714
|
-
if (mt
|
|
2104
|
+
if (mt === null) return { stale: true, reason: "config-missing", detail: cfg };
|
|
1715
2105
|
const cachedMt = cache.project.configFiles[cfg];
|
|
1716
|
-
if (cachedMt
|
|
2106
|
+
if (cachedMt === null || Math.abs(cachedMt - mt) > 1e-4) {
|
|
1717
2107
|
return { stale: true, reason: "config-updated", detail: cfg };
|
|
1718
2108
|
}
|
|
1719
2109
|
}
|
|
1720
2110
|
if (cache.project.lockfile?.path) {
|
|
1721
2111
|
const mt = statMtimeMs(cache.project.lockfile.path);
|
|
1722
|
-
if (mt
|
|
2112
|
+
if (mt === null) return { stale: true, reason: "lockfile-missing", detail: cache.project.lockfile.path };
|
|
1723
2113
|
if (Math.abs(cache.project.lockfile.mtimeMs - mt) > 1e-4) {
|
|
1724
2114
|
return { stale: true, reason: "lockfile-updated", detail: cache.project.lockfile.path };
|
|
1725
2115
|
}
|
|
1726
2116
|
}
|
|
1727
2117
|
for (const [file, cachedMt] of Object.entries(cache.inputs)) {
|
|
1728
2118
|
const mt = statMtimeMs(file);
|
|
1729
|
-
if (mt
|
|
2119
|
+
if (mt === null) return { stale: true, reason: "input-missing", detail: file };
|
|
1730
2120
|
if (Math.abs(cachedMt - mt) > 1e-4) return { stale: true, reason: "input-updated", detail: file };
|
|
1731
2121
|
}
|
|
1732
2122
|
return { stale: false, reason: "up-to-date" };
|
|
@@ -1735,7 +2125,7 @@ async function isStale(params) {
|
|
|
1735
2125
|
// src/compiler/cache/writeCache.ts
|
|
1736
2126
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
1737
2127
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
1738
|
-
var
|
|
2128
|
+
var import_typescript11 = __toESM(require("typescript"), 1);
|
|
1739
2129
|
function statMtimeMs2(p) {
|
|
1740
2130
|
return import_node_fs4.default.statSync(p).mtimeMs;
|
|
1741
2131
|
}
|
|
@@ -1768,7 +2158,7 @@ function writeCache(params) {
|
|
|
1768
2158
|
generator: {
|
|
1769
2159
|
name: "adorn-api",
|
|
1770
2160
|
version: params.adornVersion,
|
|
1771
|
-
typescript:
|
|
2161
|
+
typescript: import_typescript11.default.version
|
|
1772
2162
|
},
|
|
1773
2163
|
project: {
|
|
1774
2164
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -1781,7 +2171,7 @@ function writeCache(params) {
|
|
|
1781
2171
|
}
|
|
1782
2172
|
|
|
1783
2173
|
// src/cli.ts
|
|
1784
|
-
var
|
|
2174
|
+
var import_typescript12 = __toESM(require("typescript"), 1);
|
|
1785
2175
|
var import_node_process = __toESM(require("process"), 1);
|
|
1786
2176
|
var ADORN_VERSION = "0.1.0";
|
|
1787
2177
|
function log(msg) {
|
|
@@ -1829,7 +2219,7 @@ async function buildCommand(args) {
|
|
|
1829
2219
|
outDir: outputDir,
|
|
1830
2220
|
project: projectPath,
|
|
1831
2221
|
adornVersion: ADORN_VERSION,
|
|
1832
|
-
typescriptVersion:
|
|
2222
|
+
typescriptVersion: import_typescript12.default.version
|
|
1833
2223
|
});
|
|
1834
2224
|
if (!stale.stale) {
|
|
1835
2225
|
log("adorn-api: artifacts up-to-date");
|