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