adorn-api 1.0.12 → 1.0.14
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/auth.d.ts +8 -0
- package/dist/adapter/express/auth.d.ts.map +1 -1
- package/dist/adapter/express/bootstrap.d.ts +12 -0
- package/dist/adapter/express/bootstrap.d.ts.map +1 -1
- package/dist/adapter/express/coercion.d.ts +81 -1
- package/dist/adapter/express/coercion.d.ts.map +1 -1
- package/dist/adapter/express/index.d.ts +1 -0
- package/dist/adapter/express/index.d.ts.map +1 -1
- package/dist/adapter/express/merge.d.ts +17 -0
- package/dist/adapter/express/merge.d.ts.map +1 -1
- package/dist/adapter/express/openapi.d.ts +55 -0
- package/dist/adapter/express/openapi.d.ts.map +1 -1
- package/dist/adapter/express/router.d.ts +6 -0
- package/dist/adapter/express/router.d.ts.map +1 -1
- package/dist/adapter/express/swagger.d.ts +6 -0
- package/dist/adapter/express/swagger.d.ts.map +1 -1
- package/dist/adapter/express/types.d.ts +26 -0
- package/dist/adapter/express/types.d.ts.map +1 -1
- package/dist/adapter/express/validation.d.ts +19 -2
- package/dist/adapter/express/validation.d.ts.map +1 -1
- package/dist/cli.cjs +1016 -445
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1016 -445
- package/dist/cli.js.map +1 -1
- package/dist/compiler/analyze/index.d.ts +5 -0
- package/dist/compiler/analyze/index.d.ts.map +1 -0
- package/dist/compiler/analyze/scanControllers.d.ts +52 -0
- package/dist/compiler/analyze/scanControllers.d.ts.map +1 -1
- package/dist/compiler/cache/isStale.d.ts +26 -0
- package/dist/compiler/cache/isStale.d.ts.map +1 -1
- package/dist/compiler/cache/loadArtifacts.d.ts +36 -0
- package/dist/compiler/cache/loadArtifacts.d.ts.map +1 -1
- package/dist/compiler/cache/schema.d.ts +14 -0
- package/dist/compiler/cache/schema.d.ts.map +1 -1
- package/dist/compiler/cache/writeCache.d.ts +6 -0
- package/dist/compiler/cache/writeCache.d.ts.map +1 -1
- package/dist/compiler/gems.d.ts +75 -0
- package/dist/compiler/gems.d.ts.map +1 -0
- package/dist/compiler/generator/index.d.ts +7 -0
- package/dist/compiler/generator/index.d.ts.map +1 -0
- package/dist/compiler/generator/manifest.d.ts +23 -0
- package/dist/compiler/generator/manifest.d.ts.map +1 -0
- package/dist/compiler/generator/openapi.d.ts +118 -0
- package/dist/compiler/generator/openapi.d.ts.map +1 -0
- package/dist/compiler/graph/builder.d.ts +24 -0
- package/dist/compiler/graph/builder.d.ts.map +1 -0
- package/dist/compiler/graph/index.d.ts +7 -0
- package/dist/compiler/graph/index.d.ts.map +1 -0
- package/dist/compiler/graph/schemaGraph.d.ts +67 -0
- package/dist/compiler/graph/schemaGraph.d.ts.map +1 -0
- package/dist/compiler/graph/types.d.ts +203 -0
- package/dist/compiler/graph/types.d.ts.map +1 -0
- package/dist/compiler/index.d.ts +12 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/ir/index.d.ts +7 -0
- package/dist/compiler/ir/index.d.ts.map +1 -0
- package/dist/compiler/ir/pipeline.d.ts +82 -0
- package/dist/compiler/ir/pipeline.d.ts.map +1 -0
- package/dist/compiler/ir/stages.d.ts +40 -0
- package/dist/compiler/ir/stages.d.ts.map +1 -0
- package/dist/compiler/ir/visitor.d.ts +98 -0
- package/dist/compiler/ir/visitor.d.ts.map +1 -0
- package/dist/compiler/manifest/emit.d.ts +14 -0
- package/dist/compiler/manifest/emit.d.ts.map +1 -1
- package/dist/compiler/manifest/format.d.ts +42 -0
- package/dist/compiler/manifest/format.d.ts.map +1 -1
- package/dist/compiler/manifest/index.d.ts +6 -0
- package/dist/compiler/manifest/index.d.ts.map +1 -0
- package/dist/compiler/runner/createProgram.d.ts +16 -0
- package/dist/compiler/runner/createProgram.d.ts.map +1 -1
- package/dist/compiler/runner/index.d.ts +5 -0
- package/dist/compiler/runner/index.d.ts.map +1 -0
- package/dist/compiler/schema/extractAnnotations.d.ts +47 -0
- package/dist/compiler/schema/extractAnnotations.d.ts.map +1 -1
- package/dist/compiler/schema/index.d.ts +6 -0
- package/dist/compiler/schema/index.d.ts.map +1 -0
- package/dist/compiler/schema/intersectionHandler.d.ts +44 -0
- package/dist/compiler/schema/intersectionHandler.d.ts.map +1 -0
- package/dist/compiler/schema/objectHandler.d.ts +106 -0
- package/dist/compiler/schema/objectHandler.d.ts.map +1 -0
- package/dist/compiler/schema/openapi.d.ts +16 -1
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/parameters.d.ts +90 -0
- package/dist/compiler/schema/parameters.d.ts.map +1 -0
- package/dist/compiler/schema/primitives.d.ts +68 -0
- package/dist/compiler/schema/primitives.d.ts.map +1 -0
- package/dist/compiler/schema/typeToJsonSchema.d.ts +22 -51
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +1 -1
- package/dist/compiler/schema/types.d.ts +69 -0
- package/dist/compiler/schema/types.d.ts.map +1 -0
- package/dist/compiler/schema/unionHandler.d.ts +70 -0
- package/dist/compiler/schema/unionHandler.d.ts.map +1 -0
- package/dist/compiler/transform/dedup.d.ts +35 -0
- package/dist/compiler/transform/dedup.d.ts.map +1 -0
- package/dist/compiler/transform/flatten.d.ts +50 -0
- package/dist/compiler/transform/flatten.d.ts.map +1 -0
- package/dist/compiler/transform/index.d.ts +7 -0
- package/dist/compiler/transform/index.d.ts.map +1 -0
- package/dist/compiler/transform/inline.d.ts +46 -0
- package/dist/compiler/transform/inline.d.ts.map +1 -0
- package/dist/compiler/validation/emitPrecompiledValidators.d.ts +16 -0
- package/dist/compiler/validation/emitPrecompiledValidators.d.ts.map +1 -1
- package/dist/compiler/validation/index.d.ts +5 -0
- package/dist/compiler/validation/index.d.ts.map +1 -0
- package/dist/decorators/Auth.d.ts +17 -0
- package/dist/decorators/Auth.d.ts.map +1 -1
- package/dist/decorators/Controller.d.ts +15 -0
- package/dist/decorators/Controller.d.ts.map +1 -1
- package/dist/decorators/Public.d.ts +13 -0
- package/dist/decorators/Public.d.ts.map +1 -1
- package/dist/decorators/Use.d.ts +18 -0
- package/dist/decorators/Use.d.ts.map +1 -1
- package/dist/decorators/methods.d.ts +20 -0
- package/dist/decorators/methods.d.ts.map +1 -1
- package/dist/express.cjs +73 -54
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +73 -54
- 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 +161 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -3
- package/dist/index.js.map +1 -1
- package/dist/metal/applyListQuery.d.ts +73 -0
- package/dist/metal/applyListQuery.d.ts.map +1 -1
- package/dist/metal/index.cjs +2 -2
- package/dist/metal/index.cjs.map +1 -1
- package/dist/metal/index.d.ts +4 -0
- package/dist/metal/index.d.ts.map +1 -1
- package/dist/metal/index.js +2 -2
- package/dist/metal/index.js.map +1 -1
- package/dist/metal/listQuery.d.ts +19 -0
- package/dist/metal/listQuery.d.ts.map +1 -1
- package/dist/metal/queryOptions.d.ts +8 -0
- package/dist/metal/queryOptions.d.ts.map +1 -1
- package/dist/metal/readMetalBag.d.ts +36 -0
- package/dist/metal/readMetalBag.d.ts.map +1 -1
- package/dist/metal/registerMetalEntities.d.ts +20 -0
- package/dist/metal/registerMetalEntities.d.ts.map +1 -1
- package/dist/metal/schemaFromEntity.d.ts +30 -0
- package/dist/metal/schemaFromEntity.d.ts.map +1 -1
- package/dist/metal/searchWhere.d.ts +39 -0
- package/dist/metal/searchWhere.d.ts.map +1 -1
- package/dist/metal/symbolMetadata.d.ts +6 -0
- package/dist/metal/symbolMetadata.d.ts.map +1 -1
- package/dist/runtime/auth/runtime.d.ts +155 -6
- package/dist/runtime/auth/runtime.d.ts.map +1 -1
- package/dist/runtime/metadata/bucket.d.ts +1 -2
- package/dist/runtime/metadata/bucket.d.ts.map +1 -1
- package/dist/runtime/metadata/key.d.ts +1 -1
- package/dist/runtime/metadata/key.d.ts.map +1 -1
- package/dist/runtime/metadata/read.d.ts +1 -2
- package/dist/runtime/metadata/read.d.ts.map +1 -1
- package/dist/runtime/metadata/types.d.ts +74 -0
- package/dist/runtime/metadata/types.d.ts.map +1 -1
- package/dist/runtime/polyfill.d.ts +1 -1
- package/dist/runtime/polyfill.d.ts.map +1 -1
- package/dist/runtime/upload.d.ts +37 -0
- package/dist/runtime/upload.d.ts.map +1 -1
- package/dist/runtime/validation/ajv.d.ts +100 -0
- package/dist/runtime/validation/ajv.d.ts.map +1 -1
- package/dist/runtime/validation/index.d.ts +9 -0
- package/dist/runtime/validation/index.d.ts.map +1 -1
- package/dist/scripts/adorn-example.cjs +238 -6
- package/dist/scripts/adorn-example.cjs.map +1 -1
- package/dist/utils/port.d.ts +9 -0
- package/dist/utils/port.d.ts.map +1 -0
- package/package.json +4 -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,
|
|
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,
|
|
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;
|
|
@@ -284,7 +284,7 @@ function extractDecoratorStringArg(decorator) {
|
|
|
284
284
|
}
|
|
285
285
|
return null;
|
|
286
286
|
}
|
|
287
|
-
function unwrapPromise(type,
|
|
287
|
+
function unwrapPromise(type, _checker) {
|
|
288
288
|
const symbol = type.getSymbol();
|
|
289
289
|
if (symbol?.getName() === "Promise") {
|
|
290
290
|
const typeArgs = type.typeArguments;
|
|
@@ -305,12 +305,15 @@ function unwrapPromiseTypeNode(typeNode) {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// src/compiler/schema/openapi.ts
|
|
308
|
-
import
|
|
308
|
+
import ts9 from "typescript";
|
|
309
309
|
|
|
310
310
|
// src/compiler/schema/typeToJsonSchema.ts
|
|
311
|
+
import ts7 from "typescript";
|
|
312
|
+
|
|
313
|
+
// src/compiler/schema/primitives.ts
|
|
311
314
|
import ts3 from "typescript";
|
|
312
|
-
function
|
|
313
|
-
const { checker } = ctx;
|
|
315
|
+
function handlePrimitiveType(type, ctx, typeNode) {
|
|
316
|
+
const { checker, propertyName } = ctx;
|
|
314
317
|
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
315
318
|
return {};
|
|
316
319
|
}
|
|
@@ -324,7 +327,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
324
327
|
return { type: "string" };
|
|
325
328
|
}
|
|
326
329
|
if (type.flags & ts3.TypeFlags.Number) {
|
|
327
|
-
return normalizeNumericType(type, checker, typeNode);
|
|
330
|
+
return normalizeNumericType(type, checker, typeNode, propertyName);
|
|
328
331
|
}
|
|
329
332
|
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
330
333
|
return { type: "boolean" };
|
|
@@ -348,26 +351,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
348
351
|
const intrinsic = type.intrinsicName;
|
|
349
352
|
return { type: "boolean", enum: [intrinsic === "true"] };
|
|
350
353
|
}
|
|
351
|
-
|
|
352
|
-
return handleUnion(type, ctx, typeNode);
|
|
353
|
-
}
|
|
354
|
-
if (type.isIntersection()) {
|
|
355
|
-
return handleIntersection(type, ctx, typeNode);
|
|
356
|
-
}
|
|
357
|
-
if (checker.isArrayType(type)) {
|
|
358
|
-
const typeArgs = type.typeArguments;
|
|
359
|
-
const itemType = typeArgs?.[0];
|
|
360
|
-
const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
|
|
361
|
-
return {
|
|
362
|
-
type: "array",
|
|
363
|
-
items,
|
|
364
|
-
uniqueItems: isSetType(type, checker) ? true : void 0
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
if (type.flags & ts3.TypeFlags.Object) {
|
|
368
|
-
return handleObjectType(type, ctx, typeNode);
|
|
369
|
-
}
|
|
370
|
-
return {};
|
|
354
|
+
return null;
|
|
371
355
|
}
|
|
372
356
|
function isDateType(type, checker) {
|
|
373
357
|
const symbol = type.getSymbol();
|
|
@@ -382,53 +366,51 @@ function isDateType(type, checker) {
|
|
|
382
366
|
}
|
|
383
367
|
return symbol?.getName() === "Date";
|
|
384
368
|
}
|
|
385
|
-
function
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
function getSchemaName(type, typeNode) {
|
|
393
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
394
|
-
const aliasName = aliasSymbol?.getName();
|
|
395
|
-
if (aliasName && aliasName !== "__type") {
|
|
396
|
-
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" };
|
|
397
375
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
}
|
|
402
389
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
390
|
+
if (ts3.isTypeAliasDeclaration(typeNode.parent)) {
|
|
391
|
+
if (ts3.isIdentifier(typeNode.parent.name)) {
|
|
392
|
+
return typeNode.parent.name.text;
|
|
393
|
+
}
|
|
406
394
|
}
|
|
407
395
|
return null;
|
|
408
396
|
}
|
|
409
|
-
function
|
|
410
|
-
const
|
|
411
|
-
if (
|
|
412
|
-
return
|
|
413
|
-
}
|
|
414
|
-
const { components, typeStack } = ctx;
|
|
415
|
-
if (components.has(name) || typeStack.has(type)) {
|
|
416
|
-
return { $ref: `#/components/schemas/${name}` };
|
|
417
|
-
}
|
|
418
|
-
typeStack.add(type);
|
|
419
|
-
const schema = build();
|
|
420
|
-
typeStack.delete(type);
|
|
421
|
-
if (!components.has(name)) {
|
|
422
|
-
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);
|
|
423
401
|
}
|
|
424
|
-
return
|
|
402
|
+
return type.getSymbol() ?? null;
|
|
425
403
|
}
|
|
404
|
+
|
|
405
|
+
// src/compiler/schema/unionHandler.ts
|
|
406
|
+
import ts4 from "typescript";
|
|
426
407
|
function handleUnion(type, ctx, typeNode) {
|
|
427
408
|
return buildNamedSchema(type, ctx, typeNode, () => {
|
|
428
409
|
const types = type.types;
|
|
429
|
-
const nullType = types.find((t) => t.flags &
|
|
430
|
-
const
|
|
431
|
-
const
|
|
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);
|
|
432
414
|
if (allStringLiterals && otherTypes.length > 0) {
|
|
433
415
|
const enumValues = otherTypes.map((t) => t.value);
|
|
434
416
|
const schema = { type: "string", enum: enumValues };
|
|
@@ -437,6 +419,14 @@ function handleUnion(type, ctx, typeNode) {
|
|
|
437
419
|
}
|
|
438
420
|
return schema;
|
|
439
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
|
+
}
|
|
440
430
|
if (otherTypes.length === 1 && nullType) {
|
|
441
431
|
const innerSchema = typeToJsonSchema(otherTypes[0], ctx);
|
|
442
432
|
if (typeof innerSchema.type === "string") {
|
|
@@ -466,49 +456,7 @@ function handleUnion(type, ctx, typeNode) {
|
|
|
466
456
|
return {};
|
|
467
457
|
});
|
|
468
458
|
}
|
|
469
|
-
function
|
|
470
|
-
return buildNamedSchema(type, ctx, typeNode, () => {
|
|
471
|
-
const types = type.types;
|
|
472
|
-
const brandCollapsed = tryCollapseBrandedIntersection(types, ctx, typeNode);
|
|
473
|
-
if (brandCollapsed) {
|
|
474
|
-
return brandCollapsed;
|
|
475
|
-
}
|
|
476
|
-
const allOf = [];
|
|
477
|
-
for (const t of types) {
|
|
478
|
-
allOf.push(typeToJsonSchema(t, ctx));
|
|
479
|
-
}
|
|
480
|
-
return { allOf };
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
function tryCollapseBrandedIntersection(types, ctx, typeNode) {
|
|
484
|
-
const { checker } = ctx;
|
|
485
|
-
const parts = [...types];
|
|
486
|
-
const prim = parts.find(isPrimitiveLike);
|
|
487
|
-
if (!prim) return null;
|
|
488
|
-
const rest = parts.filter((p) => p !== prim);
|
|
489
|
-
if (rest.every((r) => isBrandObject(checker, r, ctx))) {
|
|
490
|
-
return typeToJsonSchema(prim, ctx);
|
|
491
|
-
}
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
function isPrimitiveLike(t) {
|
|
495
|
-
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;
|
|
496
|
-
}
|
|
497
|
-
function isBrandObject(checker, t, ctx) {
|
|
498
|
-
if (!(t.flags & ts3.TypeFlags.Object)) return false;
|
|
499
|
-
const props = t.getProperties();
|
|
500
|
-
if (props.length === 0) return false;
|
|
501
|
-
const allowed = /* @__PURE__ */ new Set(["__brand", "__type", "__tag", "brand"]);
|
|
502
|
-
for (const p of props) {
|
|
503
|
-
if (!allowed.has(p.getName())) return false;
|
|
504
|
-
}
|
|
505
|
-
const callSigs = t.getCallSignatures?.();
|
|
506
|
-
if (callSigs && callSigs.length > 0) return false;
|
|
507
|
-
const constructSigs = t.getConstructSignatures?.();
|
|
508
|
-
if (constructSigs && constructSigs.length > 0) return false;
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
511
|
-
function detectDiscriminatedUnion(types, ctx, branches) {
|
|
459
|
+
function detectDiscriminatedUnion(types, ctx, _branches) {
|
|
512
460
|
if (types.length < 2) return null;
|
|
513
461
|
const candidates = findCommonPropertyNames(ctx.checker, types);
|
|
514
462
|
for (const propName of candidates) {
|
|
@@ -539,10 +487,10 @@ function findCommonPropertyNames(checker, types) {
|
|
|
539
487
|
function isRequiredProperty(checker, type, propName) {
|
|
540
488
|
const sym = checker.getPropertyOfType(type, propName);
|
|
541
489
|
if (!sym) return false;
|
|
542
|
-
if (sym.flags &
|
|
490
|
+
if (sym.flags & ts4.SymbolFlags.Optional) return false;
|
|
543
491
|
const propType = checker.getTypeOfSymbol(sym);
|
|
544
492
|
if (propType.isUnion?.()) {
|
|
545
|
-
const hasUndefined = propType.types.some((t) => (t.flags &
|
|
493
|
+
const hasUndefined = propType.types.some((t) => (t.flags & ts4.TypeFlags.Undefined) !== 0);
|
|
546
494
|
if (hasUndefined) return false;
|
|
547
495
|
}
|
|
548
496
|
return true;
|
|
@@ -585,16 +533,186 @@ function getBranchSchemaName(type, ctx) {
|
|
|
585
533
|
}
|
|
586
534
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
587
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";
|
|
588
703
|
function handleObjectType(type, ctx, typeNode) {
|
|
589
|
-
const { checker, components, typeStack
|
|
704
|
+
const { checker, components, typeStack } = ctx;
|
|
590
705
|
const symbol = type.getSymbol();
|
|
591
706
|
const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
|
|
592
707
|
if (isMetalOrmWrapperType(type, checker)) {
|
|
593
708
|
return handleMetalOrmWrapper(type, ctx);
|
|
594
709
|
}
|
|
595
710
|
if (typeName && typeName !== "__type") {
|
|
596
|
-
|
|
597
|
-
|
|
711
|
+
const isMetalOrmGeneric = METAL_ORM_WRAPPER_NAMES.some(
|
|
712
|
+
(name) => typeName === name || typeName.endsWith("Api")
|
|
713
|
+
);
|
|
714
|
+
if (isMetalOrmGeneric) {
|
|
715
|
+
return {};
|
|
598
716
|
}
|
|
599
717
|
if (typeStack.has(type)) {
|
|
600
718
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -607,46 +725,18 @@ function handleObjectType(type, ctx, typeNode) {
|
|
|
607
725
|
const existing = components.get(typeName);
|
|
608
726
|
if (!existing) {
|
|
609
727
|
components.set(typeName, schema);
|
|
728
|
+
} else {
|
|
729
|
+
const merged = mergeSchemasIfNeeded(existing, schema);
|
|
730
|
+
if (merged !== existing) {
|
|
731
|
+
components.set(typeName, merged);
|
|
732
|
+
}
|
|
610
733
|
}
|
|
611
734
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
612
735
|
}
|
|
613
736
|
typeStack.delete(type);
|
|
614
737
|
return schema;
|
|
615
738
|
}
|
|
616
|
-
function
|
|
617
|
-
if (!typeNode) return null;
|
|
618
|
-
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
619
|
-
if (ts3.isIdentifier(typeNode.typeName)) {
|
|
620
|
-
return typeNode.typeName.text;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
if (ts3.isTypeAliasDeclaration(typeNode.parent)) {
|
|
624
|
-
if (ts3.isIdentifier(typeNode.parent.name)) {
|
|
625
|
-
return typeNode.parent.name.text;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
function shouldBeIntegerType(typeName) {
|
|
631
|
-
if (!typeName) return false;
|
|
632
|
-
const lower = typeName.toLowerCase();
|
|
633
|
-
return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk";
|
|
634
|
-
}
|
|
635
|
-
function normalizeNumericType(type, checker, typeNode) {
|
|
636
|
-
const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
|
|
637
|
-
const symbol = getEffectiveSymbol(type, checker);
|
|
638
|
-
const symbolName = symbol?.getName() ?? null;
|
|
639
|
-
if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName)) {
|
|
640
|
-
return { type: "integer" };
|
|
641
|
-
}
|
|
642
|
-
return { type: "number" };
|
|
643
|
-
}
|
|
644
|
-
function getTypeNameFromNode(typeNode, ctx) {
|
|
645
|
-
const explicitName = getExplicitTypeNameFromNode(typeNode);
|
|
646
|
-
if (explicitName) return explicitName;
|
|
647
|
-
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
648
|
-
}
|
|
649
|
-
function buildObjectSchema(type, ctx, typeNode) {
|
|
739
|
+
function buildObjectSchema(type, ctx, _typeNode) {
|
|
650
740
|
const { checker, mode } = ctx;
|
|
651
741
|
const properties = {};
|
|
652
742
|
const required = [];
|
|
@@ -660,9 +750,10 @@ function buildObjectSchema(type, ctx, typeNode) {
|
|
|
660
750
|
if (isMethodLike(propType)) {
|
|
661
751
|
continue;
|
|
662
752
|
}
|
|
663
|
-
const isOptional = !!(prop.flags &
|
|
753
|
+
const isOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
|
|
664
754
|
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
665
|
-
|
|
755
|
+
const propCtx = { ...ctx, propertyName: propName };
|
|
756
|
+
properties[propName] = typeToJsonSchema(propType, propCtx);
|
|
666
757
|
const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
|
|
667
758
|
if (shouldRequire) {
|
|
668
759
|
required.push(propName);
|
|
@@ -683,14 +774,14 @@ function buildObjectSchema(type, ctx, typeNode) {
|
|
|
683
774
|
}
|
|
684
775
|
return schema;
|
|
685
776
|
}
|
|
686
|
-
function isRecordType(type,
|
|
777
|
+
function isRecordType(type, _checker) {
|
|
687
778
|
const symbol = type.getSymbol();
|
|
688
779
|
if (!symbol) return false;
|
|
689
780
|
const name = symbol.getName();
|
|
690
781
|
if (name === "Record") return true;
|
|
691
782
|
return false;
|
|
692
783
|
}
|
|
693
|
-
function getRecordValueType(type,
|
|
784
|
+
function getRecordValueType(type, _checker) {
|
|
694
785
|
const symbol = type.getSymbol();
|
|
695
786
|
if (!symbol) return null;
|
|
696
787
|
const name = symbol.getName();
|
|
@@ -703,24 +794,8 @@ function getRecordValueType(type, checker) {
|
|
|
703
794
|
}
|
|
704
795
|
return null;
|
|
705
796
|
}
|
|
706
|
-
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
707
797
|
function isMetalOrmWrapperType(type, checker) {
|
|
708
|
-
|
|
709
|
-
if (!aliasSymbol) return false;
|
|
710
|
-
return METAL_ORM_WRAPPER_NAMES.includes(aliasSymbol.getName());
|
|
711
|
-
}
|
|
712
|
-
function getWrapperTypeName(type, checker) {
|
|
713
|
-
const symbol = getEffectiveSymbol(type, checker);
|
|
714
|
-
if (!symbol) return null;
|
|
715
|
-
const name = symbol.getName();
|
|
716
|
-
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
717
|
-
}
|
|
718
|
-
function getEffectiveSymbol(type, checker) {
|
|
719
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
720
|
-
if (aliasSymbol && aliasSymbol.flags & ts3.SymbolFlags.Alias) {
|
|
721
|
-
return checker.getAliasedSymbol(aliasSymbol);
|
|
722
|
-
}
|
|
723
|
-
return type.getSymbol() ?? null;
|
|
798
|
+
return !!findMetalOrmWrapper(type, checker);
|
|
724
799
|
}
|
|
725
800
|
function isMethodLike(type) {
|
|
726
801
|
const callSigs = type.getCallSignatures?.();
|
|
@@ -729,6 +804,159 @@ function isMethodLike(type) {
|
|
|
729
804
|
function isIteratorOrSymbolProperty(propName) {
|
|
730
805
|
return propName.startsWith("__@") || propName.startsWith("[") || propName === Symbol.iterator.toString();
|
|
731
806
|
}
|
|
807
|
+
function getTypeNameFromNode(typeNode, _ctx) {
|
|
808
|
+
const explicitName = getExplicitTypeNameFromNode4(typeNode);
|
|
809
|
+
if (explicitName) return explicitName;
|
|
810
|
+
return "Anonymous_${ctx.typeNameStack.length}";
|
|
811
|
+
}
|
|
812
|
+
function getExplicitTypeNameFromNode4(typeNode) {
|
|
813
|
+
if (!typeNode) return null;
|
|
814
|
+
if (ts6.isTypeReferenceNode(typeNode)) {
|
|
815
|
+
if (ts6.isIdentifier(typeNode.typeName)) {
|
|
816
|
+
return typeNode.typeName.text;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (ts6.isTypeAliasDeclaration(typeNode.parent)) {
|
|
820
|
+
if (ts6.isIdentifier(typeNode.parent.name)) {
|
|
821
|
+
return typeNode.parent.name.text;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
function mergeSchemasIfNeeded(existing, newSchema) {
|
|
827
|
+
if (existing.type === "array" && newSchema.type === "array") {
|
|
828
|
+
return mergeArraySchemas(existing, newSchema);
|
|
829
|
+
}
|
|
830
|
+
const result = { ...existing };
|
|
831
|
+
for (const [key, newValue] of Object.entries(newSchema)) {
|
|
832
|
+
if (key === "properties" && newValue) {
|
|
833
|
+
result.properties = mergePropertiesIfNeeded(existing.properties || {}, newValue);
|
|
834
|
+
} else if (key === "required" && newValue) {
|
|
835
|
+
result.required = mergeRequiredFields(existing.required || [], newValue);
|
|
836
|
+
} else if (!deepEqual(existing[key], newValue)) {
|
|
837
|
+
result[key] = newValue;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return result;
|
|
841
|
+
}
|
|
842
|
+
function mergePropertiesIfNeeded(existing, newProps) {
|
|
843
|
+
const result = { ...existing };
|
|
844
|
+
for (const [propName, newPropSchema] of Object.entries(newProps)) {
|
|
845
|
+
const existingProp = existing[propName];
|
|
846
|
+
if (!existingProp) {
|
|
847
|
+
result[propName] = newPropSchema;
|
|
848
|
+
} else if (deepEqual(existingProp, newPropSchema)) {
|
|
849
|
+
continue;
|
|
850
|
+
} else {
|
|
851
|
+
result[propName] = mergePropertySchemas(existingProp, newPropSchema);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return result;
|
|
855
|
+
}
|
|
856
|
+
function mergePropertySchemas(schema1, schema2) {
|
|
857
|
+
if (deepEqual(schema1, schema2)) {
|
|
858
|
+
return schema1;
|
|
859
|
+
}
|
|
860
|
+
if (schema1.type === "array" && schema2.type === "array") {
|
|
861
|
+
return mergeArraySchemas(schema1, schema2);
|
|
862
|
+
}
|
|
863
|
+
const existingOneOf = schema1.oneOf || schema1.anyOf;
|
|
864
|
+
const newOneOf = schema2.oneOf || schema2.anyOf;
|
|
865
|
+
if (existingOneOf) {
|
|
866
|
+
const mergedOneOf = [...existingOneOf];
|
|
867
|
+
if (newOneOf) {
|
|
868
|
+
for (const newItem of newOneOf) {
|
|
869
|
+
if (!mergedOneOf.some((item) => deepEqual(item, newItem))) {
|
|
870
|
+
mergedOneOf.push(newItem);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
} else if (!mergedOneOf.some((item) => deepEqual(item, schema2))) {
|
|
874
|
+
mergedOneOf.push(schema2);
|
|
875
|
+
}
|
|
876
|
+
return { ...schema1, oneOf: mergedOneOf };
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
oneOf: [schema1, schema2]
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function mergeArraySchemas(schema1, schema2) {
|
|
883
|
+
const result = { type: "array" };
|
|
884
|
+
if (schema1.uniqueItems || schema2.uniqueItems) {
|
|
885
|
+
result.uniqueItems = true;
|
|
886
|
+
}
|
|
887
|
+
if (schema1.items && schema2.items) {
|
|
888
|
+
result.items = mergePropertySchemas(schema1.items, schema2.items);
|
|
889
|
+
} else if (schema1.items) {
|
|
890
|
+
result.items = schema1.items;
|
|
891
|
+
} else if (schema2.items) {
|
|
892
|
+
result.items = schema2.items;
|
|
893
|
+
}
|
|
894
|
+
return result;
|
|
895
|
+
}
|
|
896
|
+
function mergeRequiredFields(existing, newFields) {
|
|
897
|
+
const merged = /* @__PURE__ */ new Set([...existing, ...newFields]);
|
|
898
|
+
return Array.from(merged);
|
|
899
|
+
}
|
|
900
|
+
function deepEqual(a, b) {
|
|
901
|
+
if (a === b) return true;
|
|
902
|
+
if (a == null || b == null) return false;
|
|
903
|
+
if (typeof a !== typeof b) return false;
|
|
904
|
+
if (typeof a === "object") {
|
|
905
|
+
const aKeys = Object.keys(a);
|
|
906
|
+
const bKeys = Object.keys(b);
|
|
907
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
908
|
+
for (const key of aKeys) {
|
|
909
|
+
if (!bKeys.includes(key)) return false;
|
|
910
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
911
|
+
}
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
917
|
+
function findMetalOrmWrapper(type, checker) {
|
|
918
|
+
if (type.isIntersection()) {
|
|
919
|
+
let wrapperInfo = null;
|
|
920
|
+
let hasReadonlyArray = false;
|
|
921
|
+
for (const constituent of type.types) {
|
|
922
|
+
const result = findWrapperInType(constituent, checker);
|
|
923
|
+
if (result) {
|
|
924
|
+
wrapperInfo = result;
|
|
925
|
+
}
|
|
926
|
+
if (!(constituent.flags & ts6.TypeFlags.Object)) continue;
|
|
927
|
+
const symbol = constituent.getSymbol();
|
|
928
|
+
if (symbol?.getName() === "ReadonlyArray") {
|
|
929
|
+
hasReadonlyArray = true;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (wrapperInfo) {
|
|
933
|
+
return { ...wrapperInfo, isReadonlyArray: hasReadonlyArray };
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
return findWrapperInType(type, checker);
|
|
938
|
+
}
|
|
939
|
+
function findWrapperInType(type, checker) {
|
|
940
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
941
|
+
const symbol = type.getSymbol();
|
|
942
|
+
const effectiveSymbol = aliasSymbol && aliasSymbol.flags & ts6.SymbolFlags.Alias ? checker.getAliasedSymbol(aliasSymbol) : symbol;
|
|
943
|
+
if (!effectiveSymbol) return null;
|
|
944
|
+
const name = effectiveSymbol.getName();
|
|
945
|
+
if (!METAL_ORM_WRAPPER_NAMES.includes(name)) return null;
|
|
946
|
+
const typeRef = type;
|
|
947
|
+
const typeArgs = typeRef.typeArguments || [];
|
|
948
|
+
return {
|
|
949
|
+
wrapperName: name,
|
|
950
|
+
targetTypeArgs: typeArgs,
|
|
951
|
+
isReadonlyArray: false
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function getWrapperTypeName(type, _checker) {
|
|
955
|
+
const symbol = type.getSymbol();
|
|
956
|
+
if (!symbol) return null;
|
|
957
|
+
const name = symbol.getName();
|
|
958
|
+
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
959
|
+
}
|
|
732
960
|
function handleMetalOrmWrapper(type, ctx) {
|
|
733
961
|
const typeRef = type;
|
|
734
962
|
const typeArgs = typeRef.typeArguments;
|
|
@@ -736,7 +964,27 @@ function handleMetalOrmWrapper(type, ctx) {
|
|
|
736
964
|
const wrapperName = getWrapperTypeName(type, ctx.checker);
|
|
737
965
|
if (!wrapperName) return {};
|
|
738
966
|
const wrapperRel = { wrapper: wrapperName };
|
|
967
|
+
if (!targetType) {
|
|
968
|
+
return { "x-metal-orm-rel": wrapperRel };
|
|
969
|
+
}
|
|
739
970
|
if (wrapperName === "HasManyCollection" || wrapperName === "ManyToManyCollection") {
|
|
971
|
+
if (ctx.typeStack.has(targetType)) {
|
|
972
|
+
const items2 = {
|
|
973
|
+
type: "object",
|
|
974
|
+
properties: {
|
|
975
|
+
id: { type: "integer" }
|
|
976
|
+
},
|
|
977
|
+
required: ["id"]
|
|
978
|
+
};
|
|
979
|
+
if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
|
|
980
|
+
wrapperRel.pivot = typeArgs[1];
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
type: "array",
|
|
984
|
+
items: items2,
|
|
985
|
+
"x-metal-orm-rel": wrapperRel
|
|
986
|
+
};
|
|
987
|
+
}
|
|
740
988
|
const items = targetType ? typeToJsonSchema(targetType, ctx) : {};
|
|
741
989
|
if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
|
|
742
990
|
wrapperRel.pivot = typeArgs[1];
|
|
@@ -747,30 +995,149 @@ function handleMetalOrmWrapper(type, ctx) {
|
|
|
747
995
|
"x-metal-orm-rel": wrapperRel
|
|
748
996
|
};
|
|
749
997
|
}
|
|
750
|
-
|
|
998
|
+
if (wrapperName === "BelongsToReference" || wrapperName === "HasOneReference") {
|
|
999
|
+
return handleBelongsToReference(targetType, ctx, wrapperRel);
|
|
1000
|
+
}
|
|
1001
|
+
const targetSchema = typeToJsonSchema(targetType, ctx);
|
|
751
1002
|
return {
|
|
752
1003
|
...targetSchema,
|
|
753
1004
|
"x-metal-orm-rel": wrapperRel
|
|
754
1005
|
};
|
|
755
1006
|
}
|
|
1007
|
+
function handleBelongsToReference(targetType, ctx, wrapperRel) {
|
|
1008
|
+
const { components, typeStack } = ctx;
|
|
1009
|
+
const targetSymbol = targetType.getSymbol();
|
|
1010
|
+
const typeName = targetSymbol?.getName();
|
|
1011
|
+
if (!typeName) {
|
|
1012
|
+
return {
|
|
1013
|
+
type: "object",
|
|
1014
|
+
properties: {},
|
|
1015
|
+
"x-metal-orm-rel": wrapperRel
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
const refSchemaName = `${typeName}Ref`;
|
|
1019
|
+
if (components.has(refSchemaName)) {
|
|
1020
|
+
return {
|
|
1021
|
+
$ref: `#/components/schemas/${refSchemaName}`,
|
|
1022
|
+
"x-metal-orm-rel": wrapperRel
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
if (typeStack.has(targetType)) {
|
|
1026
|
+
const circularRefSchema = {
|
|
1027
|
+
type: "object",
|
|
1028
|
+
properties: {
|
|
1029
|
+
id: { type: "integer" }
|
|
1030
|
+
},
|
|
1031
|
+
required: ["id"]
|
|
1032
|
+
};
|
|
1033
|
+
components.set(refSchemaName, circularRefSchema);
|
|
1034
|
+
return {
|
|
1035
|
+
$ref: `#/components/schemas/${refSchemaName}`,
|
|
1036
|
+
"x-metal-orm-rel": wrapperRel
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
const refSchema = buildRefSchema(targetType, ctx);
|
|
1040
|
+
components.set(refSchemaName, refSchema);
|
|
1041
|
+
return {
|
|
1042
|
+
$ref: `#/components/schemas/${refSchemaName}`,
|
|
1043
|
+
"x-metal-orm-rel": wrapperRel
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function buildRefSchema(type, ctx) {
|
|
1047
|
+
const { checker } = ctx;
|
|
1048
|
+
if (!(type.flags & ts6.TypeFlags.Object)) {
|
|
1049
|
+
return { type: "object", properties: {} };
|
|
1050
|
+
}
|
|
1051
|
+
const objectType = type;
|
|
1052
|
+
const properties = {};
|
|
1053
|
+
const required = [];
|
|
1054
|
+
const props = checker.getPropertiesOfType(objectType);
|
|
1055
|
+
for (const prop of props) {
|
|
1056
|
+
const propName = prop.getName();
|
|
1057
|
+
if (isIteratorOrSymbolProperty(propName)) {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
1061
|
+
if (isMethodLike(propType)) {
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
const isOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
|
|
1065
|
+
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
1066
|
+
if (isRelation) {
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const propCtx = { ...ctx, propertyName: propName };
|
|
1070
|
+
properties[propName] = typeToJsonSchema(propType, propCtx);
|
|
1071
|
+
if (!isOptional) {
|
|
1072
|
+
required.push(propName);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const schema = {
|
|
1076
|
+
type: "object",
|
|
1077
|
+
properties
|
|
1078
|
+
};
|
|
1079
|
+
if (required.length > 0) {
|
|
1080
|
+
schema.required = required;
|
|
1081
|
+
}
|
|
1082
|
+
return schema;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/compiler/schema/typeToJsonSchema.ts
|
|
1086
|
+
function typeToJsonSchema(type, ctx, typeNode) {
|
|
1087
|
+
const primitiveResult = handlePrimitiveType(type, ctx, typeNode);
|
|
1088
|
+
if (primitiveResult) {
|
|
1089
|
+
return primitiveResult;
|
|
1090
|
+
}
|
|
1091
|
+
if (type.isUnion()) {
|
|
1092
|
+
return handleUnion(type, ctx, typeNode);
|
|
1093
|
+
}
|
|
1094
|
+
if (type.isIntersection()) {
|
|
1095
|
+
return handleIntersection(type, ctx, typeNode);
|
|
1096
|
+
}
|
|
1097
|
+
if (ctx.checker.isArrayType(type)) {
|
|
1098
|
+
const typeArgs = type.typeArguments;
|
|
1099
|
+
const itemType = typeArgs?.[0];
|
|
1100
|
+
const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
|
|
1101
|
+
return {
|
|
1102
|
+
type: "array",
|
|
1103
|
+
items,
|
|
1104
|
+
uniqueItems: isSetType(type, ctx.checker) ? true : void 0
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
if (type.flags & ts7.TypeFlags.Object) {
|
|
1108
|
+
const objectType = type;
|
|
1109
|
+
if (isMetalOrmWrapperType(type, ctx.checker)) {
|
|
1110
|
+
return handleMetalOrmWrapper(objectType, ctx);
|
|
1111
|
+
}
|
|
1112
|
+
return handleObjectType(objectType, ctx, typeNode);
|
|
1113
|
+
}
|
|
1114
|
+
return {};
|
|
1115
|
+
}
|
|
1116
|
+
function isSetType(type, _checker) {
|
|
1117
|
+
const symbol = type.getSymbol();
|
|
1118
|
+
if (!symbol) return false;
|
|
1119
|
+
const name = symbol.getName();
|
|
1120
|
+
if (name === "Set") return true;
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
756
1123
|
|
|
757
1124
|
// src/compiler/schema/extractAnnotations.ts
|
|
758
|
-
import
|
|
1125
|
+
import ts8 from "typescript";
|
|
759
1126
|
function extractPropertySchemaFragments(checker, prop) {
|
|
760
|
-
if (!
|
|
761
|
-
const decs =
|
|
1127
|
+
if (!ts8.canHaveDecorators(prop)) return [];
|
|
1128
|
+
const decs = ts8.getDecorators(prop);
|
|
762
1129
|
if (!decs || decs.length === 0) return [];
|
|
763
1130
|
const frags = [];
|
|
764
1131
|
for (const d of decs) {
|
|
765
1132
|
const expr = d.expression;
|
|
766
1133
|
let callee;
|
|
767
1134
|
let args;
|
|
768
|
-
if (
|
|
1135
|
+
if (ts8.isCallExpression(expr)) {
|
|
769
1136
|
callee = expr.expression;
|
|
770
1137
|
args = expr.arguments;
|
|
771
1138
|
} else {
|
|
772
1139
|
callee = expr;
|
|
773
|
-
args =
|
|
1140
|
+
args = ts8.factory.createNodeArray([]);
|
|
774
1141
|
}
|
|
775
1142
|
const sym = checker.getSymbolAtLocation(callee);
|
|
776
1143
|
if (!sym) continue;
|
|
@@ -779,7 +1146,7 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
779
1146
|
const name = resolved.name;
|
|
780
1147
|
if (name === "Schema") {
|
|
781
1148
|
const obj = args[0];
|
|
782
|
-
if (obj &&
|
|
1149
|
+
if (obj && ts8.isObjectLiteralExpression(obj)) {
|
|
783
1150
|
const frag = objectLiteralToJson(obj);
|
|
784
1151
|
if (frag) frags.push(frag);
|
|
785
1152
|
}
|
|
@@ -801,7 +1168,7 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
801
1168
|
frags.push({ format: args[0].text });
|
|
802
1169
|
} else if (name === "Pattern") {
|
|
803
1170
|
const arg = args[0];
|
|
804
|
-
if (arg &&
|
|
1171
|
+
if (arg && ts8.isRegularExpressionLiteral(arg)) {
|
|
805
1172
|
frags.push({ pattern: extractRegexPattern(arg.text) });
|
|
806
1173
|
} else if (isStringLiteral(arg)) {
|
|
807
1174
|
frags.push({ pattern: arg.text });
|
|
@@ -818,11 +1185,11 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
818
1185
|
frags.push({ multipleOf: Number(args[0].text) });
|
|
819
1186
|
} else if (name === "Example") {
|
|
820
1187
|
frags.push({ example: literalToJson(args[0]) });
|
|
821
|
-
} else if (name === "Examples" &&
|
|
1188
|
+
} else if (name === "Examples" && ts8.isArrayLiteralExpression(args[0])) {
|
|
822
1189
|
frags.push({ examples: args[0].elements.map((e) => literalToJson(e)) });
|
|
823
1190
|
} else if (name === "Description" && isStringLiteral(args[0])) {
|
|
824
1191
|
frags.push({ description: args[0].text });
|
|
825
|
-
} else if (name === "Enum" &&
|
|
1192
|
+
} else if (name === "Enum" && ts8.isArrayLiteralExpression(args[0])) {
|
|
826
1193
|
frags.push({ enum: args[0].elements.map((e) => literalToJson(e)) });
|
|
827
1194
|
} else if (name === "Const") {
|
|
828
1195
|
frags.push({ const: literalToJson(args[0]) });
|
|
@@ -830,9 +1197,9 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
830
1197
|
frags.push({ default: literalToJson(args[0]) });
|
|
831
1198
|
} else if (name === "AdditionalProperties") {
|
|
832
1199
|
const arg = args[0];
|
|
833
|
-
if (arg && (arg.kind ===
|
|
834
|
-
frags.push({ additionalProperties: arg.kind ===
|
|
835
|
-
} else if (arg &&
|
|
1200
|
+
if (arg && (arg.kind === ts8.SyntaxKind.FalseKeyword || arg.kind === ts8.SyntaxKind.TrueKeyword)) {
|
|
1201
|
+
frags.push({ additionalProperties: arg.kind === ts8.SyntaxKind.TrueKeyword });
|
|
1202
|
+
} else if (arg && ts8.isObjectLiteralExpression(arg)) {
|
|
836
1203
|
const obj = objectLiteralToJson(arg);
|
|
837
1204
|
if (obj) frags.push({ additionalProperties: obj });
|
|
838
1205
|
}
|
|
@@ -845,7 +1212,7 @@ function extractPropertySchemaFragments(checker, prop) {
|
|
|
845
1212
|
return frags;
|
|
846
1213
|
}
|
|
847
1214
|
function resolveImportedDecorator(checker, sym) {
|
|
848
|
-
const target = sym.flags &
|
|
1215
|
+
const target = sym.flags & ts8.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
|
|
849
1216
|
const name = target.getName();
|
|
850
1217
|
const decl = target.declarations?.[0];
|
|
851
1218
|
if (!decl) return null;
|
|
@@ -856,10 +1223,10 @@ function resolveImportedDecorator(checker, sym) {
|
|
|
856
1223
|
return null;
|
|
857
1224
|
}
|
|
858
1225
|
function isNumberLiteral(node) {
|
|
859
|
-
return !!node &&
|
|
1226
|
+
return !!node && ts8.isNumericLiteral(node);
|
|
860
1227
|
}
|
|
861
1228
|
function isStringLiteral(node) {
|
|
862
|
-
return !!node &&
|
|
1229
|
+
return !!node && ts8.isStringLiteral(node);
|
|
863
1230
|
}
|
|
864
1231
|
function extractRegexPattern(text) {
|
|
865
1232
|
const match = text.match(/^\/(.+)\/[gimsuy]*$/);
|
|
@@ -868,12 +1235,12 @@ function extractRegexPattern(text) {
|
|
|
868
1235
|
function objectLiteralToJson(obj) {
|
|
869
1236
|
const out = {};
|
|
870
1237
|
for (const prop of obj.properties) {
|
|
871
|
-
if (!
|
|
1238
|
+
if (!ts8.isPropertyAssignment(prop)) continue;
|
|
872
1239
|
const name = prop.name;
|
|
873
1240
|
let key;
|
|
874
|
-
if (
|
|
1241
|
+
if (ts8.isIdentifier(name)) {
|
|
875
1242
|
key = name.text;
|
|
876
|
-
} else if (
|
|
1243
|
+
} else if (ts8.isStringLiteral(name)) {
|
|
877
1244
|
key = name.text;
|
|
878
1245
|
} else {
|
|
879
1246
|
continue;
|
|
@@ -883,13 +1250,13 @@ function objectLiteralToJson(obj) {
|
|
|
883
1250
|
return out;
|
|
884
1251
|
}
|
|
885
1252
|
function literalToJson(node) {
|
|
886
|
-
if (
|
|
887
|
-
if (
|
|
888
|
-
if (node.kind ===
|
|
889
|
-
if (node.kind ===
|
|
890
|
-
if (node.kind ===
|
|
891
|
-
if (
|
|
892
|
-
if (
|
|
1253
|
+
if (ts8.isStringLiteral(node)) return node.text;
|
|
1254
|
+
if (ts8.isNumericLiteral(node)) return Number(node.text);
|
|
1255
|
+
if (node.kind === ts8.SyntaxKind.TrueKeyword) return true;
|
|
1256
|
+
if (node.kind === ts8.SyntaxKind.FalseKeyword) return false;
|
|
1257
|
+
if (node.kind === ts8.SyntaxKind.NullKeyword) return null;
|
|
1258
|
+
if (ts8.isObjectLiteralExpression(node)) return objectLiteralToJson(node);
|
|
1259
|
+
if (ts8.isArrayLiteralExpression(node)) return node.elements.map((e) => literalToJson(e));
|
|
893
1260
|
return void 0;
|
|
894
1261
|
}
|
|
895
1262
|
function mergeFragments(base, ...frags) {
|
|
@@ -900,7 +1267,302 @@ function mergeFragments(base, ...frags) {
|
|
|
900
1267
|
return result;
|
|
901
1268
|
}
|
|
902
1269
|
|
|
1270
|
+
// src/compiler/schema/parameters.ts
|
|
1271
|
+
function buildPathParameters(operation, ctx, parameters) {
|
|
1272
|
+
for (const paramIndex of operation.pathParamIndices) {
|
|
1273
|
+
const param = operation.parameters[paramIndex];
|
|
1274
|
+
if (param) {
|
|
1275
|
+
let paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1276
|
+
if (param.paramNode) {
|
|
1277
|
+
const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
|
|
1278
|
+
if (frags.length > 0) {
|
|
1279
|
+
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
1283
|
+
const paramName = param.name.toLowerCase();
|
|
1284
|
+
const isIdParam = paramName === "id" || paramName.endsWith("id");
|
|
1285
|
+
if (!schema.$ref && schema.type === "number" && isIdParam) {
|
|
1286
|
+
schema.type = "integer";
|
|
1287
|
+
if (!schema.minimum) {
|
|
1288
|
+
schema.minimum = 1;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
parameters.push({
|
|
1292
|
+
name: param.name,
|
|
1293
|
+
in: "path",
|
|
1294
|
+
required: !param.isOptional,
|
|
1295
|
+
schema
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function buildQueryParameters(operation, ctx, parameters) {
|
|
1301
|
+
if (operation.queryObjectParamIndex !== null) {
|
|
1302
|
+
const queryParam = operation.parameters[operation.queryObjectParamIndex];
|
|
1303
|
+
if (!queryParam) return;
|
|
1304
|
+
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1305
|
+
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
|
|
1306
|
+
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1307
|
+
const isRequired = queryRequired.includes(propName);
|
|
1308
|
+
const isDeepObject = isDeepObjectSchema(propSchema, ctx);
|
|
1309
|
+
const isObjectLike = isObjectLikeSchema(propSchema, ctx);
|
|
1310
|
+
const serialization = determineQuerySerialization(propSchema.type);
|
|
1311
|
+
const exampleValue = generateExampleValue(propSchema, propName);
|
|
1312
|
+
if (isDeepObject) {
|
|
1313
|
+
parameters.push({
|
|
1314
|
+
name: propName,
|
|
1315
|
+
in: "query",
|
|
1316
|
+
required: isRequired,
|
|
1317
|
+
style: "deepObject",
|
|
1318
|
+
explode: true,
|
|
1319
|
+
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1320
|
+
});
|
|
1321
|
+
} else if (isObjectLike) {
|
|
1322
|
+
const schemaRef = propSchema.$ref || "#/components/schemas/InlineQueryParam";
|
|
1323
|
+
parameters.push({
|
|
1324
|
+
name: propName,
|
|
1325
|
+
in: "query",
|
|
1326
|
+
required: isRequired,
|
|
1327
|
+
schema: { type: "string" },
|
|
1328
|
+
description: `JSON-encoded object. ${exampleValue}`,
|
|
1329
|
+
examples: {
|
|
1330
|
+
default: { value: parseExampleValue(exampleValue) }
|
|
1331
|
+
},
|
|
1332
|
+
"x-adorn-jsonSchemaRef": schemaRef
|
|
1333
|
+
});
|
|
1334
|
+
} else {
|
|
1335
|
+
const paramDef = {
|
|
1336
|
+
name: propName,
|
|
1337
|
+
in: "query",
|
|
1338
|
+
required: isRequired,
|
|
1339
|
+
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1340
|
+
};
|
|
1341
|
+
if (propName === "page") {
|
|
1342
|
+
paramDef.schema = { type: "integer", default: 1, minimum: 1 };
|
|
1343
|
+
} else if (propName === "pageSize") {
|
|
1344
|
+
paramDef.schema = { type: "integer", default: 10, minimum: 1 };
|
|
1345
|
+
} else if (propName === "totalItems") {
|
|
1346
|
+
paramDef.schema = { type: "integer", minimum: 0 };
|
|
1347
|
+
} else if (propName === "sort") {
|
|
1348
|
+
paramDef.schema = {
|
|
1349
|
+
oneOf: [
|
|
1350
|
+
{ type: "string" },
|
|
1351
|
+
{ type: "array", items: { type: "string" } }
|
|
1352
|
+
]
|
|
1353
|
+
};
|
|
1354
|
+
} else if (propName === "q") {
|
|
1355
|
+
paramDef.schema = { type: "string" };
|
|
1356
|
+
} else if (propName === "hasComments") {
|
|
1357
|
+
paramDef.schema = { type: "boolean" };
|
|
1358
|
+
}
|
|
1359
|
+
if (Object.keys(serialization).length > 0) {
|
|
1360
|
+
Object.assign(paramDef, serialization);
|
|
1361
|
+
}
|
|
1362
|
+
parameters.push(paramDef);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
for (const paramIndex of operation.queryParamIndices) {
|
|
1367
|
+
const param = operation.parameters[paramIndex];
|
|
1368
|
+
if (param) {
|
|
1369
|
+
let paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1370
|
+
if (param.paramNode) {
|
|
1371
|
+
const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
|
|
1372
|
+
if (frags.length > 0) {
|
|
1373
|
+
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const isDeepObject = isDeepObjectSchema(paramSchema, ctx);
|
|
1377
|
+
const isObjectLike = isObjectLikeSchema(paramSchema, ctx);
|
|
1378
|
+
if (isDeepObject) {
|
|
1379
|
+
parameters.push({
|
|
1380
|
+
name: param.name,
|
|
1381
|
+
in: "query",
|
|
1382
|
+
required: !param.isOptional,
|
|
1383
|
+
style: "deepObject",
|
|
1384
|
+
explode: true,
|
|
1385
|
+
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema
|
|
1386
|
+
});
|
|
1387
|
+
} else if (isObjectLike) {
|
|
1388
|
+
const schemaRef = paramSchema.$ref || "#/components/schemas/InlineQueryParam";
|
|
1389
|
+
const exampleValue = generateExampleValue(paramSchema, param.name);
|
|
1390
|
+
parameters.push({
|
|
1391
|
+
name: param.name,
|
|
1392
|
+
in: "query",
|
|
1393
|
+
required: !param.isOptional,
|
|
1394
|
+
schema: { type: "string" },
|
|
1395
|
+
description: `JSON-encoded object. ${exampleValue}`,
|
|
1396
|
+
examples: {
|
|
1397
|
+
default: { value: parseExampleValue(exampleValue) }
|
|
1398
|
+
},
|
|
1399
|
+
"x-adorn-jsonSchemaRef": schemaRef
|
|
1400
|
+
});
|
|
1401
|
+
} else {
|
|
1402
|
+
const serialization = determineQuerySerialization(paramSchema.type);
|
|
1403
|
+
parameters.push({
|
|
1404
|
+
name: param.name,
|
|
1405
|
+
in: "query",
|
|
1406
|
+
required: !param.isOptional,
|
|
1407
|
+
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema,
|
|
1408
|
+
...Object.keys(serialization).length > 0 ? serialization : {}
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
function buildHeaderParameters(operation, ctx, parameters) {
|
|
1415
|
+
if (operation.headerObjectParamIndex === null) return;
|
|
1416
|
+
const headerParam = operation.parameters[operation.headerObjectParamIndex];
|
|
1417
|
+
if (!headerParam) return;
|
|
1418
|
+
const headerSchema = typeToJsonSchema(headerParam.type, ctx);
|
|
1419
|
+
if (!headerSchema.properties) return;
|
|
1420
|
+
const headerObjProps = headerSchema.properties;
|
|
1421
|
+
for (const [propName, propSchema] of Object.entries(headerObjProps)) {
|
|
1422
|
+
const isRequired = headerSchema.required?.includes(propName) ?? false;
|
|
1423
|
+
parameters.push({
|
|
1424
|
+
name: propName,
|
|
1425
|
+
in: "header",
|
|
1426
|
+
required: isRequired,
|
|
1427
|
+
schema: propSchema
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function buildCookieParameters(operation, ctx, parameters) {
|
|
1432
|
+
if (operation.cookieObjectParamIndex === null) return;
|
|
1433
|
+
const cookieParam = operation.parameters[operation.cookieObjectParamIndex];
|
|
1434
|
+
if (!cookieParam) return;
|
|
1435
|
+
const cookieSchema = typeToJsonSchema(cookieParam.type, ctx);
|
|
1436
|
+
if (!cookieSchema.properties) return;
|
|
1437
|
+
const cookieObjProps = cookieSchema.properties;
|
|
1438
|
+
for (const [propName, propSchema] of Object.entries(cookieObjProps)) {
|
|
1439
|
+
const isRequired = cookieSchema.required?.includes(propName) ?? false;
|
|
1440
|
+
parameters.push({
|
|
1441
|
+
name: propName,
|
|
1442
|
+
in: "cookie",
|
|
1443
|
+
required: isRequired,
|
|
1444
|
+
schema: propSchema,
|
|
1445
|
+
style: "form",
|
|
1446
|
+
explode: true
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function determineQuerySerialization(schemaType) {
|
|
1451
|
+
const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
1452
|
+
const isArray = typeArray.includes("array");
|
|
1453
|
+
if (isArray) {
|
|
1454
|
+
return { style: "form", explode: true };
|
|
1455
|
+
}
|
|
1456
|
+
return {};
|
|
1457
|
+
}
|
|
1458
|
+
function generateExampleValue(schema, propName) {
|
|
1459
|
+
const resolved = resolveSchemaRef(schema, /* @__PURE__ */ new Map());
|
|
1460
|
+
if (resolved.type === "object" && resolved.properties) {
|
|
1461
|
+
const example = {};
|
|
1462
|
+
for (const [key, prop] of Object.entries(resolved.properties)) {
|
|
1463
|
+
const propResolved = resolveSchemaRef(prop, /* @__PURE__ */ new Map());
|
|
1464
|
+
if (propResolved.type === "string") {
|
|
1465
|
+
example[key] = "value";
|
|
1466
|
+
} else if (propResolved.type === "number" || propResolved.type === "integer") {
|
|
1467
|
+
example[key] = 1;
|
|
1468
|
+
} else if (propResolved.type === "boolean") {
|
|
1469
|
+
example[key] = true;
|
|
1470
|
+
} else if (Array.isArray(propResolved.type) && propResolved.type.includes("null")) {
|
|
1471
|
+
example[key] = null;
|
|
1472
|
+
} else if (propResolved.enum) {
|
|
1473
|
+
example[key] = propResolved.enum[0];
|
|
1474
|
+
} else {
|
|
1475
|
+
example[key] = "value";
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return `Example: ${propName}=${JSON.stringify(example)}`;
|
|
1479
|
+
}
|
|
1480
|
+
return `Example: ${propName}=${JSON.stringify({ key: "value" })}`;
|
|
1481
|
+
}
|
|
1482
|
+
function parseExampleValue(description) {
|
|
1483
|
+
const match = description.match(/Example:\s*\w+=(\{[^}]+\})/);
|
|
1484
|
+
if (match) {
|
|
1485
|
+
return match[1];
|
|
1486
|
+
}
|
|
1487
|
+
return JSON.stringify({ key: "value" });
|
|
1488
|
+
}
|
|
1489
|
+
function isDeepObjectSchema(schema, ctx) {
|
|
1490
|
+
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1491
|
+
if (resolved.type === "array") {
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1495
|
+
return true;
|
|
1496
|
+
}
|
|
1497
|
+
if (resolved.allOf) {
|
|
1498
|
+
for (const branch of resolved.allOf) {
|
|
1499
|
+
if (isDeepObjectSchema(branch, ctx)) {
|
|
1500
|
+
return true;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
function isObjectLikeSchema(schema, ctx) {
|
|
1507
|
+
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1508
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1509
|
+
return true;
|
|
1510
|
+
}
|
|
1511
|
+
if (resolved.allOf) {
|
|
1512
|
+
for (const branch of resolved.allOf) {
|
|
1513
|
+
if (isObjectLikeSchema(branch, ctx)) {
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if (resolved.type === "array" && resolved.items) {
|
|
1519
|
+
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1520
|
+
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1521
|
+
}
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
function resolveSchemaRef(schema, components) {
|
|
1525
|
+
const ref = schema.$ref;
|
|
1526
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1527
|
+
return schema;
|
|
1528
|
+
}
|
|
1529
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
1530
|
+
const next = components.get(name);
|
|
1531
|
+
if (!next) return schema;
|
|
1532
|
+
return resolveSchemaRef(next, components);
|
|
1533
|
+
}
|
|
1534
|
+
function resolveAndCollectObjectProps(schema, components) {
|
|
1535
|
+
const resolved = resolveSchemaRef(schema, components);
|
|
1536
|
+
const properties = {};
|
|
1537
|
+
const required = [];
|
|
1538
|
+
const processSchema = (s) => {
|
|
1539
|
+
const current = resolveSchemaRef(s, components);
|
|
1540
|
+
if (current.properties) {
|
|
1541
|
+
for (const [key, val] of Object.entries(current.properties)) {
|
|
1542
|
+
if (!properties[key]) {
|
|
1543
|
+
properties[key] = val;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (current.required) {
|
|
1548
|
+
for (const req of current.required) {
|
|
1549
|
+
if (!required.includes(req)) {
|
|
1550
|
+
required.push(req);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if (current.allOf) {
|
|
1555
|
+
for (const branch of current.allOf) {
|
|
1556
|
+
processSchema(branch);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
processSchema(resolved);
|
|
1561
|
+
return { properties, required };
|
|
1562
|
+
}
|
|
1563
|
+
|
|
903
1564
|
// src/compiler/schema/openapi.ts
|
|
1565
|
+
var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
|
|
904
1566
|
function generateOpenAPI(controllers, checker, options = {}) {
|
|
905
1567
|
const components = /* @__PURE__ */ new Map();
|
|
906
1568
|
const ctx = {
|
|
@@ -917,21 +1579,124 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
917
1579
|
if (!paths[fullPath]) {
|
|
918
1580
|
paths[fullPath] = {};
|
|
919
1581
|
}
|
|
920
|
-
const method = operation.httpMethod.toLowerCase();
|
|
921
|
-
paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
|
|
1582
|
+
const method = operation.httpMethod.toLowerCase();
|
|
1583
|
+
paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
const schemas = Object.fromEntries(components);
|
|
1587
|
+
cleanupMetalOrmWrappers(schemas, paths);
|
|
1588
|
+
return {
|
|
1589
|
+
openapi: "3.1.0",
|
|
1590
|
+
info: {
|
|
1591
|
+
title: options.title ?? "API",
|
|
1592
|
+
version: options.version ?? "1.0.0"
|
|
1593
|
+
},
|
|
1594
|
+
components: {
|
|
1595
|
+
schemas
|
|
1596
|
+
},
|
|
1597
|
+
paths
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
function cleanupMetalOrmWrappers(schemas, paths) {
|
|
1601
|
+
const schemasToDelete = /* @__PURE__ */ new Set();
|
|
1602
|
+
for (const wrapperName of METAL_ORM_WRAPPER_NAMES2) {
|
|
1603
|
+
if (schemas[wrapperName]) {
|
|
1604
|
+
schemasToDelete.add(wrapperName);
|
|
1605
|
+
}
|
|
1606
|
+
if (schemas[`${wrapperName}Api`]) {
|
|
1607
|
+
schemasToDelete.add(`${wrapperName}Api`);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
for (const schema of Object.values(schemas)) {
|
|
1611
|
+
cleanupSchemaRefs(schema, schemasToDelete);
|
|
1612
|
+
}
|
|
1613
|
+
for (const pathItem of Object.values(paths)) {
|
|
1614
|
+
cleanupPathItemRefs(pathItem, schemasToDelete);
|
|
1615
|
+
}
|
|
1616
|
+
for (const schemaName of schemasToDelete) {
|
|
1617
|
+
delete schemas[schemaName];
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
function cleanupSchemaRefs(schema, schemasToDelete) {
|
|
1621
|
+
if (typeof schema !== "object" || schema === null) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
if (schema.properties) {
|
|
1625
|
+
for (const propName of Object.keys(schema.properties)) {
|
|
1626
|
+
const propSchema = schema.properties[propName];
|
|
1627
|
+
if (propSchema.$ref && typeof propSchema.$ref === "string") {
|
|
1628
|
+
const refName = propSchema.$ref.replace("#/components/schemas/", "");
|
|
1629
|
+
if (schemasToDelete.has(refName)) {
|
|
1630
|
+
delete schema.properties[propName];
|
|
1631
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
1632
|
+
schema.required = schema.required.filter((r) => r !== propName);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
} else {
|
|
1636
|
+
cleanupSchemaRefs(propSchema, schemasToDelete);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (schema.items) {
|
|
1641
|
+
cleanupSchemaRefs(schema.items, schemasToDelete);
|
|
1642
|
+
}
|
|
1643
|
+
if (schema.allOf) {
|
|
1644
|
+
for (const item of schema.allOf) {
|
|
1645
|
+
cleanupSchemaRefs(item, schemasToDelete);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
function cleanupPathItemRefs(pathItem, schemasToDelete) {
|
|
1650
|
+
if (typeof pathItem !== "object" || pathItem === null) {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
for (const method of Object.keys(pathItem)) {
|
|
1654
|
+
const operation = pathItem[method];
|
|
1655
|
+
if (typeof operation !== "object" || operation === null) continue;
|
|
1656
|
+
if (operation.requestBody) {
|
|
1657
|
+
cleanupRequestBodyRefs(operation.requestBody, schemasToDelete);
|
|
1658
|
+
}
|
|
1659
|
+
if (operation.responses) {
|
|
1660
|
+
const responses = Object.values(operation.responses);
|
|
1661
|
+
for (const response of responses) {
|
|
1662
|
+
if (response.content) {
|
|
1663
|
+
const contentTypes = Object.values(response.content);
|
|
1664
|
+
for (const contentType of contentTypes) {
|
|
1665
|
+
if (contentType.schema) {
|
|
1666
|
+
cleanupSchemaRefs(contentType.schema, schemasToDelete);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function cleanupRequestBodyRefs(requestBody, schemasToDelete) {
|
|
1675
|
+
if (typeof requestBody !== "object" || requestBody === null) return;
|
|
1676
|
+
if (requestBody.content) {
|
|
1677
|
+
const contentTypes = Object.values(requestBody.content);
|
|
1678
|
+
for (const contentType of contentTypes) {
|
|
1679
|
+
if (contentType.schema) {
|
|
1680
|
+
cleanupSchemaRefs(contentType.schema, schemasToDelete);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (requestBody.properties) {
|
|
1685
|
+
for (const propName of Object.keys(requestBody.properties)) {
|
|
1686
|
+
const propSchema = requestBody.properties[propName];
|
|
1687
|
+
if (propSchema.$ref && typeof propSchema.$ref === "string") {
|
|
1688
|
+
const refName = propSchema.$ref.replace("#/components/schemas/", "");
|
|
1689
|
+
if (schemasToDelete.has(refName)) {
|
|
1690
|
+
delete requestBody.properties[propName];
|
|
1691
|
+
if (requestBody.required && Array.isArray(requestBody.required)) {
|
|
1692
|
+
requestBody.required = requestBody.required.filter((r) => r !== propName);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
} else {
|
|
1696
|
+
cleanupSchemaRefs(propSchema, schemasToDelete);
|
|
1697
|
+
}
|
|
922
1698
|
}
|
|
923
1699
|
}
|
|
924
|
-
return {
|
|
925
|
-
openapi: "3.1.0",
|
|
926
|
-
info: {
|
|
927
|
-
title: options.title ?? "API",
|
|
928
|
-
version: options.version ?? "1.0.0"
|
|
929
|
-
},
|
|
930
|
-
components: {
|
|
931
|
-
schemas: Object.fromEntries(components)
|
|
932
|
-
},
|
|
933
|
-
paths
|
|
934
|
-
};
|
|
935
1700
|
}
|
|
936
1701
|
function convertToOpenApiPath(basePath, path4) {
|
|
937
1702
|
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
@@ -998,12 +1763,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
998
1763
|
const declarations = typeSymbol.getDeclarations();
|
|
999
1764
|
if (!declarations || declarations.length === 0) return schema;
|
|
1000
1765
|
const classDecl = declarations[0];
|
|
1001
|
-
if (!
|
|
1766
|
+
if (!ts9.isClassDeclaration(classDecl)) return schema;
|
|
1002
1767
|
const result = { ...schema };
|
|
1003
1768
|
const props = { ...result.properties };
|
|
1004
1769
|
for (const member of classDecl.members) {
|
|
1005
|
-
if (!
|
|
1006
|
-
const propName =
|
|
1770
|
+
if (!ts9.isPropertyDeclaration(member) || !member.name) continue;
|
|
1771
|
+
const propName = ts9.isIdentifier(member.name) ? member.name.text : null;
|
|
1007
1772
|
if (!propName) continue;
|
|
1008
1773
|
if (!props[propName]) continue;
|
|
1009
1774
|
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
@@ -1014,220 +1779,9 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
1014
1779
|
result.properties = props;
|
|
1015
1780
|
return result;
|
|
1016
1781
|
}
|
|
1017
|
-
function buildPathParameters(operation, ctx, parameters) {
|
|
1018
|
-
for (const paramIndex of operation.pathParamIndices) {
|
|
1019
|
-
const param = operation.parameters[paramIndex];
|
|
1020
|
-
if (param) {
|
|
1021
|
-
let paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1022
|
-
if (param.paramNode) {
|
|
1023
|
-
const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
|
|
1024
|
-
if (frags.length > 0) {
|
|
1025
|
-
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
1029
|
-
parameters.push({
|
|
1030
|
-
name: param.name,
|
|
1031
|
-
in: "path",
|
|
1032
|
-
required: !param.isOptional,
|
|
1033
|
-
schema
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
function isObjectLikeSchema(schema, ctx) {
|
|
1039
|
-
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1040
|
-
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1041
|
-
return true;
|
|
1042
|
-
}
|
|
1043
|
-
if (resolved.allOf) {
|
|
1044
|
-
for (const branch of resolved.allOf) {
|
|
1045
|
-
if (isObjectLikeSchema(branch, ctx)) {
|
|
1046
|
-
return true;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (resolved.type === "array" && resolved.items) {
|
|
1051
|
-
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1052
|
-
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1053
|
-
}
|
|
1054
|
-
return false;
|
|
1055
|
-
}
|
|
1056
|
-
function resolveSchemaRef(schema, components) {
|
|
1057
|
-
const ref = schema.$ref;
|
|
1058
|
-
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1059
|
-
return schema;
|
|
1060
|
-
}
|
|
1061
|
-
const name = ref.replace("#/components/schemas/", "");
|
|
1062
|
-
const next = components.get(name);
|
|
1063
|
-
if (!next) return schema;
|
|
1064
|
-
return resolveSchemaRef(next, components);
|
|
1065
|
-
}
|
|
1066
|
-
function resolveAndCollectObjectProps(schema, components) {
|
|
1067
|
-
const resolved = resolveSchemaRef(schema, components);
|
|
1068
|
-
const properties = {};
|
|
1069
|
-
const required = [];
|
|
1070
|
-
const processSchema = (s) => {
|
|
1071
|
-
const current = resolveSchemaRef(s, components);
|
|
1072
|
-
if (current.properties) {
|
|
1073
|
-
for (const [key, val] of Object.entries(current.properties)) {
|
|
1074
|
-
if (!properties[key]) {
|
|
1075
|
-
properties[key] = val;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
if (current.required) {
|
|
1080
|
-
for (const req of current.required) {
|
|
1081
|
-
if (!required.includes(req)) {
|
|
1082
|
-
required.push(req);
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
if (current.allOf) {
|
|
1087
|
-
for (const branch of current.allOf) {
|
|
1088
|
-
processSchema(branch);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
processSchema(resolved);
|
|
1093
|
-
return { properties, required };
|
|
1094
|
-
}
|
|
1095
|
-
function buildQueryParameters(operation, ctx, parameters) {
|
|
1096
|
-
if (operation.queryObjectParamIndex !== null) {
|
|
1097
|
-
const queryParam = operation.parameters[operation.queryObjectParamIndex];
|
|
1098
|
-
if (!queryParam) return;
|
|
1099
|
-
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1100
|
-
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
|
|
1101
|
-
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1102
|
-
const isRequired = queryRequired.includes(propName) ?? false;
|
|
1103
|
-
const isObjectLike = isObjectLikeSchema(propSchema, ctx);
|
|
1104
|
-
const serialization = determineQuerySerialization(propSchema.type);
|
|
1105
|
-
if (isObjectLike) {
|
|
1106
|
-
parameters.push({
|
|
1107
|
-
name: propName,
|
|
1108
|
-
in: "query",
|
|
1109
|
-
required: isRequired,
|
|
1110
|
-
content: {
|
|
1111
|
-
"application/json": {
|
|
1112
|
-
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1113
|
-
}
|
|
1114
|
-
},
|
|
1115
|
-
description: `URL-encoded JSON. Example: ${propName}=${encodeURIComponent(JSON.stringify({ example: "value" }))}`
|
|
1116
|
-
});
|
|
1117
|
-
} else {
|
|
1118
|
-
const paramDef = {
|
|
1119
|
-
name: propName,
|
|
1120
|
-
in: "query",
|
|
1121
|
-
required: isRequired,
|
|
1122
|
-
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1123
|
-
};
|
|
1124
|
-
if (propName === "page") {
|
|
1125
|
-
paramDef.schema = { type: "integer", default: 1, minimum: 1 };
|
|
1126
|
-
} else if (propName === "pageSize") {
|
|
1127
|
-
paramDef.schema = { type: "integer", default: 10, minimum: 1 };
|
|
1128
|
-
} else if (propName === "totalItems") {
|
|
1129
|
-
paramDef.schema = { type: "integer", minimum: 0 };
|
|
1130
|
-
} else if (propName === "sort") {
|
|
1131
|
-
paramDef.schema = {
|
|
1132
|
-
oneOf: [
|
|
1133
|
-
{ type: "string" },
|
|
1134
|
-
{ type: "array", items: { type: "string" } }
|
|
1135
|
-
]
|
|
1136
|
-
};
|
|
1137
|
-
} else if (propName === "q") {
|
|
1138
|
-
paramDef.schema = { type: "string" };
|
|
1139
|
-
} else if (propName === "hasComments") {
|
|
1140
|
-
paramDef.schema = { type: "boolean" };
|
|
1141
|
-
}
|
|
1142
|
-
if (Object.keys(serialization).length > 0) {
|
|
1143
|
-
Object.assign(paramDef, serialization);
|
|
1144
|
-
}
|
|
1145
|
-
parameters.push(paramDef);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
for (const paramIndex of operation.queryParamIndices) {
|
|
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 isObjectLike = isObjectLikeSchema(paramSchema, ctx);
|
|
1160
|
-
if (isObjectLike) {
|
|
1161
|
-
parameters.push({
|
|
1162
|
-
name: param.name,
|
|
1163
|
-
in: "query",
|
|
1164
|
-
required: !param.isOptional,
|
|
1165
|
-
content: {
|
|
1166
|
-
"application/json": {
|
|
1167
|
-
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1171
|
-
} else {
|
|
1172
|
-
const serialization = determineQuerySerialization(paramSchema.type);
|
|
1173
|
-
parameters.push({
|
|
1174
|
-
name: param.name,
|
|
1175
|
-
in: "query",
|
|
1176
|
-
required: !param.isOptional,
|
|
1177
|
-
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema,
|
|
1178
|
-
...Object.keys(serialization).length > 0 ? serialization : {}
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
function determineQuerySerialization(schemaType) {
|
|
1185
|
-
const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
1186
|
-
const isArray = typeArray.includes("array");
|
|
1187
|
-
if (isArray) {
|
|
1188
|
-
return { style: "form", explode: true };
|
|
1189
|
-
}
|
|
1190
|
-
return {};
|
|
1191
|
-
}
|
|
1192
|
-
function buildHeaderParameters(operation, ctx, parameters) {
|
|
1193
|
-
if (operation.headerObjectParamIndex === null) return;
|
|
1194
|
-
const headerParam = operation.parameters[operation.headerObjectParamIndex];
|
|
1195
|
-
if (!headerParam) return;
|
|
1196
|
-
const headerSchema = typeToJsonSchema(headerParam.type, ctx);
|
|
1197
|
-
if (!headerSchema.properties) return;
|
|
1198
|
-
const headerObjProps = headerSchema.properties;
|
|
1199
|
-
for (const [propName, propSchema] of Object.entries(headerObjProps)) {
|
|
1200
|
-
const isRequired = headerSchema.required?.includes(propName) ?? false;
|
|
1201
|
-
parameters.push({
|
|
1202
|
-
name: propName,
|
|
1203
|
-
in: "header",
|
|
1204
|
-
required: isRequired,
|
|
1205
|
-
schema: propSchema
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
function buildCookieParameters(operation, ctx, parameters) {
|
|
1210
|
-
if (operation.cookieObjectParamIndex === null) return;
|
|
1211
|
-
const cookieParam = operation.parameters[operation.cookieObjectParamIndex];
|
|
1212
|
-
if (!cookieParam) return;
|
|
1213
|
-
const cookieSchema = typeToJsonSchema(cookieParam.type, ctx);
|
|
1214
|
-
if (!cookieSchema.properties) return;
|
|
1215
|
-
const cookieObjProps = cookieSchema.properties;
|
|
1216
|
-
for (const [propName, propSchema] of Object.entries(cookieObjProps)) {
|
|
1217
|
-
const isRequired = cookieSchema.required?.includes(propName) ?? false;
|
|
1218
|
-
parameters.push({
|
|
1219
|
-
name: propName,
|
|
1220
|
-
in: "cookie",
|
|
1221
|
-
required: isRequired,
|
|
1222
|
-
schema: propSchema,
|
|
1223
|
-
style: "form",
|
|
1224
|
-
explode: true
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
1782
|
|
|
1229
1783
|
// src/compiler/manifest/emit.ts
|
|
1230
|
-
import
|
|
1784
|
+
import ts10 from "typescript";
|
|
1231
1785
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
1232
1786
|
const components = /* @__PURE__ */ new Map();
|
|
1233
1787
|
const ctx = {
|
|
@@ -1249,7 +1803,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1249
1803
|
generator: {
|
|
1250
1804
|
name: "adorn-api",
|
|
1251
1805
|
version,
|
|
1252
|
-
typescript:
|
|
1806
|
+
typescript: ts10.version
|
|
1253
1807
|
},
|
|
1254
1808
|
schemas: {
|
|
1255
1809
|
kind: "openapi-3.1",
|
|
@@ -1299,6 +1853,23 @@ function resolveAndCollectObjectProps2(schema, components) {
|
|
|
1299
1853
|
processSchema(resolved);
|
|
1300
1854
|
return { properties, required };
|
|
1301
1855
|
}
|
|
1856
|
+
function isDeepObjectSchema2(schema, components) {
|
|
1857
|
+
const resolved = resolveSchemaRef2(schema, components);
|
|
1858
|
+
if (resolved.type === "array") {
|
|
1859
|
+
return false;
|
|
1860
|
+
}
|
|
1861
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1862
|
+
return true;
|
|
1863
|
+
}
|
|
1864
|
+
if (resolved.allOf) {
|
|
1865
|
+
for (const branch of resolved.allOf) {
|
|
1866
|
+
if (isDeepObjectSchema2(branch, components)) {
|
|
1867
|
+
return true;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return false;
|
|
1872
|
+
}
|
|
1302
1873
|
function isObjectLikeSchema2(schema, components) {
|
|
1303
1874
|
const resolved = resolveSchemaRef2(schema, components);
|
|
1304
1875
|
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
@@ -1395,6 +1966,7 @@ function buildQueryArgs(op, ctx, args) {
|
|
|
1395
1966
|
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps2(querySchema, ctx.components);
|
|
1396
1967
|
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1397
1968
|
const isRequired = queryRequired.includes(propName) ?? false;
|
|
1969
|
+
const isDeepObject = isDeepObjectSchema2(propSchema, ctx.components);
|
|
1398
1970
|
const isObjectLike = isObjectLikeSchema2(propSchema, ctx.components);
|
|
1399
1971
|
let schemaRef = propSchema.$ref;
|
|
1400
1972
|
if (!schemaRef) {
|
|
@@ -1403,10 +1975,11 @@ function buildQueryArgs(op, ctx, args) {
|
|
|
1403
1975
|
args.query.push({
|
|
1404
1976
|
name: propName,
|
|
1405
1977
|
index: queryParam.index,
|
|
1406
|
-
required:
|
|
1978
|
+
required: isRequired,
|
|
1407
1979
|
schemaRef,
|
|
1408
1980
|
schemaType: propSchema.type,
|
|
1409
|
-
|
|
1981
|
+
serialization: isDeepObject ? { style: "deepObject", explode: true } : void 0,
|
|
1982
|
+
content: !isDeepObject && isObjectLike ? "application/json" : void 0
|
|
1410
1983
|
});
|
|
1411
1984
|
}
|
|
1412
1985
|
}
|
|
@@ -1414,6 +1987,7 @@ function buildQueryArgs(op, ctx, args) {
|
|
|
1414
1987
|
const param = op.parameters[paramIndex];
|
|
1415
1988
|
if (param) {
|
|
1416
1989
|
const paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1990
|
+
const isDeepObject = isDeepObjectSchema2(paramSchema, ctx.components);
|
|
1417
1991
|
const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
|
|
1418
1992
|
const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
|
|
1419
1993
|
args.query.push({
|
|
@@ -1422,7 +1996,8 @@ function buildQueryArgs(op, ctx, args) {
|
|
|
1422
1996
|
required: !param.isOptional,
|
|
1423
1997
|
schemaRef,
|
|
1424
1998
|
schemaType: paramSchema.type,
|
|
1425
|
-
|
|
1999
|
+
serialization: isDeepObject ? { style: "deepObject", explode: true } : void 0,
|
|
2000
|
+
content: !isDeepObject && isObjectLike ? "application/json" : void 0
|
|
1426
2001
|
});
|
|
1427
2002
|
}
|
|
1428
2003
|
}
|
|
@@ -1442,7 +2017,7 @@ function buildHeaderArgs(op, ctx, args) {
|
|
|
1442
2017
|
args.headers.push({
|
|
1443
2018
|
name: propName,
|
|
1444
2019
|
index: headerParam.index,
|
|
1445
|
-
required:
|
|
2020
|
+
required: isRequired,
|
|
1446
2021
|
schemaRef,
|
|
1447
2022
|
schemaType: propSchema.type
|
|
1448
2023
|
});
|
|
@@ -1463,7 +2038,7 @@ function buildCookieArgs(op, ctx, args) {
|
|
|
1463
2038
|
args.cookies.push({
|
|
1464
2039
|
name: propName,
|
|
1465
2040
|
index: cookieParam.index,
|
|
1466
|
-
required:
|
|
2041
|
+
required: isRequired,
|
|
1467
2042
|
schemaRef,
|
|
1468
2043
|
schemaType: propSchema.type,
|
|
1469
2044
|
serialization: { style: "form", explode: true }
|
|
@@ -1479,7 +2054,7 @@ import Ajv from "ajv";
|
|
|
1479
2054
|
import addFormats from "ajv-formats";
|
|
1480
2055
|
var OAS_SCHEMA_ONLY = /* @__PURE__ */ new Set(["discriminator", "xml", "externalDocs", "example"]);
|
|
1481
2056
|
function sanitizeSchemaForAjv(schema) {
|
|
1482
|
-
if (schema
|
|
2057
|
+
if (schema === null || typeof schema !== "object") return schema;
|
|
1483
2058
|
if (Array.isArray(schema)) return schema.map(sanitizeSchemaForAjv);
|
|
1484
2059
|
const out = {};
|
|
1485
2060
|
for (const [k, v] of Object.entries(schema)) {
|
|
@@ -1490,7 +2065,7 @@ function sanitizeSchemaForAjv(schema) {
|
|
|
1490
2065
|
return out;
|
|
1491
2066
|
}
|
|
1492
2067
|
function rewriteComponentRefs(schema) {
|
|
1493
|
-
if (schema
|
|
2068
|
+
if (schema === null || typeof schema !== "object") return schema;
|
|
1494
2069
|
if (Array.isArray(schema)) return schema.map(rewriteComponentRefs);
|
|
1495
2070
|
if (typeof schema.$ref === "string") {
|
|
1496
2071
|
const ref = schema.$ref;
|
|
@@ -1567,16 +2142,13 @@ async function emitPrecompiledValidators(opts) {
|
|
|
1567
2142
|
`;
|
|
1568
2143
|
cjs += ` body: ${v.body ? `exports[${JSON.stringify(v.body)}]` : "undefined"},
|
|
1569
2144
|
`;
|
|
1570
|
-
cjs +=
|
|
1571
|
-
`;
|
|
2145
|
+
cjs += " response: {\n";
|
|
1572
2146
|
for (const [key, id] of Object.entries(v.response)) {
|
|
1573
2147
|
cjs += ` ${JSON.stringify(key)}: exports[${JSON.stringify(id)}],
|
|
1574
2148
|
`;
|
|
1575
2149
|
}
|
|
1576
|
-
cjs +=
|
|
1577
|
-
|
|
1578
|
-
cjs += ` },
|
|
1579
|
-
`;
|
|
2150
|
+
cjs += " }\n";
|
|
2151
|
+
cjs += " },\n";
|
|
1580
2152
|
}
|
|
1581
2153
|
cjs += "};\n";
|
|
1582
2154
|
fs.writeFileSync(cjsPath, cjs, "utf8");
|
|
@@ -1611,7 +2183,6 @@ export function validateResponse(operationId, status, contentType, data) {
|
|
|
1611
2183
|
// src/compiler/cache/isStale.ts
|
|
1612
2184
|
import fs2 from "fs";
|
|
1613
2185
|
import path2 from "path";
|
|
1614
|
-
import "typescript";
|
|
1615
2186
|
function readJson(p) {
|
|
1616
2187
|
try {
|
|
1617
2188
|
return JSON.parse(fs2.readFileSync(p, "utf8"));
|
|
@@ -1668,7 +2239,7 @@ function findLockfile(startDir) {
|
|
|
1668
2239
|
for (const n of names) {
|
|
1669
2240
|
const p = path2.join(dir, n);
|
|
1670
2241
|
const mt = statMtimeMs(p);
|
|
1671
|
-
if (mt
|
|
2242
|
+
if (mt !== null) return { path: p, mtimeMs: mt };
|
|
1672
2243
|
}
|
|
1673
2244
|
const parent = path2.dirname(dir);
|
|
1674
2245
|
if (parent === dir) break;
|
|
@@ -1693,22 +2264,22 @@ async function isStale(params) {
|
|
|
1693
2264
|
const chain = collectTsconfigChain(tsconfigAbs);
|
|
1694
2265
|
for (const cfg of chain) {
|
|
1695
2266
|
const mt = statMtimeMs(cfg);
|
|
1696
|
-
if (mt
|
|
2267
|
+
if (mt === null) return { stale: true, reason: "config-missing", detail: cfg };
|
|
1697
2268
|
const cachedMt = cache.project.configFiles[cfg];
|
|
1698
|
-
if (cachedMt
|
|
2269
|
+
if (cachedMt === null || Math.abs(cachedMt - mt) > 1e-4) {
|
|
1699
2270
|
return { stale: true, reason: "config-updated", detail: cfg };
|
|
1700
2271
|
}
|
|
1701
2272
|
}
|
|
1702
2273
|
if (cache.project.lockfile?.path) {
|
|
1703
2274
|
const mt = statMtimeMs(cache.project.lockfile.path);
|
|
1704
|
-
if (mt
|
|
2275
|
+
if (mt === null) return { stale: true, reason: "lockfile-missing", detail: cache.project.lockfile.path };
|
|
1705
2276
|
if (Math.abs(cache.project.lockfile.mtimeMs - mt) > 1e-4) {
|
|
1706
2277
|
return { stale: true, reason: "lockfile-updated", detail: cache.project.lockfile.path };
|
|
1707
2278
|
}
|
|
1708
2279
|
}
|
|
1709
2280
|
for (const [file, cachedMt] of Object.entries(cache.inputs)) {
|
|
1710
2281
|
const mt = statMtimeMs(file);
|
|
1711
|
-
if (mt
|
|
2282
|
+
if (mt === null) return { stale: true, reason: "input-missing", detail: file };
|
|
1712
2283
|
if (Math.abs(cachedMt - mt) > 1e-4) return { stale: true, reason: "input-updated", detail: file };
|
|
1713
2284
|
}
|
|
1714
2285
|
return { stale: false, reason: "up-to-date" };
|
|
@@ -1717,7 +2288,7 @@ async function isStale(params) {
|
|
|
1717
2288
|
// src/compiler/cache/writeCache.ts
|
|
1718
2289
|
import fs3 from "fs";
|
|
1719
2290
|
import path3 from "path";
|
|
1720
|
-
import
|
|
2291
|
+
import ts11 from "typescript";
|
|
1721
2292
|
function statMtimeMs2(p) {
|
|
1722
2293
|
return fs3.statSync(p).mtimeMs;
|
|
1723
2294
|
}
|
|
@@ -1750,7 +2321,7 @@ function writeCache(params) {
|
|
|
1750
2321
|
generator: {
|
|
1751
2322
|
name: "adorn-api",
|
|
1752
2323
|
version: params.adornVersion,
|
|
1753
|
-
typescript:
|
|
2324
|
+
typescript: ts11.version
|
|
1754
2325
|
},
|
|
1755
2326
|
project: {
|
|
1756
2327
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -1763,7 +2334,7 @@ function writeCache(params) {
|
|
|
1763
2334
|
}
|
|
1764
2335
|
|
|
1765
2336
|
// src/cli.ts
|
|
1766
|
-
import
|
|
2337
|
+
import ts12 from "typescript";
|
|
1767
2338
|
import process from "process";
|
|
1768
2339
|
var ADORN_VERSION = "0.1.0";
|
|
1769
2340
|
function log(msg) {
|
|
@@ -1811,7 +2382,7 @@ async function buildCommand(args) {
|
|
|
1811
2382
|
outDir: outputDir,
|
|
1812
2383
|
project: projectPath,
|
|
1813
2384
|
adornVersion: ADORN_VERSION,
|
|
1814
|
-
typescriptVersion:
|
|
2385
|
+
typescriptVersion: ts12.version
|
|
1815
2386
|
});
|
|
1816
2387
|
if (!stale.stale) {
|
|
1817
2388
|
log("adorn-api: artifacts up-to-date");
|