adorn-api 1.0.23 → 1.0.24
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/.eslintignore +3 -0
- package/.eslintrc.cjs +30 -0
- package/README.md +375 -531
- package/dist/core/express-adapter.d.ts +27 -0
- package/dist/core/express-adapter.d.ts.map +1 -0
- package/dist/core/express-adapter.js +146 -0
- package/dist/core/express-adapter.js.map +1 -0
- package/dist/core/http-error.d.ts +7 -0
- package/dist/core/http-error.d.ts.map +1 -0
- package/dist/core/http-error.js +14 -0
- package/dist/core/http-error.js.map +1 -0
- package/dist/decorators/controller.decorator.d.ts +2 -0
- package/dist/decorators/controller.decorator.d.ts.map +1 -0
- package/dist/decorators/controller.decorator.js +26 -0
- package/dist/decorators/controller.decorator.js.map +1 -0
- package/dist/decorators/create.decorator.d.ts +8 -0
- package/dist/decorators/create.decorator.d.ts.map +1 -0
- package/dist/decorators/create.decorator.js +67 -0
- package/dist/decorators/create.decorator.js.map +1 -0
- package/dist/decorators/http-method.decorator.d.ts +16 -0
- package/dist/decorators/http-method.decorator.d.ts.map +1 -0
- package/dist/decorators/http-method.decorator.js +117 -0
- package/dist/decorators/http-method.decorator.js.map +1 -0
- package/dist/decorators/http-params.d.ts +17 -0
- package/dist/decorators/http-params.d.ts.map +1 -0
- package/dist/decorators/http-params.js +26 -0
- package/dist/decorators/http-params.js.map +1 -0
- package/dist/decorators/index.d.ts +10 -5
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +14 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/list.decorator.d.ts +18 -0
- package/dist/decorators/list.decorator.d.ts.map +1 -0
- package/dist/decorators/list.decorator.js +99 -0
- package/dist/decorators/list.decorator.js.map +1 -0
- package/dist/decorators/middleware.decorator.d.ts +4 -0
- package/dist/decorators/middleware.decorator.d.ts.map +1 -0
- package/dist/decorators/middleware.decorator.js +34 -0
- package/dist/decorators/middleware.decorator.js.map +1 -0
- package/dist/decorators/response.decorator.d.ts +8 -0
- package/dist/decorators/response.decorator.d.ts.map +1 -0
- package/dist/decorators/response.decorator.js +44 -0
- package/dist/decorators/response.decorator.js.map +1 -0
- package/dist/decorators/route-options.d.ts +14 -0
- package/dist/decorators/route-options.d.ts.map +1 -0
- package/dist/decorators/route-options.js +22 -0
- package/dist/decorators/route-options.js.map +1 -0
- package/dist/decorators/schema.decorator.d.ts +82 -0
- package/dist/decorators/schema.decorator.d.ts.map +1 -0
- package/dist/decorators/schema.decorator.js +123 -0
- package/dist/decorators/schema.decorator.js.map +1 -0
- package/dist/decorators/update.decorator.d.ts +8 -0
- package/dist/decorators/update.decorator.d.ts.map +1 -0
- package/dist/decorators/update.decorator.js +63 -0
- package/dist/decorators/update.decorator.js.map +1 -0
- package/dist/index.d.ts +11 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -637
- package/dist/index.js.map +1 -1
- package/dist/metadata/metadata-storage.d.ts +38 -0
- package/dist/metadata/metadata-storage.d.ts.map +1 -0
- package/dist/metadata/metadata-storage.js +102 -0
- package/dist/metadata/metadata-storage.js.map +1 -0
- package/dist/metal-orm-integration/dto-helper.d.ts +5 -0
- package/dist/metal-orm-integration/dto-helper.d.ts.map +1 -0
- package/dist/metal-orm-integration/dto-helper.js +48 -0
- package/dist/metal-orm-integration/dto-helper.js.map +1 -0
- package/dist/metal-orm-integration/dto-response.decorator.d.ts +4 -0
- package/dist/metal-orm-integration/dto-response.decorator.d.ts.map +1 -0
- package/dist/metal-orm-integration/dto-response.decorator.js +69 -0
- package/dist/metal-orm-integration/dto-response.decorator.js.map +1 -0
- package/dist/metal-orm-integration/entity-schema-builder.d.ts +20 -0
- package/dist/metal-orm-integration/entity-schema-builder.d.ts.map +1 -0
- package/dist/metal-orm-integration/entity-schema-builder.js +356 -0
- package/dist/metal-orm-integration/entity-schema-builder.js.map +1 -0
- package/dist/metal-orm-integration/index.d.ts +5 -0
- package/dist/metal-orm-integration/index.d.ts.map +1 -0
- package/dist/metal-orm-integration/index.js +5 -0
- package/dist/metal-orm-integration/index.js.map +1 -0
- package/dist/metal-orm-integration/schema-modifier.d.ts +11 -0
- package/dist/metal-orm-integration/schema-modifier.d.ts.map +1 -0
- package/dist/metal-orm-integration/schema-modifier.js +62 -0
- package/dist/metal-orm-integration/schema-modifier.js.map +1 -0
- package/dist/openapi/index.d.ts +4 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +4 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/openapi/openapi-generator.d.ts +22 -0
- package/dist/openapi/openapi-generator.d.ts.map +1 -0
- package/dist/openapi/openapi-generator.js +428 -0
- package/dist/openapi/openapi-generator.js.map +1 -0
- package/dist/openapi/swagger-ui.d.ts +11 -0
- package/dist/openapi/swagger-ui.d.ts.map +1 -0
- package/dist/openapi/swagger-ui.js +20 -0
- package/dist/openapi/swagger-ui.js.map +1 -0
- package/dist/openapi/zod-to-openapi.d.ts +4 -0
- package/dist/openapi/zod-to-openapi.d.ts.map +1 -0
- package/dist/openapi/zod-to-openapi.js +184 -0
- package/dist/openapi/zod-to-openapi.js.map +1 -0
- package/dist/types/common.d.ts +4 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/controller.d.ts +14 -0
- package/dist/types/controller.d.ts.map +1 -0
- package/dist/types/controller.js +2 -0
- package/dist/types/controller.js.map +1 -0
- package/dist/types/metadata.d.ts +48 -0
- package/dist/types/metadata.d.ts.map +1 -0
- package/dist/types/metadata.js +2 -0
- package/dist/types/metadata.js.map +1 -0
- package/dist/types/openapi.d.ts +30 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +2 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/validation/zod-adapter.d.ts +15 -0
- package/dist/validation/zod-adapter.d.ts.map +1 -0
- package/dist/validation/zod-adapter.js +61 -0
- package/dist/validation/zod-adapter.js.map +1 -0
- package/examples/basic/app.ts +15 -0
- package/examples/basic/index.ts +6 -0
- package/examples/basic/user.controller.ts +35 -0
- package/examples/basic/user.dtos.ts +23 -0
- package/examples/metal-orm-sqlite/app.ts +18 -0
- package/examples/metal-orm-sqlite/db.ts +90 -0
- package/examples/metal-orm-sqlite/index.ts +6 -0
- package/examples/metal-orm-sqlite/post.controller.ts +209 -0
- package/examples/metal-orm-sqlite/post.dtos.ts +78 -0
- package/examples/metal-orm-sqlite/post.entity.ts +24 -0
- package/examples/metal-orm-sqlite/user.controller.helpers.ts +305 -0
- package/examples/metal-orm-sqlite/user.controller.ts +231 -0
- package/examples/metal-orm-sqlite/user.dtos.ts +88 -0
- package/examples/metal-orm-sqlite/user.entity.ts +21 -0
- package/examples/metal-orm-sqlite-music/album.controller.ts +278 -0
- package/examples/metal-orm-sqlite-music/album.dtos.ts +85 -0
- package/examples/metal-orm-sqlite-music/album.entity.ts +28 -0
- package/examples/metal-orm-sqlite-music/app.ts +19 -0
- package/examples/metal-orm-sqlite-music/artist.controller.ts +272 -0
- package/examples/metal-orm-sqlite-music/artist.dtos.ts +68 -0
- package/examples/metal-orm-sqlite-music/artist.entity.ts +27 -0
- package/examples/metal-orm-sqlite-music/db.ts +148 -0
- package/examples/metal-orm-sqlite-music/index.ts +6 -0
- package/examples/metal-orm-sqlite-music/track.controller.ts +221 -0
- package/examples/metal-orm-sqlite-music/track.dtos.ts +82 -0
- package/examples/metal-orm-sqlite-music/track.entity.ts +27 -0
- package/examples/openapi/health.controller.ts +11 -0
- package/examples/openapi/health.dto.ts +7 -0
- package/examples/openapi/index.ts +12 -0
- package/examples/restful/app.ts +15 -0
- package/examples/restful/index.ts +9 -0
- package/examples/restful/task.controller.ts +118 -0
- package/examples/restful/task.dtos.ts +66 -0
- package/examples/restful/task.store.ts +95 -0
- package/examples/tsconfig.json +8 -0
- package/examples/utils/start-server.ts +56 -0
- package/package.json +33 -97
- package/scripts/run-example.js +29 -0
- package/src/adapter/express.ts +589 -0
- package/src/adapter/metal-orm/convention-overrides.ts +115 -0
- package/src/adapter/metal-orm/crud-dtos.ts +141 -0
- package/src/adapter/metal-orm/dto.ts +20 -0
- package/src/adapter/metal-orm/error-dtos.ts +51 -0
- package/src/adapter/metal-orm/field-builder.ts +185 -0
- package/src/adapter/metal-orm/filters.ts +52 -0
- package/src/adapter/metal-orm/index.ts +66 -0
- package/src/adapter/metal-orm/paged-dtos.ts +94 -0
- package/src/adapter/metal-orm/pagination.ts +28 -0
- package/src/adapter/metal-orm/types.ts +250 -0
- package/src/adapter/metal-orm/utils.ts +36 -0
- package/src/adapter/metal-orm.test.ts +439 -0
- package/src/core/__tests__/coerce.test.ts +39 -0
- package/src/core/__tests__/dto-compose.test.ts +68 -0
- package/src/core/__tests__/schema-builder.test.ts +82 -0
- package/src/core/coerce.ts +190 -0
- package/src/core/decorators.ts +645 -0
- package/src/core/errors.ts +55 -0
- package/src/core/metadata.ts +110 -0
- package/src/core/openapi.ts +282 -0
- package/src/core/schema-builder.ts +287 -0
- package/src/core/schema.ts +400 -0
- package/src/core/types.ts +14 -0
- package/src/e2e/http-error.e2e.test.ts +52 -0
- package/src/e2e/sqlite-metal-orm.e2e.test.ts +174 -0
- package/src/e2e/sqlite.e2e.test.ts +126 -0
- package/src/index.ts +8 -0
- package/tsconfig.eslint.json +7 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +8 -0
- package/dist/adapter/express/auth.d.ts +0 -13
- package/dist/adapter/express/auth.d.ts.map +0 -1
- package/dist/adapter/express/bootstrap.d.ts +0 -40
- package/dist/adapter/express/bootstrap.d.ts.map +0 -1
- package/dist/adapter/express/coercion.d.ts +0 -102
- package/dist/adapter/express/coercion.d.ts.map +0 -1
- package/dist/adapter/express/index.d.ts +0 -6
- package/dist/adapter/express/index.d.ts.map +0 -1
- package/dist/adapter/express/merge.d.ts +0 -45
- package/dist/adapter/express/merge.d.ts.map +0 -1
- package/dist/adapter/express/openapi.d.ts +0 -66
- package/dist/adapter/express/openapi.d.ts.map +0 -1
- package/dist/adapter/express/router.d.ts +0 -10
- package/dist/adapter/express/router.d.ts.map +0 -1
- package/dist/adapter/express/swagger.d.ts +0 -18
- package/dist/adapter/express/swagger.d.ts.map +0 -1
- package/dist/adapter/express/types.d.ts +0 -110
- package/dist/adapter/express/types.d.ts.map +0 -1
- package/dist/adapter/express/validation.d.ts +0 -27
- package/dist/adapter/express/validation.d.ts.map +0 -1
- package/dist/cli/progress.d.ts +0 -124
- package/dist/cli/progress.d.ts.map +0 -1
- package/dist/cli.cjs +0 -4622
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -4603
- package/dist/cli.js.map +0 -1
- package/dist/compiler/analyze/index.d.ts +0 -5
- package/dist/compiler/analyze/index.d.ts.map +0 -1
- package/dist/compiler/analyze/scanControllers.d.ts +0 -88
- package/dist/compiler/analyze/scanControllers.d.ts.map +0 -1
- package/dist/compiler/cache/isStale.d.ts +0 -46
- package/dist/compiler/cache/isStale.d.ts.map +0 -1
- package/dist/compiler/cache/loadArtifacts.d.ts +0 -149
- package/dist/compiler/cache/loadArtifacts.d.ts.map +0 -1
- package/dist/compiler/cache/schema.d.ts +0 -32
- package/dist/compiler/cache/schema.d.ts.map +0 -1
- package/dist/compiler/cache/writeCache.d.ts +0 -14
- package/dist/compiler/cache/writeCache.d.ts.map +0 -1
- package/dist/compiler/gems.d.ts +0 -75
- package/dist/compiler/gems.d.ts.map +0 -1
- package/dist/compiler/generator/index.d.ts +0 -7
- package/dist/compiler/generator/index.d.ts.map +0 -1
- package/dist/compiler/generator/manifest.d.ts +0 -23
- package/dist/compiler/generator/manifest.d.ts.map +0 -1
- package/dist/compiler/generator/openapi.d.ts +0 -118
- package/dist/compiler/generator/openapi.d.ts.map +0 -1
- package/dist/compiler/graph/builder.d.ts +0 -24
- package/dist/compiler/graph/builder.d.ts.map +0 -1
- package/dist/compiler/graph/index.d.ts +0 -7
- package/dist/compiler/graph/index.d.ts.map +0 -1
- package/dist/compiler/graph/schemaGraph.d.ts +0 -67
- package/dist/compiler/graph/schemaGraph.d.ts.map +0 -1
- package/dist/compiler/graph/types.d.ts +0 -203
- package/dist/compiler/graph/types.d.ts.map +0 -1
- package/dist/compiler/index.d.ts +0 -12
- package/dist/compiler/index.d.ts.map +0 -1
- package/dist/compiler/ir/index.d.ts +0 -7
- package/dist/compiler/ir/index.d.ts.map +0 -1
- package/dist/compiler/ir/pipeline.d.ts +0 -82
- package/dist/compiler/ir/pipeline.d.ts.map +0 -1
- package/dist/compiler/ir/stages.d.ts +0 -40
- package/dist/compiler/ir/stages.d.ts.map +0 -1
- package/dist/compiler/ir/visitor.d.ts +0 -98
- package/dist/compiler/ir/visitor.d.ts.map +0 -1
- package/dist/compiler/manifest/emit.d.ts +0 -21
- package/dist/compiler/manifest/emit.d.ts.map +0 -1
- package/dist/compiler/manifest/format.d.ts +0 -119
- package/dist/compiler/manifest/format.d.ts.map +0 -1
- package/dist/compiler/manifest/index.d.ts +0 -6
- package/dist/compiler/manifest/index.d.ts.map +0 -1
- package/dist/compiler/runner/createProgram.d.ts +0 -24
- package/dist/compiler/runner/createProgram.d.ts.map +0 -1
- package/dist/compiler/runner/index.d.ts +0 -5
- package/dist/compiler/runner/index.d.ts.map +0 -1
- package/dist/compiler/schema/extractAnnotations.d.ts +0 -57
- package/dist/compiler/schema/extractAnnotations.d.ts.map +0 -1
- package/dist/compiler/schema/index.d.ts +0 -10
- package/dist/compiler/schema/index.d.ts.map +0 -1
- package/dist/compiler/schema/intersectionHandler.d.ts +0 -44
- package/dist/compiler/schema/intersectionHandler.d.ts.map +0 -1
- package/dist/compiler/schema/objectHandler.d.ts +0 -146
- package/dist/compiler/schema/objectHandler.d.ts.map +0 -1
- package/dist/compiler/schema/openapi.d.ts +0 -71
- package/dist/compiler/schema/openapi.d.ts.map +0 -1
- package/dist/compiler/schema/parameters.d.ts +0 -90
- package/dist/compiler/schema/parameters.d.ts.map +0 -1
- package/dist/compiler/schema/partitioner.d.ts +0 -85
- package/dist/compiler/schema/partitioner.d.ts.map +0 -1
- package/dist/compiler/schema/primitives.d.ts +0 -68
- package/dist/compiler/schema/primitives.d.ts.map +0 -1
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts +0 -95
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts.map +0 -1
- package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts +0 -13
- package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts.map +0 -1
- package/dist/compiler/schema/serviceCallAnalyzer.d.ts +0 -102
- package/dist/compiler/schema/serviceCallAnalyzer.d.ts.map +0 -1
- package/dist/compiler/schema/splitOpenapi.d.ts +0 -46
- package/dist/compiler/schema/splitOpenapi.d.ts.map +0 -1
- package/dist/compiler/schema/typeToJsonSchema.d.ts +0 -26
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +0 -1
- package/dist/compiler/schema/types.d.ts +0 -70
- package/dist/compiler/schema/types.d.ts.map +0 -1
- package/dist/compiler/schema/unionHandler.d.ts +0 -70
- package/dist/compiler/schema/unionHandler.d.ts.map +0 -1
- package/dist/compiler/transform/dedup.d.ts +0 -35
- package/dist/compiler/transform/dedup.d.ts.map +0 -1
- package/dist/compiler/transform/flatten.d.ts +0 -50
- package/dist/compiler/transform/flatten.d.ts.map +0 -1
- package/dist/compiler/transform/index.d.ts +0 -7
- package/dist/compiler/transform/index.d.ts.map +0 -1
- package/dist/compiler/transform/inline.d.ts +0 -46
- package/dist/compiler/transform/inline.d.ts.map +0 -1
- package/dist/compiler/validation/emitPrecompiledValidators.d.ts +0 -62
- package/dist/compiler/validation/emitPrecompiledValidators.d.ts.map +0 -1
- package/dist/compiler/validation/index.d.ts +0 -5
- package/dist/compiler/validation/index.d.ts.map +0 -1
- package/dist/decorators/Auth.d.ts +0 -22
- package/dist/decorators/Auth.d.ts.map +0 -1
- package/dist/decorators/Controller.d.ts +0 -17
- package/dist/decorators/Controller.d.ts.map +0 -1
- package/dist/decorators/Public.d.ts +0 -15
- package/dist/decorators/Public.d.ts.map +0 -1
- package/dist/decorators/Use.d.ts +0 -23
- package/dist/decorators/Use.d.ts.map +0 -1
- package/dist/decorators/methods.d.ts +0 -26
- package/dist/decorators/methods.d.ts.map +0 -1
- package/dist/express.cjs +0 -1186
- package/dist/express.cjs.map +0 -1
- package/dist/express.d.ts +0 -8
- package/dist/express.d.ts.map +0 -1
- package/dist/express.js +0 -1150
- package/dist/express.js.map +0 -1
- package/dist/http.d.ts +0 -33
- package/dist/http.d.ts.map +0 -1
- package/dist/index.cjs +0 -724
- package/dist/index.cjs.map +0 -1
- package/dist/metal/applyListQuery.d.ts +0 -100
- package/dist/metal/applyListQuery.d.ts.map +0 -1
- package/dist/metal/index.cjs +0 -278
- package/dist/metal/index.cjs.map +0 -1
- package/dist/metal/index.d.ts +0 -15
- package/dist/metal/index.d.ts.map +0 -1
- package/dist/metal/index.js +0 -243
- package/dist/metal/index.js.map +0 -1
- package/dist/metal/listQuery.d.ts +0 -26
- package/dist/metal/listQuery.d.ts.map +0 -1
- package/dist/metal/queryOptions.d.ts +0 -16
- package/dist/metal/queryOptions.d.ts.map +0 -1
- package/dist/metal/readMetalBag.d.ts +0 -69
- package/dist/metal/readMetalBag.d.ts.map +0 -1
- package/dist/metal/registerMetalEntities.d.ts +0 -26
- package/dist/metal/registerMetalEntities.d.ts.map +0 -1
- package/dist/metal/schemaFromEntity.d.ts +0 -41
- package/dist/metal/schemaFromEntity.d.ts.map +0 -1
- package/dist/metal/searchWhere.d.ts +0 -97
- package/dist/metal/searchWhere.d.ts.map +0 -1
- package/dist/metal/symbolMetadata.d.ts +0 -8
- package/dist/metal/symbolMetadata.d.ts.map +0 -1
- package/dist/runtime/auth/runtime.d.ts +0 -183
- package/dist/runtime/auth/runtime.d.ts.map +0 -1
- package/dist/runtime/metadata/bucket.d.ts +0 -2
- package/dist/runtime/metadata/bucket.d.ts.map +0 -1
- package/dist/runtime/metadata/key.d.ts +0 -2
- package/dist/runtime/metadata/key.d.ts.map +0 -1
- package/dist/runtime/metadata/read.d.ts +0 -2
- package/dist/runtime/metadata/read.d.ts.map +0 -1
- package/dist/runtime/metadata/types.d.ts +0 -95
- package/dist/runtime/metadata/types.d.ts.map +0 -1
- package/dist/runtime/polyfill.d.ts +0 -2
- package/dist/runtime/polyfill.d.ts.map +0 -1
- package/dist/runtime/upload.d.ts +0 -44
- package/dist/runtime/upload.d.ts.map +0 -1
- package/dist/runtime/validation/ajv.d.ts +0 -120
- package/dist/runtime/validation/ajv.d.ts.map +0 -1
- package/dist/runtime/validation/index.d.ts +0 -11
- package/dist/runtime/validation/index.d.ts.map +0 -1
- package/dist/schema/decorators.d.ts +0 -37
- package/dist/schema/decorators.d.ts.map +0 -1
- package/dist/schema/index.cjs +0 -214
- package/dist/schema/index.cjs.map +0 -1
- package/dist/schema/index.d.ts +0 -2
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -163
- package/dist/schema/index.js.map +0 -1
- package/dist/scripts/adorn-example.cjs +0 -404
- package/dist/scripts/adorn-example.cjs.map +0 -1
- package/dist/utils/operationId.d.ts +0 -2
- package/dist/utils/operationId.d.ts.map +0 -1
- package/dist/utils/path.d.ts +0 -2
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/port.d.ts +0 -9
- package/dist/utils/port.d.ts.map +0 -1
package/dist/cli.cjs
DELETED
|
@@ -1,4622 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
-
mod
|
|
24
|
-
));
|
|
25
|
-
|
|
26
|
-
// src/cli.ts
|
|
27
|
-
var import_node_fs6 = require("fs");
|
|
28
|
-
var import_node_path6 = require("path");
|
|
29
|
-
var import_node_url = require("url");
|
|
30
|
-
|
|
31
|
-
// src/compiler/runner/createProgram.ts
|
|
32
|
-
var import_typescript = __toESM(require("typescript"), 1);
|
|
33
|
-
var import_node_fs = require("fs");
|
|
34
|
-
var import_node_path = require("path");
|
|
35
|
-
function createProgramFromConfig(tsconfigPath) {
|
|
36
|
-
const absolutePath = (0, import_node_path.resolve)(tsconfigPath);
|
|
37
|
-
const configDir = (0, import_node_path.dirname)(absolutePath);
|
|
38
|
-
const configFile = import_typescript.default.readConfigFile(absolutePath, (path4) => (0, import_node_fs.readFileSync)(path4, "utf-8"));
|
|
39
|
-
if (configFile.error) {
|
|
40
|
-
throw new Error(`Failed to read tsconfig: ${import_typescript.default.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`);
|
|
41
|
-
}
|
|
42
|
-
const parsed = import_typescript.default.parseJsonConfigFileContent(configFile.config, import_typescript.default.sys, configDir);
|
|
43
|
-
if (parsed.errors.length > 0) {
|
|
44
|
-
const messages = parsed.errors.map((e) => import_typescript.default.flattenDiagnosticMessageText(e.messageText, "\n"));
|
|
45
|
-
throw new Error(`Failed to parse tsconfig:
|
|
46
|
-
${messages.join("\n")}`);
|
|
47
|
-
}
|
|
48
|
-
const program = import_typescript.default.createProgram(parsed.fileNames, parsed.options);
|
|
49
|
-
const checker = program.getTypeChecker();
|
|
50
|
-
const sourceFiles = program.getSourceFiles().filter(
|
|
51
|
-
(sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules")
|
|
52
|
-
);
|
|
53
|
-
return { program, checker, sourceFiles };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// src/compiler/analyze/scanControllers.ts
|
|
57
|
-
var import_typescript2 = __toESM(require("typescript"), 1);
|
|
58
|
-
|
|
59
|
-
// src/utils/operationId.ts
|
|
60
|
-
function defaultOperationId(controllerName, methodName) {
|
|
61
|
-
return `${controllerName}_${methodName}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// src/compiler/analyze/scanControllers.ts
|
|
65
|
-
function scanControllers(sourceFiles, checker) {
|
|
66
|
-
const controllers = [];
|
|
67
|
-
for (const sourceFile of sourceFiles) {
|
|
68
|
-
import_typescript2.default.forEachChild(sourceFile, (node) => {
|
|
69
|
-
if (import_typescript2.default.isClassDeclaration(node) && node.name) {
|
|
70
|
-
const controller = analyzeClass(node, sourceFile, checker);
|
|
71
|
-
if (controller) {
|
|
72
|
-
controllers.push(controller);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return controllers;
|
|
78
|
-
}
|
|
79
|
-
function analyzeClass(node, sourceFile, checker) {
|
|
80
|
-
if (!node.name) return null;
|
|
81
|
-
const controllerDecorator = findDecorator(node, "Controller");
|
|
82
|
-
if (!controllerDecorator) return null;
|
|
83
|
-
const basePath = extractDecoratorStringArg(controllerDecorator) ?? "/";
|
|
84
|
-
const className = node.name.text;
|
|
85
|
-
const consumes = extractClassConsumes(node, checker);
|
|
86
|
-
const produces = extractClassProduces(node, checker);
|
|
87
|
-
const operations = [];
|
|
88
|
-
for (const member of node.members) {
|
|
89
|
-
if (import_typescript2.default.isMethodDeclaration(member) && member.name) {
|
|
90
|
-
const operation = analyzeMethod(member, className, checker);
|
|
91
|
-
if (operation) {
|
|
92
|
-
operations.push(operation);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (operations.length === 0) return null;
|
|
97
|
-
return {
|
|
98
|
-
className,
|
|
99
|
-
basePath,
|
|
100
|
-
sourceFile,
|
|
101
|
-
classDeclaration: node,
|
|
102
|
-
operations,
|
|
103
|
-
consumes,
|
|
104
|
-
produces
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function extractClassConsumes(node, _checker) {
|
|
108
|
-
const decorator = findDecorator(node, "Consumes");
|
|
109
|
-
if (!decorator) return void 0;
|
|
110
|
-
const callExpr = decorator.expression;
|
|
111
|
-
if (!import_typescript2.default.isCallExpression(callExpr)) return void 0;
|
|
112
|
-
const args = callExpr.arguments;
|
|
113
|
-
if (args.length === 0) return void 0;
|
|
114
|
-
const firstArg = args[0];
|
|
115
|
-
if (import_typescript2.default.isStringLiteral(firstArg)) {
|
|
116
|
-
return [firstArg.text];
|
|
117
|
-
}
|
|
118
|
-
if (import_typescript2.default.isArrayLiteralExpression(firstArg)) {
|
|
119
|
-
return firstArg.elements.filter(import_typescript2.default.isStringLiteral).map((e) => e.text);
|
|
120
|
-
}
|
|
121
|
-
return void 0;
|
|
122
|
-
}
|
|
123
|
-
function extractClassProduces(node, _checker) {
|
|
124
|
-
const decorator = findDecorator(node, "Produces");
|
|
125
|
-
if (!decorator) return void 0;
|
|
126
|
-
const callExpr = decorator.expression;
|
|
127
|
-
if (!import_typescript2.default.isCallExpression(callExpr)) return void 0;
|
|
128
|
-
const args = callExpr.arguments;
|
|
129
|
-
if (args.length === 0) return void 0;
|
|
130
|
-
const firstArg = args[0];
|
|
131
|
-
if (import_typescript2.default.isStringLiteral(firstArg)) {
|
|
132
|
-
return [firstArg.text];
|
|
133
|
-
}
|
|
134
|
-
if (import_typescript2.default.isArrayLiteralExpression(firstArg)) {
|
|
135
|
-
return firstArg.elements.filter(import_typescript2.default.isStringLiteral).map((e) => e.text);
|
|
136
|
-
}
|
|
137
|
-
return void 0;
|
|
138
|
-
}
|
|
139
|
-
function analyzeMethod(node, className, checker) {
|
|
140
|
-
const methodName = import_typescript2.default.isIdentifier(node.name) ? node.name.text : null;
|
|
141
|
-
if (!methodName) return null;
|
|
142
|
-
const httpMethods = ["Get", "Post", "Put", "Patch", "Delete"];
|
|
143
|
-
let httpMethod = null;
|
|
144
|
-
let path4 = "/";
|
|
145
|
-
for (const method of httpMethods) {
|
|
146
|
-
const decorator = findDecorator(node, method);
|
|
147
|
-
if (decorator) {
|
|
148
|
-
httpMethod = method.toUpperCase();
|
|
149
|
-
path4 = extractDecoratorStringArg(decorator) ?? "/";
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (!httpMethod) return null;
|
|
154
|
-
const signature = checker.getSignatureFromDeclaration(node);
|
|
155
|
-
if (!signature) return null;
|
|
156
|
-
let returnType = checker.getReturnTypeOfSignature(signature);
|
|
157
|
-
returnType = unwrapPromise(returnType, checker);
|
|
158
|
-
const returnTypeNode = unwrapPromiseTypeNode(node.type);
|
|
159
|
-
const parameters = [];
|
|
160
|
-
for (let i = 0; i < node.parameters.length; i++) {
|
|
161
|
-
const param = node.parameters[i];
|
|
162
|
-
const paramName = import_typescript2.default.isIdentifier(param.name) ? param.name.text : `arg${i}`;
|
|
163
|
-
const paramType = checker.getTypeAtLocation(param);
|
|
164
|
-
const isOptional = !!param.questionToken || !!param.initializer;
|
|
165
|
-
parameters.push({
|
|
166
|
-
name: paramName,
|
|
167
|
-
index: i,
|
|
168
|
-
type: paramType,
|
|
169
|
-
isOptional,
|
|
170
|
-
paramNode: param
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
const pathParamNames = extractPathParams(path4);
|
|
174
|
-
const pathParamIndices = matchPathParamsToIndices(pathParamNames, parameters);
|
|
175
|
-
const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
|
|
176
|
-
return {
|
|
177
|
-
methodName,
|
|
178
|
-
httpMethod,
|
|
179
|
-
path: path4,
|
|
180
|
-
operationId: defaultOperationId(className, methodName),
|
|
181
|
-
methodDeclaration: node,
|
|
182
|
-
returnType,
|
|
183
|
-
returnTypeNode,
|
|
184
|
-
parameters,
|
|
185
|
-
pathParamIndices,
|
|
186
|
-
bodyParamIndex,
|
|
187
|
-
queryParamIndices,
|
|
188
|
-
queryObjectParamIndex,
|
|
189
|
-
headerObjectParamIndex,
|
|
190
|
-
cookieObjectParamIndex,
|
|
191
|
-
bodyContentType
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function extractPathParams(path4) {
|
|
195
|
-
const matches = path4.match(/:([^/]+)/g);
|
|
196
|
-
if (!matches) return [];
|
|
197
|
-
return matches.map((m) => m.slice(1));
|
|
198
|
-
}
|
|
199
|
-
function matchPathParamsToIndices(pathParamNames, parameters) {
|
|
200
|
-
const indices = [];
|
|
201
|
-
for (const name of pathParamNames) {
|
|
202
|
-
const param = parameters.find((p) => p.name === name);
|
|
203
|
-
if (param) {
|
|
204
|
-
indices.push(param.index);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return indices;
|
|
208
|
-
}
|
|
209
|
-
function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
|
|
210
|
-
const usedIndices = new Set(pathParamIndices);
|
|
211
|
-
const queryParamIndices = [];
|
|
212
|
-
let bodyParamIndex = null;
|
|
213
|
-
let queryObjectParamIndex = null;
|
|
214
|
-
let headerObjectParamIndex = null;
|
|
215
|
-
let cookieObjectParamIndex = null;
|
|
216
|
-
const isBodyMethod = ["POST", "PUT", "PATCH"].includes(httpMethod);
|
|
217
|
-
for (let i = 0; i < parameters.length; i++) {
|
|
218
|
-
const param = parameters[i];
|
|
219
|
-
if (usedIndices.has(i)) continue;
|
|
220
|
-
const nonNullableType = checker.getNonNullableType(param.type);
|
|
221
|
-
const typeStr = getTypeName(param.type) || getTypeName(nonNullableType);
|
|
222
|
-
if (typeStr === "Body") {
|
|
223
|
-
bodyParamIndex = i;
|
|
224
|
-
usedIndices.add(i);
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (typeStr === "Query") {
|
|
228
|
-
queryObjectParamIndex = i;
|
|
229
|
-
usedIndices.add(i);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (typeStr === "Headers") {
|
|
233
|
-
headerObjectParamIndex = i;
|
|
234
|
-
usedIndices.add(i);
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
if (typeStr === "Cookies") {
|
|
238
|
-
cookieObjectParamIndex = i;
|
|
239
|
-
usedIndices.add(i);
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (isBodyMethod && bodyParamIndex === null) {
|
|
243
|
-
bodyParamIndex = i;
|
|
244
|
-
usedIndices.add(i);
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
const isObj = isObjectType(nonNullableType, checker);
|
|
248
|
-
if (isObj && queryObjectParamIndex === null && !isBodyMethod) {
|
|
249
|
-
queryObjectParamIndex = i;
|
|
250
|
-
usedIndices.add(i);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
queryParamIndices.push(i);
|
|
254
|
-
usedIndices.add(i);
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
bodyParamIndex,
|
|
258
|
-
queryParamIndices,
|
|
259
|
-
queryObjectParamIndex,
|
|
260
|
-
headerObjectParamIndex,
|
|
261
|
-
cookieObjectParamIndex,
|
|
262
|
-
bodyContentType: void 0
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
function isObjectType(type, checker) {
|
|
266
|
-
const objectFlags = (type.flags & import_typescript2.default.TypeFlags.Object) !== 0;
|
|
267
|
-
const intersectionFlags = (type.flags & import_typescript2.default.TypeFlags.Intersection) !== 0;
|
|
268
|
-
if (!objectFlags && !intersectionFlags) return false;
|
|
269
|
-
const symbol = type.getSymbol();
|
|
270
|
-
if (symbol?.getName() === "__object") return true;
|
|
271
|
-
const properties = checker.getPropertiesOfType(type);
|
|
272
|
-
if (properties.length > 0) return true;
|
|
273
|
-
const callSigs = type.getCallSignatures?.();
|
|
274
|
-
if (callSigs && callSigs.length > 0) return false;
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
function getTypeName(type) {
|
|
278
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
279
|
-
if (aliasSymbol) return aliasSymbol.getName();
|
|
280
|
-
const symbol = type.getSymbol();
|
|
281
|
-
return symbol?.getName() ?? "";
|
|
282
|
-
}
|
|
283
|
-
function findDecorator(node, name) {
|
|
284
|
-
const decorators = import_typescript2.default.getDecorators(node);
|
|
285
|
-
if (!decorators) return null;
|
|
286
|
-
for (const decorator of decorators) {
|
|
287
|
-
if (import_typescript2.default.isCallExpression(decorator.expression)) {
|
|
288
|
-
const expr = decorator.expression.expression;
|
|
289
|
-
if (import_typescript2.default.isIdentifier(expr) && expr.text === name) {
|
|
290
|
-
return decorator;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
function extractDecoratorStringArg(decorator) {
|
|
297
|
-
if (import_typescript2.default.isCallExpression(decorator.expression)) {
|
|
298
|
-
const arg = decorator.expression.arguments[0];
|
|
299
|
-
if (arg && import_typescript2.default.isStringLiteral(arg)) {
|
|
300
|
-
return arg.text;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
function unwrapPromise(type, _checker) {
|
|
306
|
-
const symbol = type.getSymbol();
|
|
307
|
-
if (symbol?.getName() === "Promise") {
|
|
308
|
-
const typeArgs = type.typeArguments;
|
|
309
|
-
if (typeArgs && typeArgs.length > 0) {
|
|
310
|
-
return typeArgs[0];
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return type;
|
|
314
|
-
}
|
|
315
|
-
function unwrapPromiseTypeNode(typeNode) {
|
|
316
|
-
if (!typeNode) return void 0;
|
|
317
|
-
if (import_typescript2.default.isTypeReferenceNode(typeNode)) {
|
|
318
|
-
if (import_typescript2.default.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise") {
|
|
319
|
-
return typeNode.typeArguments?.[0] ?? typeNode;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return typeNode;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/compiler/schema/openapi.ts
|
|
326
|
-
var import_typescript12 = __toESM(require("typescript"), 1);
|
|
327
|
-
|
|
328
|
-
// src/compiler/schema/typeToJsonSchema.ts
|
|
329
|
-
var import_typescript7 = __toESM(require("typescript"), 1);
|
|
330
|
-
|
|
331
|
-
// src/compiler/schema/primitives.ts
|
|
332
|
-
var import_typescript3 = __toESM(require("typescript"), 1);
|
|
333
|
-
function handlePrimitiveType(type, ctx, typeNode) {
|
|
334
|
-
const { checker, propertyName } = ctx;
|
|
335
|
-
if (type.flags & import_typescript3.default.TypeFlags.Undefined) {
|
|
336
|
-
return {};
|
|
337
|
-
}
|
|
338
|
-
if (type.flags & import_typescript3.default.TypeFlags.Null) {
|
|
339
|
-
return { type: "null" };
|
|
340
|
-
}
|
|
341
|
-
if (isDateType(type, checker)) {
|
|
342
|
-
return { type: "string", format: "date-time" };
|
|
343
|
-
}
|
|
344
|
-
if (type.flags & import_typescript3.default.TypeFlags.String) {
|
|
345
|
-
return { type: "string" };
|
|
346
|
-
}
|
|
347
|
-
if (type.flags & import_typescript3.default.TypeFlags.Number) {
|
|
348
|
-
return normalizeNumericType(type, checker, typeNode, propertyName);
|
|
349
|
-
}
|
|
350
|
-
if (type.flags & import_typescript3.default.TypeFlags.Boolean) {
|
|
351
|
-
return { type: "boolean" };
|
|
352
|
-
}
|
|
353
|
-
if (type.flags & import_typescript3.default.TypeFlags.BigInt) {
|
|
354
|
-
return {
|
|
355
|
-
type: "string",
|
|
356
|
-
format: "int64",
|
|
357
|
-
pattern: "^-?\\d+$"
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
if (type.flags & import_typescript3.default.TypeFlags.StringLiteral) {
|
|
361
|
-
const value = type.value;
|
|
362
|
-
return { type: "string", enum: [value] };
|
|
363
|
-
}
|
|
364
|
-
if (type.flags & import_typescript3.default.TypeFlags.NumberLiteral) {
|
|
365
|
-
const value = type.value;
|
|
366
|
-
return { type: "number", enum: [value] };
|
|
367
|
-
}
|
|
368
|
-
if (type.flags & import_typescript3.default.TypeFlags.BooleanLiteral) {
|
|
369
|
-
const intrinsic = type.intrinsicName;
|
|
370
|
-
return { type: "boolean", enum: [intrinsic === "true"] };
|
|
371
|
-
}
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
function isDateType(type, checker) {
|
|
375
|
-
const symbol = type.getSymbol();
|
|
376
|
-
const aliasSymbol = type.aliasSymbol;
|
|
377
|
-
if (aliasSymbol && aliasSymbol.flags & import_typescript3.default.SymbolFlags.Alias) {
|
|
378
|
-
const aliased = checker.getAliasedSymbol(aliasSymbol);
|
|
379
|
-
return aliased?.getName() === "Date";
|
|
380
|
-
}
|
|
381
|
-
if (symbol && symbol.flags & import_typescript3.default.SymbolFlags.Alias) {
|
|
382
|
-
const aliased = checker.getAliasedSymbol(symbol);
|
|
383
|
-
return aliased?.getName() === "Date";
|
|
384
|
-
}
|
|
385
|
-
return symbol?.getName() === "Date";
|
|
386
|
-
}
|
|
387
|
-
function normalizeNumericType(type, checker, typeNode, propertyName) {
|
|
388
|
-
const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
|
|
389
|
-
const symbol = getEffectiveSymbol(type, checker);
|
|
390
|
-
const symbolName = symbol?.getName() ?? null;
|
|
391
|
-
if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName) || shouldBeIntegerType(propertyName ?? null)) {
|
|
392
|
-
return { type: "integer" };
|
|
393
|
-
}
|
|
394
|
-
return { type: "number" };
|
|
395
|
-
}
|
|
396
|
-
function shouldBeIntegerType(typeName) {
|
|
397
|
-
if (!typeName) return false;
|
|
398
|
-
const lower = typeName.toLowerCase();
|
|
399
|
-
return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk" || lower === "page" || lower === "pagesize" || lower === "totalitems" || lower === "limit" || lower === "offset";
|
|
400
|
-
}
|
|
401
|
-
function getExplicitTypeNameFromNode(typeNode) {
|
|
402
|
-
if (!typeNode) return null;
|
|
403
|
-
if (import_typescript3.default.isTypeReferenceNode(typeNode)) {
|
|
404
|
-
if (import_typescript3.default.isIdentifier(typeNode.typeName)) {
|
|
405
|
-
return typeNode.typeName.text;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (import_typescript3.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
409
|
-
if (import_typescript3.default.isIdentifier(typeNode.parent.name)) {
|
|
410
|
-
return typeNode.parent.name.text;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
function getEffectiveSymbol(type, checker) {
|
|
416
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
417
|
-
if (aliasSymbol && aliasSymbol.flags & import_typescript3.default.SymbolFlags.Alias) {
|
|
418
|
-
return checker.getAliasedSymbol(aliasSymbol);
|
|
419
|
-
}
|
|
420
|
-
return type.getSymbol() ?? null;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// src/compiler/schema/unionHandler.ts
|
|
424
|
-
var import_typescript4 = __toESM(require("typescript"), 1);
|
|
425
|
-
function handleUnion(type, ctx, typeNode) {
|
|
426
|
-
return buildNamedSchema(type, ctx, typeNode, () => {
|
|
427
|
-
const types = type.types;
|
|
428
|
-
const nullType = types.find((t) => t.flags & import_typescript4.default.TypeFlags.Null);
|
|
429
|
-
const undefinedType = types.find((t) => t.flags & import_typescript4.default.TypeFlags.Undefined);
|
|
430
|
-
const otherTypes = types.filter((t) => !(t.flags & import_typescript4.default.TypeFlags.Null) && !(t.flags & import_typescript4.default.TypeFlags.Undefined));
|
|
431
|
-
const allStringLiterals = otherTypes.every((t) => t.flags & import_typescript4.default.TypeFlags.StringLiteral);
|
|
432
|
-
if (allStringLiterals && otherTypes.length > 0) {
|
|
433
|
-
const enumValues = otherTypes.map((t) => t.value);
|
|
434
|
-
const schema = { type: "string", enum: enumValues };
|
|
435
|
-
if (nullType) {
|
|
436
|
-
schema.type = ["string", "null"];
|
|
437
|
-
}
|
|
438
|
-
return schema;
|
|
439
|
-
}
|
|
440
|
-
const allBooleanLiterals = otherTypes.length > 0 && otherTypes.every((t) => t.flags & import_typescript4.default.TypeFlags.BooleanLiteral);
|
|
441
|
-
if (allBooleanLiterals) {
|
|
442
|
-
const schema = { type: "boolean" };
|
|
443
|
-
if (nullType || undefinedType) {
|
|
444
|
-
schema.type = ["boolean", "null"];
|
|
445
|
-
}
|
|
446
|
-
return schema;
|
|
447
|
-
}
|
|
448
|
-
if (otherTypes.length === 1 && nullType) {
|
|
449
|
-
const innerSchema = typeToJsonSchema(otherTypes[0], ctx);
|
|
450
|
-
if (typeof innerSchema.type === "string") {
|
|
451
|
-
innerSchema.type = [innerSchema.type, "null"];
|
|
452
|
-
}
|
|
453
|
-
return innerSchema;
|
|
454
|
-
}
|
|
455
|
-
if (otherTypes.length > 1) {
|
|
456
|
-
const branches = otherTypes.map((t) => typeToJsonSchema(t, ctx));
|
|
457
|
-
const hasNull = !!nullType;
|
|
458
|
-
const result = {};
|
|
459
|
-
if (hasNull) {
|
|
460
|
-
result.anyOf = [...branches, { type: "null" }];
|
|
461
|
-
} else {
|
|
462
|
-
result.anyOf = branches;
|
|
463
|
-
}
|
|
464
|
-
const discriminatorResult = detectDiscriminatedUnion(otherTypes, ctx, branches);
|
|
465
|
-
if (discriminatorResult) {
|
|
466
|
-
result.oneOf = branches;
|
|
467
|
-
result.discriminator = discriminatorResult;
|
|
468
|
-
}
|
|
469
|
-
return result;
|
|
470
|
-
}
|
|
471
|
-
if (otherTypes.length === 1) {
|
|
472
|
-
return typeToJsonSchema(otherTypes[0], ctx);
|
|
473
|
-
}
|
|
474
|
-
return {};
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
function detectDiscriminatedUnion(types, ctx, _branches) {
|
|
478
|
-
if (types.length < 2) return null;
|
|
479
|
-
const candidates = findCommonPropertyNames(ctx.checker, types);
|
|
480
|
-
for (const propName of candidates) {
|
|
481
|
-
const requiredInAll = types.every((t) => isRequiredProperty(ctx.checker, t, propName));
|
|
482
|
-
if (!requiredInAll) continue;
|
|
483
|
-
const literalSets = types.map((t) => getPropertyLiteralValues(ctx.checker, t, propName));
|
|
484
|
-
if (literalSets.some((s) => s === null)) continue;
|
|
485
|
-
const allSets = literalSets;
|
|
486
|
-
if (!areSetsDisjoint(allSets)) continue;
|
|
487
|
-
const mapping = {};
|
|
488
|
-
for (let i = 0; i < types.length; i++) {
|
|
489
|
-
const branchName = getBranchSchemaName(types[i], ctx);
|
|
490
|
-
for (const val of allSets[i]) {
|
|
491
|
-
mapping[val] = `#/components/schemas/${branchName}`;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return { propertyName: propName, mapping };
|
|
495
|
-
}
|
|
496
|
-
return null;
|
|
497
|
-
}
|
|
498
|
-
function findCommonPropertyNames(checker, types) {
|
|
499
|
-
if (types.length === 0) return [];
|
|
500
|
-
const firstProps = types[0].getProperties().map((s) => s.getName());
|
|
501
|
-
return firstProps.filter(
|
|
502
|
-
(name) => types.every((m) => !!checker.getPropertyOfType(m, name))
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
function isRequiredProperty(checker, type, propName) {
|
|
506
|
-
const sym = checker.getPropertyOfType(type, propName);
|
|
507
|
-
if (!sym) return false;
|
|
508
|
-
if (sym.flags & import_typescript4.default.SymbolFlags.Optional) return false;
|
|
509
|
-
const propType = checker.getTypeOfSymbol(sym);
|
|
510
|
-
if (propType.isUnion?.()) {
|
|
511
|
-
const hasUndefined = propType.types.some((t) => (t.flags & import_typescript4.default.TypeFlags.Undefined) !== 0);
|
|
512
|
-
if (hasUndefined) return false;
|
|
513
|
-
}
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
function getPropertyLiteralValues(checker, type, propName) {
|
|
517
|
-
const sym = checker.getPropertyOfType(type, propName);
|
|
518
|
-
if (!sym) return null;
|
|
519
|
-
const propType = checker.getTypeOfSymbol(sym);
|
|
520
|
-
if (propType.isStringLiteral?.()) {
|
|
521
|
-
return /* @__PURE__ */ new Set([propType.value]);
|
|
522
|
-
}
|
|
523
|
-
if (propType.isUnion?.()) {
|
|
524
|
-
const values = /* @__PURE__ */ new Set();
|
|
525
|
-
for (const m of propType.types) {
|
|
526
|
-
if (!m.isStringLiteral?.()) return null;
|
|
527
|
-
values.add(m.value);
|
|
528
|
-
}
|
|
529
|
-
return values;
|
|
530
|
-
}
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
function areSetsDisjoint(sets) {
|
|
534
|
-
const seen = /* @__PURE__ */ new Set();
|
|
535
|
-
for (const s of sets) {
|
|
536
|
-
for (const v of s) {
|
|
537
|
-
if (seen.has(v)) return false;
|
|
538
|
-
seen.add(v);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return true;
|
|
542
|
-
}
|
|
543
|
-
function getBranchSchemaName(type, ctx) {
|
|
544
|
-
const symbol = type.getSymbol();
|
|
545
|
-
if (symbol) {
|
|
546
|
-
return symbol.getName();
|
|
547
|
-
}
|
|
548
|
-
const aliasSymbol = type.aliasSymbol;
|
|
549
|
-
if (aliasSymbol) {
|
|
550
|
-
return aliasSymbol.getName();
|
|
551
|
-
}
|
|
552
|
-
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
553
|
-
}
|
|
554
|
-
function buildNamedSchema(type, ctx, typeNode, build) {
|
|
555
|
-
const name = getSchemaName(type, typeNode);
|
|
556
|
-
if (!name) {
|
|
557
|
-
return build();
|
|
558
|
-
}
|
|
559
|
-
const { components, typeStack } = ctx;
|
|
560
|
-
if (components.has(name) || typeStack.has(type)) {
|
|
561
|
-
return { $ref: `#/components/schemas/${name}` };
|
|
562
|
-
}
|
|
563
|
-
typeStack.add(type);
|
|
564
|
-
const schema = build();
|
|
565
|
-
typeStack.delete(type);
|
|
566
|
-
if (!components.has(name)) {
|
|
567
|
-
components.set(name, schema);
|
|
568
|
-
}
|
|
569
|
-
return { $ref: `#/components/schemas/${name}` };
|
|
570
|
-
}
|
|
571
|
-
function getSchemaName(type, typeNode) {
|
|
572
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
573
|
-
const aliasName = aliasSymbol?.getName();
|
|
574
|
-
if (aliasName && aliasName !== "__type") {
|
|
575
|
-
return aliasName;
|
|
576
|
-
}
|
|
577
|
-
const symbol = type.getSymbol();
|
|
578
|
-
const symbolName = symbol?.getName?.();
|
|
579
|
-
if (symbolName && symbolName !== "__type") {
|
|
580
|
-
return symbolName;
|
|
581
|
-
}
|
|
582
|
-
const nodeName = getExplicitTypeNameFromNode2(typeNode);
|
|
583
|
-
if (nodeName && nodeName !== "__type") {
|
|
584
|
-
return nodeName;
|
|
585
|
-
}
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
function getExplicitTypeNameFromNode2(typeNode) {
|
|
589
|
-
if (!typeNode) return null;
|
|
590
|
-
if (import_typescript4.default.isTypeReferenceNode(typeNode)) {
|
|
591
|
-
if (import_typescript4.default.isIdentifier(typeNode.typeName)) {
|
|
592
|
-
return typeNode.typeName.text;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (import_typescript4.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
596
|
-
if (import_typescript4.default.isIdentifier(typeNode.parent.name)) {
|
|
597
|
-
return typeNode.parent.name.text;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// src/compiler/schema/intersectionHandler.ts
|
|
604
|
-
var import_typescript5 = __toESM(require("typescript"), 1);
|
|
605
|
-
function handleIntersection(type, ctx, typeNode) {
|
|
606
|
-
return buildNamedSchema2(type, ctx, typeNode, () => {
|
|
607
|
-
const types = type.types;
|
|
608
|
-
const brandCollapsed = tryCollapseBrandedIntersection(types, ctx, typeNode);
|
|
609
|
-
if (brandCollapsed) {
|
|
610
|
-
return brandCollapsed;
|
|
611
|
-
}
|
|
612
|
-
const allOf = [];
|
|
613
|
-
for (const t of types) {
|
|
614
|
-
const schema = typeToJsonSchema(t, ctx);
|
|
615
|
-
if (Object.keys(schema).length > 0) {
|
|
616
|
-
if (isEmptyObjectSchema(schema)) {
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
allOf.push(schema);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
if (allOf.length === 0) {
|
|
623
|
-
return {};
|
|
624
|
-
}
|
|
625
|
-
if (allOf.length === 1) {
|
|
626
|
-
return allOf[0];
|
|
627
|
-
}
|
|
628
|
-
return { allOf };
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
function tryCollapseBrandedIntersection(types, ctx, _typeNode) {
|
|
632
|
-
const { checker } = ctx;
|
|
633
|
-
const parts = [...types];
|
|
634
|
-
const prim = parts.find(isPrimitiveLike);
|
|
635
|
-
if (!prim) return null;
|
|
636
|
-
const rest = parts.filter((p) => p !== prim);
|
|
637
|
-
if (rest.every((r) => isBrandObject(checker, r, ctx))) {
|
|
638
|
-
return typeToJsonSchema(prim, ctx);
|
|
639
|
-
}
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
function isPrimitiveLike(t) {
|
|
643
|
-
return (t.flags & (import_typescript5.default.TypeFlags.String | import_typescript5.default.TypeFlags.Number | import_typescript5.default.TypeFlags.Boolean | import_typescript5.default.TypeFlags.BigInt)) !== 0 || (t.flags & import_typescript5.default.TypeFlags.StringLiteral) !== 0 || (t.flags & import_typescript5.default.TypeFlags.NumberLiteral) !== 0;
|
|
644
|
-
}
|
|
645
|
-
function isBrandObject(checker, t, _ctx) {
|
|
646
|
-
if (!(t.flags & import_typescript5.default.TypeFlags.Object)) return false;
|
|
647
|
-
const props = t.getProperties();
|
|
648
|
-
if (props.length === 0) return false;
|
|
649
|
-
const allowed = /* @__PURE__ */ new Set(["__brand", "__type", "__tag", "brand"]);
|
|
650
|
-
for (const p of props) {
|
|
651
|
-
if (!allowed.has(p.getName())) return false;
|
|
652
|
-
}
|
|
653
|
-
const callSigs = t.getCallSignatures?.();
|
|
654
|
-
if (callSigs && callSigs.length > 0) return false;
|
|
655
|
-
const constructSigs = t.getConstructSignatures?.();
|
|
656
|
-
if (constructSigs && constructSigs.length > 0) return false;
|
|
657
|
-
return true;
|
|
658
|
-
}
|
|
659
|
-
function isEmptyObjectSchema(schema) {
|
|
660
|
-
if (schema.type !== "object") {
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
|
664
|
-
if (!schema.additionalProperties) {
|
|
665
|
-
return true;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
function buildNamedSchema2(type, ctx, typeNode, build) {
|
|
671
|
-
const name = getSchemaName2(type, typeNode);
|
|
672
|
-
if (!name) {
|
|
673
|
-
return build();
|
|
674
|
-
}
|
|
675
|
-
const { components, typeStack } = ctx;
|
|
676
|
-
if (components.has(name) || typeStack.has(type)) {
|
|
677
|
-
return { $ref: `#/components/schemas/${name}` };
|
|
678
|
-
}
|
|
679
|
-
typeStack.add(type);
|
|
680
|
-
const schema = build();
|
|
681
|
-
typeStack.delete(type);
|
|
682
|
-
if (!components.has(name)) {
|
|
683
|
-
components.set(name, schema);
|
|
684
|
-
}
|
|
685
|
-
return { $ref: `#/components/schemas/${name}` };
|
|
686
|
-
}
|
|
687
|
-
function getSchemaName2(type, typeNode) {
|
|
688
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
689
|
-
const aliasName = aliasSymbol?.getName();
|
|
690
|
-
if (aliasName && aliasName !== "__type") {
|
|
691
|
-
return aliasName;
|
|
692
|
-
}
|
|
693
|
-
const symbol = type.getSymbol();
|
|
694
|
-
const symbolName = symbol?.getName?.();
|
|
695
|
-
if (symbolName && symbolName !== "__type") {
|
|
696
|
-
return symbolName;
|
|
697
|
-
}
|
|
698
|
-
const nodeName = getExplicitTypeNameFromNode3(typeNode);
|
|
699
|
-
if (nodeName && nodeName !== "__type") {
|
|
700
|
-
return nodeName;
|
|
701
|
-
}
|
|
702
|
-
return null;
|
|
703
|
-
}
|
|
704
|
-
function getExplicitTypeNameFromNode3(typeNode) {
|
|
705
|
-
if (!typeNode) return null;
|
|
706
|
-
if (import_typescript5.default.isTypeReferenceNode(typeNode)) {
|
|
707
|
-
if (import_typescript5.default.isIdentifier(typeNode.typeName)) {
|
|
708
|
-
return typeNode.typeName.text;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
if (import_typescript5.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
712
|
-
if (import_typescript5.default.isIdentifier(typeNode.parent.name)) {
|
|
713
|
-
return typeNode.parent.name.text;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
return null;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// src/compiler/schema/objectHandler.ts
|
|
720
|
-
var import_typescript6 = __toESM(require("typescript"), 1);
|
|
721
|
-
function getTypeParameterName(type) {
|
|
722
|
-
if (type.flags & import_typescript6.default.TypeFlags.TypeParameter) {
|
|
723
|
-
const typeParam = type;
|
|
724
|
-
return typeParam.symbol?.getName() ?? null;
|
|
725
|
-
}
|
|
726
|
-
return null;
|
|
727
|
-
}
|
|
728
|
-
function getTypeArguments(type) {
|
|
729
|
-
const typeRef = type;
|
|
730
|
-
const args = typeRef.typeArguments;
|
|
731
|
-
if (!args) return void 0;
|
|
732
|
-
return Array.from(args);
|
|
733
|
-
}
|
|
734
|
-
function createTypeParameterSubstitutions(type, typeNode, checker) {
|
|
735
|
-
const typeArgs = getTypeArguments(type);
|
|
736
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
737
|
-
return void 0;
|
|
738
|
-
}
|
|
739
|
-
if (!typeNode || !import_typescript6.default.isTypeReferenceNode(typeNode)) {
|
|
740
|
-
return void 0;
|
|
741
|
-
}
|
|
742
|
-
const typeParams = typeNode.typeArguments;
|
|
743
|
-
if (!typeParams || typeParams.length !== typeArgs.length) {
|
|
744
|
-
return void 0;
|
|
745
|
-
}
|
|
746
|
-
const substitutions = /* @__PURE__ */ new Map();
|
|
747
|
-
for (let i = 0; i < typeParams.length; i++) {
|
|
748
|
-
const typeParamNode = typeParams[i];
|
|
749
|
-
const typeArg = typeArgs[i];
|
|
750
|
-
if (import_typescript6.default.isIdentifier(typeParamNode)) {
|
|
751
|
-
substitutions.set(typeParamNode.text, typeArg);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return substitutions.size > 0 ? substitutions : void 0;
|
|
755
|
-
}
|
|
756
|
-
function resolveTypeParameter(type, substitutions, _checker) {
|
|
757
|
-
if (!substitutions) return null;
|
|
758
|
-
const paramName = getTypeParameterName(type);
|
|
759
|
-
if (!paramName) return null;
|
|
760
|
-
const resolved = substitutions.get(paramName);
|
|
761
|
-
return resolved ?? null;
|
|
762
|
-
}
|
|
763
|
-
function handleObjectType(type, ctx, typeNode) {
|
|
764
|
-
const { checker, components, typeStack } = ctx;
|
|
765
|
-
const symbol = type.getSymbol();
|
|
766
|
-
const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
|
|
767
|
-
if (isMetalOrmWrapperType(type, checker)) {
|
|
768
|
-
return handleMetalOrmWrapper(type, ctx);
|
|
769
|
-
}
|
|
770
|
-
if (typeName && typeName !== "__type") {
|
|
771
|
-
const isMetalOrmGeneric = METAL_ORM_WRAPPER_NAMES.some(
|
|
772
|
-
(name) => typeName === name || typeName.endsWith("Api")
|
|
773
|
-
);
|
|
774
|
-
if (isMetalOrmGeneric) {
|
|
775
|
-
return {};
|
|
776
|
-
}
|
|
777
|
-
if (typeStack.has(type)) {
|
|
778
|
-
return { $ref: `#/components/schemas/${typeName}` };
|
|
779
|
-
}
|
|
780
|
-
typeStack.add(type);
|
|
781
|
-
}
|
|
782
|
-
const typeParamSubstitutions = createTypeParameterSubstitutions(type, typeNode, checker);
|
|
783
|
-
const buildCtx = typeParamSubstitutions ? { ...ctx, typeParameterSubstitutions: typeParamSubstitutions } : ctx;
|
|
784
|
-
const schema = buildObjectSchema(type, buildCtx, typeNode);
|
|
785
|
-
if (typeName && typeName !== "__type") {
|
|
786
|
-
typeStack.delete(type);
|
|
787
|
-
const existing = components.get(typeName);
|
|
788
|
-
if (!existing) {
|
|
789
|
-
components.set(typeName, schema);
|
|
790
|
-
} else {
|
|
791
|
-
const merged = mergeSchemasIfNeeded(existing, schema);
|
|
792
|
-
if (merged !== existing) {
|
|
793
|
-
components.set(typeName, merged);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return { $ref: `#/components/schemas/${typeName}` };
|
|
797
|
-
}
|
|
798
|
-
typeStack.delete(type);
|
|
799
|
-
return schema;
|
|
800
|
-
}
|
|
801
|
-
function buildObjectSchema(type, ctx, _typeNode) {
|
|
802
|
-
const { checker, mode, typeParameterSubstitutions } = ctx;
|
|
803
|
-
const properties = {};
|
|
804
|
-
const required = [];
|
|
805
|
-
const props = checker.getPropertiesOfType(type);
|
|
806
|
-
for (const prop of props) {
|
|
807
|
-
const propName = prop.getName();
|
|
808
|
-
if (isIteratorOrSymbolProperty(propName)) {
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
let propType = checker.getTypeOfSymbol(prop);
|
|
812
|
-
if (isMethodLike(propType)) {
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
const resolvedType = resolveTypeParameter(propType, typeParameterSubstitutions, checker);
|
|
816
|
-
if (resolvedType) {
|
|
817
|
-
propType = resolvedType;
|
|
818
|
-
}
|
|
819
|
-
const isOptional = !!(prop.flags & import_typescript6.default.SymbolFlags.Optional);
|
|
820
|
-
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
821
|
-
const propCtx = { ...ctx, propertyName: propName };
|
|
822
|
-
properties[propName] = typeToJsonSchema(propType, propCtx);
|
|
823
|
-
const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
|
|
824
|
-
if (shouldRequire) {
|
|
825
|
-
required.push(propName);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
const schema = {
|
|
829
|
-
type: "object",
|
|
830
|
-
properties
|
|
831
|
-
};
|
|
832
|
-
if (required.length > 0) {
|
|
833
|
-
schema.required = required;
|
|
834
|
-
}
|
|
835
|
-
if (isRecordType(type, checker)) {
|
|
836
|
-
const valueType = getRecordValueType(type, checker);
|
|
837
|
-
if (valueType) {
|
|
838
|
-
const resolvedValueType = resolveTypeParameter(valueType, typeParameterSubstitutions, checker);
|
|
839
|
-
schema.additionalProperties = typeToJsonSchema(resolvedValueType ?? valueType, ctx);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
return schema;
|
|
843
|
-
}
|
|
844
|
-
function isRecordType(type, _checker) {
|
|
845
|
-
const symbol = type.getSymbol();
|
|
846
|
-
if (!symbol) return false;
|
|
847
|
-
const name = symbol.getName();
|
|
848
|
-
if (name === "Record") return true;
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
function getRecordValueType(type, _checker) {
|
|
852
|
-
const symbol = type.getSymbol();
|
|
853
|
-
if (!symbol) return null;
|
|
854
|
-
const name = symbol.getName();
|
|
855
|
-
if (name === "Record") {
|
|
856
|
-
const typeRef = type;
|
|
857
|
-
const typeArgs = typeRef.typeArguments;
|
|
858
|
-
if (typeArgs && typeArgs.length >= 2) {
|
|
859
|
-
return typeArgs[1];
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
return null;
|
|
863
|
-
}
|
|
864
|
-
function isMetalOrmWrapperType(type, checker) {
|
|
865
|
-
return !!findMetalOrmWrapper(type, checker);
|
|
866
|
-
}
|
|
867
|
-
function isMethodLike(type) {
|
|
868
|
-
const callSigs = type.getCallSignatures?.();
|
|
869
|
-
return !!(callSigs && callSigs.length > 0);
|
|
870
|
-
}
|
|
871
|
-
function isIteratorOrSymbolProperty(propName) {
|
|
872
|
-
return propName.startsWith("__@") || propName.startsWith("[") || propName === Symbol.iterator.toString();
|
|
873
|
-
}
|
|
874
|
-
function getTypeNameFromNode(typeNode, _ctx) {
|
|
875
|
-
const explicitName = getExplicitTypeNameFromNode4(typeNode);
|
|
876
|
-
if (explicitName) return explicitName;
|
|
877
|
-
return "Anonymous_${ctx.typeNameStack.length}";
|
|
878
|
-
}
|
|
879
|
-
function getExplicitTypeNameFromNode4(typeNode) {
|
|
880
|
-
if (!typeNode) return null;
|
|
881
|
-
if (import_typescript6.default.isTypeReferenceNode(typeNode)) {
|
|
882
|
-
if (import_typescript6.default.isIdentifier(typeNode.typeName)) {
|
|
883
|
-
return typeNode.typeName.text;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
if (import_typescript6.default.isTypeAliasDeclaration(typeNode.parent)) {
|
|
887
|
-
if (import_typescript6.default.isIdentifier(typeNode.parent.name)) {
|
|
888
|
-
return typeNode.parent.name.text;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
893
|
-
function mergeSchemasIfNeeded(existing, newSchema) {
|
|
894
|
-
if (existing.type === "array" && newSchema.type === "array") {
|
|
895
|
-
return mergeArraySchemas(existing, newSchema);
|
|
896
|
-
}
|
|
897
|
-
const result = { ...existing };
|
|
898
|
-
for (const [key, newValue] of Object.entries(newSchema)) {
|
|
899
|
-
if (key === "properties" && newValue) {
|
|
900
|
-
result.properties = mergePropertiesIfNeeded(existing.properties || {}, newValue);
|
|
901
|
-
} else if (key === "required" && newValue) {
|
|
902
|
-
result.required = mergeRequiredFields(existing.required || [], newValue);
|
|
903
|
-
} else if (!deepEqual(existing[key], newValue)) {
|
|
904
|
-
result[key] = newValue;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
return result;
|
|
908
|
-
}
|
|
909
|
-
function mergePropertiesIfNeeded(existing, newProps) {
|
|
910
|
-
const result = { ...existing };
|
|
911
|
-
for (const [propName, newPropSchema] of Object.entries(newProps)) {
|
|
912
|
-
const existingProp = existing[propName];
|
|
913
|
-
if (!existingProp) {
|
|
914
|
-
result[propName] = newPropSchema;
|
|
915
|
-
} else if (deepEqual(existingProp, newPropSchema)) {
|
|
916
|
-
continue;
|
|
917
|
-
} else {
|
|
918
|
-
result[propName] = mergePropertySchemas(existingProp, newPropSchema);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return result;
|
|
922
|
-
}
|
|
923
|
-
function mergePropertySchemas(schema1, schema2) {
|
|
924
|
-
if (deepEqual(schema1, schema2)) {
|
|
925
|
-
return schema1;
|
|
926
|
-
}
|
|
927
|
-
if (schema1.type === "array" && schema2.type === "array") {
|
|
928
|
-
return mergeArraySchemas(schema1, schema2);
|
|
929
|
-
}
|
|
930
|
-
const existingOneOf = schema1.oneOf || schema1.anyOf;
|
|
931
|
-
const newOneOf = schema2.oneOf || schema2.anyOf;
|
|
932
|
-
if (existingOneOf) {
|
|
933
|
-
const mergedOneOf = [...existingOneOf];
|
|
934
|
-
if (newOneOf) {
|
|
935
|
-
for (const newItem of newOneOf) {
|
|
936
|
-
if (!mergedOneOf.some((item) => deepEqual(item, newItem))) {
|
|
937
|
-
mergedOneOf.push(newItem);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
} else if (!mergedOneOf.some((item) => deepEqual(item, schema2))) {
|
|
941
|
-
mergedOneOf.push(schema2);
|
|
942
|
-
}
|
|
943
|
-
return { ...schema1, oneOf: mergedOneOf };
|
|
944
|
-
}
|
|
945
|
-
return {
|
|
946
|
-
oneOf: [schema1, schema2]
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
function mergeArraySchemas(schema1, schema2) {
|
|
950
|
-
const result = { type: "array" };
|
|
951
|
-
if (schema1.uniqueItems || schema2.uniqueItems) {
|
|
952
|
-
result.uniqueItems = true;
|
|
953
|
-
}
|
|
954
|
-
if (schema1.items && schema2.items) {
|
|
955
|
-
result.items = mergePropertySchemas(schema1.items, schema2.items);
|
|
956
|
-
} else if (schema1.items) {
|
|
957
|
-
result.items = schema1.items;
|
|
958
|
-
} else if (schema2.items) {
|
|
959
|
-
result.items = schema2.items;
|
|
960
|
-
}
|
|
961
|
-
return result;
|
|
962
|
-
}
|
|
963
|
-
function mergeRequiredFields(existing, newFields) {
|
|
964
|
-
const merged = /* @__PURE__ */ new Set([...existing, ...newFields]);
|
|
965
|
-
return Array.from(merged);
|
|
966
|
-
}
|
|
967
|
-
function deepEqual(a, b) {
|
|
968
|
-
if (a === b) return true;
|
|
969
|
-
if (a == null || b == null) return false;
|
|
970
|
-
if (typeof a !== typeof b) return false;
|
|
971
|
-
if (typeof a === "object") {
|
|
972
|
-
const aKeys = Object.keys(a);
|
|
973
|
-
const bKeys = Object.keys(b);
|
|
974
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
975
|
-
for (const key of aKeys) {
|
|
976
|
-
if (!bKeys.includes(key)) return false;
|
|
977
|
-
if (!deepEqual(a[key], b[key])) return false;
|
|
978
|
-
}
|
|
979
|
-
return true;
|
|
980
|
-
}
|
|
981
|
-
return false;
|
|
982
|
-
}
|
|
983
|
-
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
984
|
-
function findMetalOrmWrapper(type, checker) {
|
|
985
|
-
if (type.isIntersection()) {
|
|
986
|
-
let wrapperInfo = null;
|
|
987
|
-
let hasReadonlyArray = false;
|
|
988
|
-
for (const constituent of type.types) {
|
|
989
|
-
const result = findWrapperInType(constituent, checker);
|
|
990
|
-
if (result) {
|
|
991
|
-
wrapperInfo = result;
|
|
992
|
-
}
|
|
993
|
-
if (!(constituent.flags & import_typescript6.default.TypeFlags.Object)) continue;
|
|
994
|
-
const symbol = constituent.getSymbol();
|
|
995
|
-
if (symbol?.getName() === "ReadonlyArray") {
|
|
996
|
-
hasReadonlyArray = true;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
if (wrapperInfo) {
|
|
1000
|
-
return { ...wrapperInfo, isReadonlyArray: hasReadonlyArray };
|
|
1001
|
-
}
|
|
1002
|
-
return null;
|
|
1003
|
-
}
|
|
1004
|
-
return findWrapperInType(type, checker);
|
|
1005
|
-
}
|
|
1006
|
-
function findWrapperInType(type, checker) {
|
|
1007
|
-
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
1008
|
-
const symbol = type.getSymbol();
|
|
1009
|
-
const effectiveSymbol = aliasSymbol && aliasSymbol.flags & import_typescript6.default.SymbolFlags.Alias ? checker.getAliasedSymbol(aliasSymbol) : symbol;
|
|
1010
|
-
if (!effectiveSymbol) return null;
|
|
1011
|
-
const name = effectiveSymbol.getName();
|
|
1012
|
-
if (!METAL_ORM_WRAPPER_NAMES.includes(name)) return null;
|
|
1013
|
-
const typeRef = type;
|
|
1014
|
-
const typeArgs = typeRef.typeArguments || [];
|
|
1015
|
-
return {
|
|
1016
|
-
wrapperName: name,
|
|
1017
|
-
targetTypeArgs: typeArgs,
|
|
1018
|
-
isReadonlyArray: false
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
function getWrapperTypeName(type, _checker) {
|
|
1022
|
-
const symbol = type.getSymbol();
|
|
1023
|
-
if (!symbol) return null;
|
|
1024
|
-
const name = symbol.getName();
|
|
1025
|
-
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
1026
|
-
}
|
|
1027
|
-
function handleMetalOrmWrapper(type, ctx) {
|
|
1028
|
-
const typeRef = type;
|
|
1029
|
-
const typeArgs = typeRef.typeArguments;
|
|
1030
|
-
const targetType = typeArgs?.[0] ?? null;
|
|
1031
|
-
const wrapperName = getWrapperTypeName(type, ctx.checker);
|
|
1032
|
-
if (!wrapperName) return {};
|
|
1033
|
-
const wrapperRel = { wrapper: wrapperName };
|
|
1034
|
-
if (!targetType) {
|
|
1035
|
-
return { "x-metal-orm-rel": wrapperRel };
|
|
1036
|
-
}
|
|
1037
|
-
if (wrapperName === "HasManyCollection" || wrapperName === "ManyToManyCollection") {
|
|
1038
|
-
if (ctx.typeStack.has(targetType)) {
|
|
1039
|
-
const items2 = {
|
|
1040
|
-
type: "object",
|
|
1041
|
-
properties: {
|
|
1042
|
-
id: { type: "integer" }
|
|
1043
|
-
},
|
|
1044
|
-
required: ["id"]
|
|
1045
|
-
};
|
|
1046
|
-
if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
|
|
1047
|
-
wrapperRel.pivot = ctx.checker.typeToString(typeArgs[1]);
|
|
1048
|
-
}
|
|
1049
|
-
return {
|
|
1050
|
-
type: "array",
|
|
1051
|
-
items: items2,
|
|
1052
|
-
"x-metal-orm-rel": wrapperRel
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
const items = targetType ? typeToJsonSchema(targetType, ctx) : {};
|
|
1056
|
-
if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
|
|
1057
|
-
wrapperRel.pivot = ctx.checker.typeToString(typeArgs[1]);
|
|
1058
|
-
}
|
|
1059
|
-
return {
|
|
1060
|
-
type: "array",
|
|
1061
|
-
items,
|
|
1062
|
-
"x-metal-orm-rel": wrapperRel
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
if (wrapperName === "BelongsToReference" || wrapperName === "HasOneReference") {
|
|
1066
|
-
return handleBelongsToReference(targetType, ctx, wrapperRel);
|
|
1067
|
-
}
|
|
1068
|
-
const targetSchema = typeToJsonSchema(targetType, ctx);
|
|
1069
|
-
return {
|
|
1070
|
-
...targetSchema,
|
|
1071
|
-
"x-metal-orm-rel": wrapperRel
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
function handleBelongsToReference(targetType, ctx, wrapperRel) {
|
|
1075
|
-
const { components, typeStack } = ctx;
|
|
1076
|
-
const targetSymbol = targetType.getSymbol();
|
|
1077
|
-
const typeName = targetSymbol?.getName();
|
|
1078
|
-
if (!typeName) {
|
|
1079
|
-
return {
|
|
1080
|
-
type: "object",
|
|
1081
|
-
properties: {},
|
|
1082
|
-
"x-metal-orm-rel": wrapperRel
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
const refSchemaName = `${typeName}Ref`;
|
|
1086
|
-
if (components.has(refSchemaName)) {
|
|
1087
|
-
return {
|
|
1088
|
-
$ref: `#/components/schemas/${refSchemaName}`,
|
|
1089
|
-
"x-metal-orm-rel": wrapperRel
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
if (typeStack.has(targetType)) {
|
|
1093
|
-
const circularRefSchema = {
|
|
1094
|
-
type: "object",
|
|
1095
|
-
properties: {
|
|
1096
|
-
id: { type: "integer" }
|
|
1097
|
-
},
|
|
1098
|
-
required: ["id"]
|
|
1099
|
-
};
|
|
1100
|
-
components.set(refSchemaName, circularRefSchema);
|
|
1101
|
-
return {
|
|
1102
|
-
$ref: `#/components/schemas/${refSchemaName}`,
|
|
1103
|
-
"x-metal-orm-rel": wrapperRel
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
const refSchema = buildRefSchema(targetType, ctx);
|
|
1107
|
-
components.set(refSchemaName, refSchema);
|
|
1108
|
-
return {
|
|
1109
|
-
$ref: `#/components/schemas/${refSchemaName}`,
|
|
1110
|
-
"x-metal-orm-rel": wrapperRel
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
function buildRefSchema(type, ctx) {
|
|
1114
|
-
const { checker } = ctx;
|
|
1115
|
-
if (!(type.flags & import_typescript6.default.TypeFlags.Object)) {
|
|
1116
|
-
return { type: "object", properties: {} };
|
|
1117
|
-
}
|
|
1118
|
-
const objectType = type;
|
|
1119
|
-
const properties = {};
|
|
1120
|
-
const required = [];
|
|
1121
|
-
const props = checker.getPropertiesOfType(objectType);
|
|
1122
|
-
for (const prop of props) {
|
|
1123
|
-
const propName = prop.getName();
|
|
1124
|
-
if (isIteratorOrSymbolProperty(propName)) {
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
const propType = checker.getTypeOfSymbol(prop);
|
|
1128
|
-
if (isMethodLike(propType)) {
|
|
1129
|
-
continue;
|
|
1130
|
-
}
|
|
1131
|
-
const isOptional = !!(prop.flags & import_typescript6.default.SymbolFlags.Optional);
|
|
1132
|
-
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
1133
|
-
if (isRelation) {
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
|
-
const propCtx = { ...ctx, propertyName: propName };
|
|
1137
|
-
properties[propName] = typeToJsonSchema(propType, propCtx);
|
|
1138
|
-
if (!isOptional) {
|
|
1139
|
-
required.push(propName);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
const schema = {
|
|
1143
|
-
type: "object",
|
|
1144
|
-
properties
|
|
1145
|
-
};
|
|
1146
|
-
if (required.length > 0) {
|
|
1147
|
-
schema.required = required;
|
|
1148
|
-
}
|
|
1149
|
-
return schema;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
// src/compiler/schema/typeToJsonSchema.ts
|
|
1153
|
-
function typeToJsonSchema(type, ctx, typeNode) {
|
|
1154
|
-
const primitiveResult = handlePrimitiveType(type, ctx, typeNode);
|
|
1155
|
-
if (primitiveResult) {
|
|
1156
|
-
return primitiveResult;
|
|
1157
|
-
}
|
|
1158
|
-
if (type.isUnion()) {
|
|
1159
|
-
return handleUnion(type, ctx, typeNode);
|
|
1160
|
-
}
|
|
1161
|
-
if (type.isIntersection()) {
|
|
1162
|
-
return handleIntersection(type, ctx, typeNode);
|
|
1163
|
-
}
|
|
1164
|
-
if (ctx.checker.isArrayType(type)) {
|
|
1165
|
-
const typeArgs = type.typeArguments;
|
|
1166
|
-
const itemType = typeArgs?.[0];
|
|
1167
|
-
const items = itemType ? typeToJsonSchema(itemType, ctx) : {};
|
|
1168
|
-
return {
|
|
1169
|
-
type: "array",
|
|
1170
|
-
items,
|
|
1171
|
-
uniqueItems: isSetType(type, ctx.checker) ? true : void 0
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
if (type.flags & import_typescript7.default.TypeFlags.Object) {
|
|
1175
|
-
const objectType = type;
|
|
1176
|
-
if (isMetalOrmWrapperType(type, ctx.checker)) {
|
|
1177
|
-
return handleMetalOrmWrapper(objectType, ctx);
|
|
1178
|
-
}
|
|
1179
|
-
return handleObjectType(objectType, ctx, typeNode);
|
|
1180
|
-
}
|
|
1181
|
-
return {};
|
|
1182
|
-
}
|
|
1183
|
-
function isSetType(type, _checker) {
|
|
1184
|
-
const symbol = type.getSymbol();
|
|
1185
|
-
if (!symbol) return false;
|
|
1186
|
-
const name = symbol.getName();
|
|
1187
|
-
if (name === "Set") return true;
|
|
1188
|
-
return false;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// src/compiler/schema/extractAnnotations.ts
|
|
1192
|
-
var import_typescript8 = __toESM(require("typescript"), 1);
|
|
1193
|
-
function extractPropertySchemaFragments(checker, prop) {
|
|
1194
|
-
if (!import_typescript8.default.canHaveDecorators(prop)) return [];
|
|
1195
|
-
const decs = import_typescript8.default.getDecorators(prop);
|
|
1196
|
-
if (!decs || decs.length === 0) return [];
|
|
1197
|
-
const frags = [];
|
|
1198
|
-
for (const d of decs) {
|
|
1199
|
-
const expr = d.expression;
|
|
1200
|
-
let callee;
|
|
1201
|
-
let args;
|
|
1202
|
-
if (import_typescript8.default.isCallExpression(expr)) {
|
|
1203
|
-
callee = expr.expression;
|
|
1204
|
-
args = expr.arguments;
|
|
1205
|
-
} else {
|
|
1206
|
-
callee = expr;
|
|
1207
|
-
args = import_typescript8.default.factory.createNodeArray([]);
|
|
1208
|
-
}
|
|
1209
|
-
const sym = checker.getSymbolAtLocation(callee);
|
|
1210
|
-
if (!sym) continue;
|
|
1211
|
-
const resolved = resolveImportedDecorator(checker, sym);
|
|
1212
|
-
if (!resolved || resolved.module !== "adorn-api/schema") continue;
|
|
1213
|
-
const name = resolved.name;
|
|
1214
|
-
if (name === "Schema") {
|
|
1215
|
-
const obj = args[0];
|
|
1216
|
-
if (obj && import_typescript8.default.isObjectLiteralExpression(obj)) {
|
|
1217
|
-
const frag = objectLiteralToJson(obj);
|
|
1218
|
-
if (frag) frags.push(frag);
|
|
1219
|
-
}
|
|
1220
|
-
continue;
|
|
1221
|
-
}
|
|
1222
|
-
if (name === "Min" && isNumberLiteral(args[0])) {
|
|
1223
|
-
frags.push({ minimum: Number(args[0].text) });
|
|
1224
|
-
} else if (name === "Max" && isNumberLiteral(args[0])) {
|
|
1225
|
-
frags.push({ maximum: Number(args[0].text) });
|
|
1226
|
-
} else if (name === "ExclusiveMin" && isNumberLiteral(args[0])) {
|
|
1227
|
-
frags.push({ exclusiveMinimum: Number(args[0].text) });
|
|
1228
|
-
} else if (name === "ExclusiveMax" && isNumberLiteral(args[0])) {
|
|
1229
|
-
frags.push({ exclusiveMaximum: Number(args[0].text) });
|
|
1230
|
-
} else if (name === "MinLength" && isNumberLiteral(args[0])) {
|
|
1231
|
-
frags.push({ minLength: Number(args[0].text) });
|
|
1232
|
-
} else if (name === "MaxLength" && isNumberLiteral(args[0])) {
|
|
1233
|
-
frags.push({ maxLength: Number(args[0].text) });
|
|
1234
|
-
} else if (name === "Format" && isStringLiteral(args[0])) {
|
|
1235
|
-
frags.push({ format: args[0].text });
|
|
1236
|
-
} else if (name === "Pattern") {
|
|
1237
|
-
const arg = args[0];
|
|
1238
|
-
if (arg && import_typescript8.default.isRegularExpressionLiteral(arg)) {
|
|
1239
|
-
frags.push({ pattern: extractRegexPattern(arg.text) });
|
|
1240
|
-
} else if (isStringLiteral(arg)) {
|
|
1241
|
-
frags.push({ pattern: arg.text });
|
|
1242
|
-
}
|
|
1243
|
-
} else if (name === "MinItems" && isNumberLiteral(args[0])) {
|
|
1244
|
-
frags.push({ minItems: Number(args[0].text) });
|
|
1245
|
-
} else if (name === "MaxItems" && isNumberLiteral(args[0])) {
|
|
1246
|
-
frags.push({ maxItems: Number(args[0].text) });
|
|
1247
|
-
} else if (name === "MinProperties" && isNumberLiteral(args[0])) {
|
|
1248
|
-
frags.push({ minProperties: Number(args[0].text) });
|
|
1249
|
-
} else if (name === "MaxProperties" && isNumberLiteral(args[0])) {
|
|
1250
|
-
frags.push({ maxProperties: Number(args[0].text) });
|
|
1251
|
-
} else if (name === "MultipleOf" && isNumberLiteral(args[0])) {
|
|
1252
|
-
frags.push({ multipleOf: Number(args[0].text) });
|
|
1253
|
-
} else if (name === "Example") {
|
|
1254
|
-
frags.push({ example: literalToJson(args[0]) });
|
|
1255
|
-
} else if (name === "Examples" && import_typescript8.default.isArrayLiteralExpression(args[0])) {
|
|
1256
|
-
frags.push({ examples: args[0].elements.map((e) => literalToJson(e)) });
|
|
1257
|
-
} else if (name === "Description" && isStringLiteral(args[0])) {
|
|
1258
|
-
frags.push({ description: args[0].text });
|
|
1259
|
-
} else if (name === "Enum" && import_typescript8.default.isArrayLiteralExpression(args[0])) {
|
|
1260
|
-
frags.push({ enum: args[0].elements.map((e) => literalToJson(e)) });
|
|
1261
|
-
} else if (name === "Const") {
|
|
1262
|
-
frags.push({ const: literalToJson(args[0]) });
|
|
1263
|
-
} else if (name === "Default") {
|
|
1264
|
-
frags.push({ default: literalToJson(args[0]) });
|
|
1265
|
-
} else if (name === "AdditionalProperties") {
|
|
1266
|
-
const arg = args[0];
|
|
1267
|
-
if (arg && (arg.kind === import_typescript8.default.SyntaxKind.FalseKeyword || arg.kind === import_typescript8.default.SyntaxKind.TrueKeyword)) {
|
|
1268
|
-
frags.push({ additionalProperties: arg.kind === import_typescript8.default.SyntaxKind.TrueKeyword });
|
|
1269
|
-
} else if (arg && import_typescript8.default.isObjectLiteralExpression(arg)) {
|
|
1270
|
-
const obj = objectLiteralToJson(arg);
|
|
1271
|
-
if (obj) frags.push({ additionalProperties: obj });
|
|
1272
|
-
}
|
|
1273
|
-
} else if (name === "Closed") {
|
|
1274
|
-
frags.push({ additionalProperties: false });
|
|
1275
|
-
} else if (name === "ClosedUnevaluated") {
|
|
1276
|
-
frags.push({ unevaluatedProperties: false });
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
return frags;
|
|
1280
|
-
}
|
|
1281
|
-
function resolveImportedDecorator(checker, sym) {
|
|
1282
|
-
const target = sym.flags & import_typescript8.default.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
|
|
1283
|
-
const name = target.getName();
|
|
1284
|
-
const decl = target.declarations?.[0];
|
|
1285
|
-
if (!decl) return null;
|
|
1286
|
-
const fileName = decl.getSourceFile().fileName.replace(/\\/g, "/");
|
|
1287
|
-
if (fileName.includes("/node_modules/adorn-api/") || fileName.includes("/src/schema/")) {
|
|
1288
|
-
return { module: "adorn-api/schema", name };
|
|
1289
|
-
}
|
|
1290
|
-
return null;
|
|
1291
|
-
}
|
|
1292
|
-
function isNumberLiteral(node) {
|
|
1293
|
-
return !!node && import_typescript8.default.isNumericLiteral(node);
|
|
1294
|
-
}
|
|
1295
|
-
function isStringLiteral(node) {
|
|
1296
|
-
return !!node && import_typescript8.default.isStringLiteral(node);
|
|
1297
|
-
}
|
|
1298
|
-
function extractRegexPattern(text) {
|
|
1299
|
-
const match = text.match(/^\/(.+)\/[gimsuy]*$/);
|
|
1300
|
-
return match ? match[1] : text;
|
|
1301
|
-
}
|
|
1302
|
-
function objectLiteralToJson(obj) {
|
|
1303
|
-
const out = {};
|
|
1304
|
-
for (const prop of obj.properties) {
|
|
1305
|
-
if (!import_typescript8.default.isPropertyAssignment(prop)) continue;
|
|
1306
|
-
const name = prop.name;
|
|
1307
|
-
let key;
|
|
1308
|
-
if (import_typescript8.default.isIdentifier(name)) {
|
|
1309
|
-
key = name.text;
|
|
1310
|
-
} else if (import_typescript8.default.isStringLiteral(name)) {
|
|
1311
|
-
key = name.text;
|
|
1312
|
-
} else {
|
|
1313
|
-
continue;
|
|
1314
|
-
}
|
|
1315
|
-
out[key] = literalToJson(prop.initializer);
|
|
1316
|
-
}
|
|
1317
|
-
return out;
|
|
1318
|
-
}
|
|
1319
|
-
function literalToJson(node) {
|
|
1320
|
-
if (import_typescript8.default.isStringLiteral(node)) return node.text;
|
|
1321
|
-
if (import_typescript8.default.isNumericLiteral(node)) return Number(node.text);
|
|
1322
|
-
if (node.kind === import_typescript8.default.SyntaxKind.TrueKeyword) return true;
|
|
1323
|
-
if (node.kind === import_typescript8.default.SyntaxKind.FalseKeyword) return false;
|
|
1324
|
-
if (node.kind === import_typescript8.default.SyntaxKind.NullKeyword) return null;
|
|
1325
|
-
if (import_typescript8.default.isObjectLiteralExpression(node)) return objectLiteralToJson(node);
|
|
1326
|
-
if (import_typescript8.default.isArrayLiteralExpression(node)) return node.elements.map((e) => literalToJson(e));
|
|
1327
|
-
return void 0;
|
|
1328
|
-
}
|
|
1329
|
-
function mergeFragments(base, ...frags) {
|
|
1330
|
-
const result = { ...base };
|
|
1331
|
-
for (const frag of frags) {
|
|
1332
|
-
Object.assign(result, frag);
|
|
1333
|
-
}
|
|
1334
|
-
return result;
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// src/compiler/schema/parameters.ts
|
|
1338
|
-
function buildPathParameters(operation, ctx, parameters) {
|
|
1339
|
-
for (const paramIndex of operation.pathParamIndices) {
|
|
1340
|
-
const param = operation.parameters[paramIndex];
|
|
1341
|
-
if (param) {
|
|
1342
|
-
let paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1343
|
-
if (param.paramNode) {
|
|
1344
|
-
const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
|
|
1345
|
-
if (frags.length > 0) {
|
|
1346
|
-
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
1350
|
-
const paramName = param.name.toLowerCase();
|
|
1351
|
-
const isIdParam = paramName === "id" || paramName.endsWith("id");
|
|
1352
|
-
if (!schema.$ref && schema.type === "number" && isIdParam) {
|
|
1353
|
-
schema.type = "integer";
|
|
1354
|
-
if (!schema.minimum) {
|
|
1355
|
-
schema.minimum = 1;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
parameters.push({
|
|
1359
|
-
name: param.name,
|
|
1360
|
-
in: "path",
|
|
1361
|
-
required: !param.isOptional,
|
|
1362
|
-
schema
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
function buildQueryParameters(operation, ctx, parameters) {
|
|
1368
|
-
if (operation.queryObjectParamIndex !== null) {
|
|
1369
|
-
const queryParam = operation.parameters[operation.queryObjectParamIndex];
|
|
1370
|
-
if (!queryParam) return;
|
|
1371
|
-
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1372
|
-
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
|
|
1373
|
-
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1374
|
-
const isRequired = queryRequired.includes(propName);
|
|
1375
|
-
const isDeepObject = isDeepObjectSchema(propSchema, ctx);
|
|
1376
|
-
const isObjectLike = isObjectLikeSchema(propSchema, ctx);
|
|
1377
|
-
const serialization = determineQuerySerialization(propSchema.type);
|
|
1378
|
-
const exampleValue = generateExampleValue(propSchema, propName);
|
|
1379
|
-
if (isDeepObject) {
|
|
1380
|
-
parameters.push({
|
|
1381
|
-
name: propName,
|
|
1382
|
-
in: "query",
|
|
1383
|
-
required: isRequired,
|
|
1384
|
-
style: "deepObject",
|
|
1385
|
-
explode: true,
|
|
1386
|
-
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1387
|
-
});
|
|
1388
|
-
} else if (isObjectLike) {
|
|
1389
|
-
const schemaRef = propSchema.$ref || "#/components/schemas/InlineQueryParam";
|
|
1390
|
-
parameters.push({
|
|
1391
|
-
name: propName,
|
|
1392
|
-
in: "query",
|
|
1393
|
-
required: isRequired,
|
|
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 paramDef = {
|
|
1403
|
-
name: propName,
|
|
1404
|
-
in: "query",
|
|
1405
|
-
required: isRequired,
|
|
1406
|
-
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1407
|
-
};
|
|
1408
|
-
if (propName === "page") {
|
|
1409
|
-
paramDef.schema = { type: "integer", default: 1, minimum: 1 };
|
|
1410
|
-
} else if (propName === "pageSize") {
|
|
1411
|
-
paramDef.schema = { type: "integer", default: 10, minimum: 1 };
|
|
1412
|
-
} else if (propName === "totalItems") {
|
|
1413
|
-
paramDef.schema = { type: "integer", minimum: 0 };
|
|
1414
|
-
} else if (propName === "sort") {
|
|
1415
|
-
paramDef.schema = {
|
|
1416
|
-
oneOf: [
|
|
1417
|
-
{ type: "string" },
|
|
1418
|
-
{ type: "array", items: { type: "string" } }
|
|
1419
|
-
]
|
|
1420
|
-
};
|
|
1421
|
-
} else if (propName === "q") {
|
|
1422
|
-
paramDef.schema = { type: "string" };
|
|
1423
|
-
} else if (propName === "hasComments") {
|
|
1424
|
-
paramDef.schema = { type: "boolean" };
|
|
1425
|
-
}
|
|
1426
|
-
if (Object.keys(serialization).length > 0) {
|
|
1427
|
-
Object.assign(paramDef, serialization);
|
|
1428
|
-
}
|
|
1429
|
-
parameters.push(paramDef);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
for (const paramIndex of operation.queryParamIndices) {
|
|
1434
|
-
const param = operation.parameters[paramIndex];
|
|
1435
|
-
if (param) {
|
|
1436
|
-
let paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1437
|
-
if (param.paramNode) {
|
|
1438
|
-
const frags = extractPropertySchemaFragments(ctx.checker, param.paramNode);
|
|
1439
|
-
if (frags.length > 0) {
|
|
1440
|
-
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
const isDeepObject = isDeepObjectSchema(paramSchema, ctx);
|
|
1444
|
-
const isObjectLike = isObjectLikeSchema(paramSchema, ctx);
|
|
1445
|
-
if (isDeepObject) {
|
|
1446
|
-
parameters.push({
|
|
1447
|
-
name: param.name,
|
|
1448
|
-
in: "query",
|
|
1449
|
-
required: !param.isOptional,
|
|
1450
|
-
style: "deepObject",
|
|
1451
|
-
explode: true,
|
|
1452
|
-
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema
|
|
1453
|
-
});
|
|
1454
|
-
} else if (isObjectLike) {
|
|
1455
|
-
const schemaRef = paramSchema.$ref || "#/components/schemas/InlineQueryParam";
|
|
1456
|
-
const exampleValue = generateExampleValue(paramSchema, param.name);
|
|
1457
|
-
parameters.push({
|
|
1458
|
-
name: param.name,
|
|
1459
|
-
in: "query",
|
|
1460
|
-
required: !param.isOptional,
|
|
1461
|
-
schema: { type: "string" },
|
|
1462
|
-
description: `JSON-encoded object. ${exampleValue}`,
|
|
1463
|
-
examples: {
|
|
1464
|
-
default: { value: parseExampleValue(exampleValue) }
|
|
1465
|
-
},
|
|
1466
|
-
"x-adorn-jsonSchemaRef": schemaRef
|
|
1467
|
-
});
|
|
1468
|
-
} else {
|
|
1469
|
-
const serialization = determineQuerySerialization(paramSchema.type);
|
|
1470
|
-
parameters.push({
|
|
1471
|
-
name: param.name,
|
|
1472
|
-
in: "query",
|
|
1473
|
-
required: !param.isOptional,
|
|
1474
|
-
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema,
|
|
1475
|
-
...Object.keys(serialization).length > 0 ? serialization : {}
|
|
1476
|
-
});
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
function buildHeaderParameters(operation, ctx, parameters) {
|
|
1482
|
-
if (operation.headerObjectParamIndex === null) return;
|
|
1483
|
-
const headerParam = operation.parameters[operation.headerObjectParamIndex];
|
|
1484
|
-
if (!headerParam) return;
|
|
1485
|
-
const headerSchema = typeToJsonSchema(headerParam.type, ctx);
|
|
1486
|
-
if (!headerSchema.properties) return;
|
|
1487
|
-
const headerObjProps = headerSchema.properties;
|
|
1488
|
-
for (const [propName, propSchema] of Object.entries(headerObjProps)) {
|
|
1489
|
-
const isRequired = headerSchema.required?.includes(propName) ?? false;
|
|
1490
|
-
parameters.push({
|
|
1491
|
-
name: propName,
|
|
1492
|
-
in: "header",
|
|
1493
|
-
required: isRequired,
|
|
1494
|
-
schema: propSchema
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
function buildCookieParameters(operation, ctx, parameters) {
|
|
1499
|
-
if (operation.cookieObjectParamIndex === null) return;
|
|
1500
|
-
const cookieParam = operation.parameters[operation.cookieObjectParamIndex];
|
|
1501
|
-
if (!cookieParam) return;
|
|
1502
|
-
const cookieSchema = typeToJsonSchema(cookieParam.type, ctx);
|
|
1503
|
-
if (!cookieSchema.properties) return;
|
|
1504
|
-
const cookieObjProps = cookieSchema.properties;
|
|
1505
|
-
for (const [propName, propSchema] of Object.entries(cookieObjProps)) {
|
|
1506
|
-
const isRequired = cookieSchema.required?.includes(propName) ?? false;
|
|
1507
|
-
parameters.push({
|
|
1508
|
-
name: propName,
|
|
1509
|
-
in: "cookie",
|
|
1510
|
-
required: isRequired,
|
|
1511
|
-
schema: propSchema,
|
|
1512
|
-
style: "form",
|
|
1513
|
-
explode: true
|
|
1514
|
-
});
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
function determineQuerySerialization(schemaType) {
|
|
1518
|
-
const typeArray = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
1519
|
-
const isArray = typeArray.includes("array");
|
|
1520
|
-
if (isArray) {
|
|
1521
|
-
return { style: "form", explode: true };
|
|
1522
|
-
}
|
|
1523
|
-
return {};
|
|
1524
|
-
}
|
|
1525
|
-
function generateExampleValue(schema, propName) {
|
|
1526
|
-
const resolved = resolveSchemaRef(schema, /* @__PURE__ */ new Map());
|
|
1527
|
-
if (resolved.type === "object" && resolved.properties) {
|
|
1528
|
-
const example = {};
|
|
1529
|
-
for (const [key, prop] of Object.entries(resolved.properties)) {
|
|
1530
|
-
const propResolved = resolveSchemaRef(prop, /* @__PURE__ */ new Map());
|
|
1531
|
-
if (propResolved.type === "string") {
|
|
1532
|
-
example[key] = "value";
|
|
1533
|
-
} else if (propResolved.type === "number" || propResolved.type === "integer") {
|
|
1534
|
-
example[key] = 1;
|
|
1535
|
-
} else if (propResolved.type === "boolean") {
|
|
1536
|
-
example[key] = true;
|
|
1537
|
-
} else if (Array.isArray(propResolved.type) && propResolved.type.includes("null")) {
|
|
1538
|
-
example[key] = null;
|
|
1539
|
-
} else if (propResolved.enum) {
|
|
1540
|
-
example[key] = propResolved.enum[0];
|
|
1541
|
-
} else {
|
|
1542
|
-
example[key] = "value";
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
return `Example: ${propName}=${JSON.stringify(example)}`;
|
|
1546
|
-
}
|
|
1547
|
-
return `Example: ${propName}=${JSON.stringify({ key: "value" })}`;
|
|
1548
|
-
}
|
|
1549
|
-
function parseExampleValue(description) {
|
|
1550
|
-
const match = description.match(/Example:\s*\w+=(\{[^}]+\})/);
|
|
1551
|
-
if (match) {
|
|
1552
|
-
return match[1];
|
|
1553
|
-
}
|
|
1554
|
-
return JSON.stringify({ key: "value" });
|
|
1555
|
-
}
|
|
1556
|
-
function isDeepObjectSchema(schema, ctx) {
|
|
1557
|
-
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1558
|
-
if (resolved.type === "array") {
|
|
1559
|
-
return false;
|
|
1560
|
-
}
|
|
1561
|
-
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1562
|
-
return true;
|
|
1563
|
-
}
|
|
1564
|
-
if (resolved.allOf) {
|
|
1565
|
-
for (const branch of resolved.allOf) {
|
|
1566
|
-
if (isDeepObjectSchema(branch, ctx)) {
|
|
1567
|
-
return true;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
return false;
|
|
1572
|
-
}
|
|
1573
|
-
function isObjectLikeSchema(schema, ctx) {
|
|
1574
|
-
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1575
|
-
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1576
|
-
return true;
|
|
1577
|
-
}
|
|
1578
|
-
if (resolved.allOf) {
|
|
1579
|
-
for (const branch of resolved.allOf) {
|
|
1580
|
-
if (isObjectLikeSchema(branch, ctx)) {
|
|
1581
|
-
return true;
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
if (resolved.type === "array" && resolved.items) {
|
|
1586
|
-
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1587
|
-
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1588
|
-
}
|
|
1589
|
-
return false;
|
|
1590
|
-
}
|
|
1591
|
-
function resolveSchemaRef(schema, components) {
|
|
1592
|
-
const ref = schema.$ref;
|
|
1593
|
-
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1594
|
-
return schema;
|
|
1595
|
-
}
|
|
1596
|
-
const name = ref.replace("#/components/schemas/", "");
|
|
1597
|
-
const next = components.get(name);
|
|
1598
|
-
if (!next) return schema;
|
|
1599
|
-
return resolveSchemaRef(next, components);
|
|
1600
|
-
}
|
|
1601
|
-
function resolveAndCollectObjectProps(schema, components) {
|
|
1602
|
-
const resolved = resolveSchemaRef(schema, components);
|
|
1603
|
-
const properties = {};
|
|
1604
|
-
const required = [];
|
|
1605
|
-
const processSchema = (s) => {
|
|
1606
|
-
const current = resolveSchemaRef(s, components);
|
|
1607
|
-
if (current.properties) {
|
|
1608
|
-
for (const [key, val] of Object.entries(current.properties)) {
|
|
1609
|
-
if (!properties[key]) {
|
|
1610
|
-
properties[key] = val;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
if (current.required) {
|
|
1615
|
-
for (const req of current.required) {
|
|
1616
|
-
if (!required.includes(req)) {
|
|
1617
|
-
required.push(req);
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
if (current.allOf) {
|
|
1622
|
-
for (const branch of current.allOf) {
|
|
1623
|
-
processSchema(branch);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1627
|
-
processSchema(resolved);
|
|
1628
|
-
return { properties, required };
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// src/compiler/schema/queryBuilderAnalyzer.ts
|
|
1632
|
-
var import_typescript10 = __toESM(require("typescript"), 1);
|
|
1633
|
-
|
|
1634
|
-
// src/compiler/schema/serviceCallAnalyzer.ts
|
|
1635
|
-
var import_typescript9 = __toESM(require("typescript"), 1);
|
|
1636
|
-
var ServiceCallAnalyzer = class {
|
|
1637
|
-
checker;
|
|
1638
|
-
program;
|
|
1639
|
-
cache = /* @__PURE__ */ new Map();
|
|
1640
|
-
analyzedMethods = /* @__PURE__ */ new Set();
|
|
1641
|
-
constructor(checker, program) {
|
|
1642
|
-
this.checker = checker;
|
|
1643
|
-
this.program = program;
|
|
1644
|
-
}
|
|
1645
|
-
/**
|
|
1646
|
-
* Analyzes a controller method for query builder patterns, following service calls
|
|
1647
|
-
*/
|
|
1648
|
-
analyzeControllerMethod(methodDeclaration, options = {}) {
|
|
1649
|
-
const cacheKey = this.getMethodCacheKey(methodDeclaration);
|
|
1650
|
-
if (this.cache.has(cacheKey)) {
|
|
1651
|
-
return this.cache.get(cacheKey) ?? null;
|
|
1652
|
-
}
|
|
1653
|
-
const maxDepth = options.maxDepth ?? 3;
|
|
1654
|
-
const schema = this.analyzeMethodWithServiceCalls(methodDeclaration, 0, maxDepth, options);
|
|
1655
|
-
this.cache.set(cacheKey, schema);
|
|
1656
|
-
return schema;
|
|
1657
|
-
}
|
|
1658
|
-
/**
|
|
1659
|
-
* Recursively analyzes method with service call traversal
|
|
1660
|
-
*/
|
|
1661
|
-
analyzeMethodWithServiceCalls(methodDeclaration, currentDepth, maxDepth, options) {
|
|
1662
|
-
if (currentDepth >= maxDepth) {
|
|
1663
|
-
return null;
|
|
1664
|
-
}
|
|
1665
|
-
const methodKey = this.getMethodCacheKey(methodDeclaration);
|
|
1666
|
-
if (this.analyzedMethods.has(methodKey)) {
|
|
1667
|
-
return null;
|
|
1668
|
-
}
|
|
1669
|
-
this.analyzedMethods.add(methodKey);
|
|
1670
|
-
const directSchema = analyzeQueryBuilderForSchema(methodDeclaration, this.checker);
|
|
1671
|
-
if (directSchema) {
|
|
1672
|
-
return directSchema;
|
|
1673
|
-
}
|
|
1674
|
-
const serviceCalls = this.findServiceCalls(methodDeclaration);
|
|
1675
|
-
for (const serviceCall of serviceCalls) {
|
|
1676
|
-
const serviceSchema = this.analyzeServiceMethod(serviceCall, currentDepth, maxDepth, options);
|
|
1677
|
-
if (serviceSchema) {
|
|
1678
|
-
return serviceSchema;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
return null;
|
|
1682
|
-
}
|
|
1683
|
-
/**
|
|
1684
|
-
* Analyzes a service method for query builder patterns
|
|
1685
|
-
*/
|
|
1686
|
-
analyzeServiceMethod(serviceCall, currentDepth, maxDepth, options) {
|
|
1687
|
-
const directSchema = analyzeQueryBuilderForSchema(serviceCall.methodDeclaration, this.checker);
|
|
1688
|
-
if (directSchema) {
|
|
1689
|
-
return directSchema;
|
|
1690
|
-
}
|
|
1691
|
-
if (options.analyzeHelpers) {
|
|
1692
|
-
return this.analyzeMethodWithServiceCalls(
|
|
1693
|
-
serviceCall.methodDeclaration,
|
|
1694
|
-
currentDepth + 1,
|
|
1695
|
-
maxDepth,
|
|
1696
|
-
options
|
|
1697
|
-
);
|
|
1698
|
-
}
|
|
1699
|
-
return null;
|
|
1700
|
-
}
|
|
1701
|
-
/**
|
|
1702
|
-
* Finds service calls in a method body
|
|
1703
|
-
*/
|
|
1704
|
-
findServiceCalls(methodDeclaration) {
|
|
1705
|
-
const serviceCalls = [];
|
|
1706
|
-
const body = methodDeclaration.body;
|
|
1707
|
-
if (!body) {
|
|
1708
|
-
return serviceCalls;
|
|
1709
|
-
}
|
|
1710
|
-
const visitor = (node) => {
|
|
1711
|
-
if (import_typescript9.default.isCallExpression(node)) {
|
|
1712
|
-
const serviceCall = this.resolveServiceCall(node);
|
|
1713
|
-
if (serviceCall) {
|
|
1714
|
-
serviceCalls.push(serviceCall);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
import_typescript9.default.forEachChild(node, visitor);
|
|
1718
|
-
};
|
|
1719
|
-
import_typescript9.default.forEachChild(body, visitor);
|
|
1720
|
-
return serviceCalls;
|
|
1721
|
-
}
|
|
1722
|
-
/**
|
|
1723
|
-
* Resolves a call expression to a service method
|
|
1724
|
-
*/
|
|
1725
|
-
resolveServiceCall(callExpression) {
|
|
1726
|
-
if (import_typescript9.default.isPropertyAccessExpression(callExpression.expression)) {
|
|
1727
|
-
const propAccess = callExpression.expression;
|
|
1728
|
-
const methodName = propAccess.name.text;
|
|
1729
|
-
const objectType = this.checker.getTypeAtLocation(propAccess.expression);
|
|
1730
|
-
const objectSymbol = objectType.getSymbol();
|
|
1731
|
-
if (objectSymbol) {
|
|
1732
|
-
const classDeclaration = this.findClassDeclaration(objectSymbol);
|
|
1733
|
-
if (classDeclaration) {
|
|
1734
|
-
const methodDeclaration = this.findMethodDeclaration(classDeclaration, methodName);
|
|
1735
|
-
if (methodDeclaration) {
|
|
1736
|
-
return {
|
|
1737
|
-
serviceName: classDeclaration.name?.text || "Unknown",
|
|
1738
|
-
methodName,
|
|
1739
|
-
filePath: classDeclaration.getSourceFile().fileName,
|
|
1740
|
-
classDeclaration,
|
|
1741
|
-
methodDeclaration
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
if (import_typescript9.default.isPropertyAccessExpression(callExpression.expression)) {
|
|
1748
|
-
const propAccess = callExpression.expression;
|
|
1749
|
-
const methodName = propAccess.name.text;
|
|
1750
|
-
if (import_typescript9.default.isIdentifier(propAccess.expression)) {
|
|
1751
|
-
const className = propAccess.expression.text;
|
|
1752
|
-
const classSymbol = this.checker.getSymbolAtLocation(propAccess.expression);
|
|
1753
|
-
if (classSymbol) {
|
|
1754
|
-
const classDeclaration = this.findClassDeclaration(classSymbol);
|
|
1755
|
-
if (classDeclaration && classDeclaration.name?.text === className) {
|
|
1756
|
-
const methodDeclaration = this.findMethodDeclaration(classDeclaration, methodName);
|
|
1757
|
-
if (methodDeclaration) {
|
|
1758
|
-
return {
|
|
1759
|
-
serviceName: className,
|
|
1760
|
-
methodName,
|
|
1761
|
-
filePath: classDeclaration.getSourceFile().fileName,
|
|
1762
|
-
classDeclaration,
|
|
1763
|
-
methodDeclaration
|
|
1764
|
-
};
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
return null;
|
|
1771
|
-
}
|
|
1772
|
-
/**
|
|
1773
|
-
* Finds class declaration from a symbol
|
|
1774
|
-
*/
|
|
1775
|
-
findClassDeclaration(symbol) {
|
|
1776
|
-
const declarations = symbol.getDeclarations();
|
|
1777
|
-
if (!declarations) return null;
|
|
1778
|
-
for (const declaration of declarations) {
|
|
1779
|
-
if (import_typescript9.default.isClassDeclaration(declaration)) {
|
|
1780
|
-
return declaration;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
return null;
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Finds method declaration in a class
|
|
1787
|
-
*/
|
|
1788
|
-
findMethodDeclaration(classDeclaration, methodName) {
|
|
1789
|
-
for (const member of classDeclaration.members) {
|
|
1790
|
-
if (import_typescript9.default.isMethodDeclaration(member) && member.name) {
|
|
1791
|
-
if (import_typescript9.default.isIdentifier(member.name) && member.name.text === methodName) {
|
|
1792
|
-
return member;
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
return null;
|
|
1797
|
-
}
|
|
1798
|
-
/**
|
|
1799
|
-
* Generates cache key for a method
|
|
1800
|
-
*/
|
|
1801
|
-
getMethodCacheKey(methodDeclaration) {
|
|
1802
|
-
const sourceFile = methodDeclaration.getSourceFile();
|
|
1803
|
-
const className = this.getClassName(methodDeclaration);
|
|
1804
|
-
const methodName = methodDeclaration.name?.getText() || "unknown";
|
|
1805
|
-
const line = sourceFile.getLineAndCharacterOfPosition(methodDeclaration.getStart()).line;
|
|
1806
|
-
return `${sourceFile.fileName}:${className}:${methodName}:${line}`;
|
|
1807
|
-
}
|
|
1808
|
-
/**
|
|
1809
|
-
* Gets class name from method declaration
|
|
1810
|
-
*/
|
|
1811
|
-
getClassName(methodDeclaration) {
|
|
1812
|
-
let node = methodDeclaration;
|
|
1813
|
-
while (node) {
|
|
1814
|
-
if (import_typescript9.default.isClassDeclaration(node)) {
|
|
1815
|
-
return node.name?.text || "Unknown";
|
|
1816
|
-
}
|
|
1817
|
-
node = node.parent;
|
|
1818
|
-
}
|
|
1819
|
-
return "Unknown";
|
|
1820
|
-
}
|
|
1821
|
-
/**
|
|
1822
|
-
* Clears the analysis cache
|
|
1823
|
-
*/
|
|
1824
|
-
clearCache() {
|
|
1825
|
-
this.cache.clear();
|
|
1826
|
-
this.analyzedMethods.clear();
|
|
1827
|
-
}
|
|
1828
|
-
/**
|
|
1829
|
-
* Gets cache statistics
|
|
1830
|
-
*/
|
|
1831
|
-
getCacheStats() {
|
|
1832
|
-
return {
|
|
1833
|
-
cached: this.cache.size,
|
|
1834
|
-
analyzed: this.analyzedMethods.size
|
|
1835
|
-
};
|
|
1836
|
-
}
|
|
1837
|
-
};
|
|
1838
|
-
function analyzeControllerWithServiceCalls(methodDeclaration, checker, program, options = {}) {
|
|
1839
|
-
if (!program) {
|
|
1840
|
-
return null;
|
|
1841
|
-
}
|
|
1842
|
-
const analyzer = new ServiceCallAnalyzer(checker, program);
|
|
1843
|
-
return analyzer.analyzeControllerMethod(methodDeclaration, options);
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
// src/compiler/schema/queryBuilderAnalyzer.ts
|
|
1847
|
-
function analyzeQueryBuilderForSchema(methodDeclaration, checker, options = {}) {
|
|
1848
|
-
const body = methodDeclaration.body;
|
|
1849
|
-
if (!body) {
|
|
1850
|
-
return null;
|
|
1851
|
-
}
|
|
1852
|
-
const trackedSchema = analyzeWithVariableTracking(body, checker, options);
|
|
1853
|
-
if (trackedSchema) {
|
|
1854
|
-
return trackedSchema;
|
|
1855
|
-
}
|
|
1856
|
-
const returnStatement = findReturnStatement(body);
|
|
1857
|
-
if (!returnStatement) {
|
|
1858
|
-
return null;
|
|
1859
|
-
}
|
|
1860
|
-
const callChain = analyzeReturnExpression(returnStatement.expression);
|
|
1861
|
-
if (!callChain) {
|
|
1862
|
-
return null;
|
|
1863
|
-
}
|
|
1864
|
-
return parseQueryBuilderChain(callChain, checker, options);
|
|
1865
|
-
}
|
|
1866
|
-
function analyzeQueryBuilderWithServiceCalls(methodDeclaration, checker, program, options = {}, operationInfo) {
|
|
1867
|
-
let schema = analyzeQueryBuilderForSchema(methodDeclaration, checker, options);
|
|
1868
|
-
if (!schema && program) {
|
|
1869
|
-
try {
|
|
1870
|
-
schema = analyzeControllerWithServiceCalls(methodDeclaration, checker, program, {
|
|
1871
|
-
maxDepth: options.maxDepth,
|
|
1872
|
-
analyzeHelpers: options.analyzeHelpers
|
|
1873
|
-
});
|
|
1874
|
-
} catch (error) {
|
|
1875
|
-
console.warn("Service call analysis failed:", error);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
return {
|
|
1879
|
-
detected: schema !== null,
|
|
1880
|
-
schema,
|
|
1881
|
-
...operationInfo
|
|
1882
|
-
};
|
|
1883
|
-
}
|
|
1884
|
-
function analyzeWithVariableTracking(body, checker, options) {
|
|
1885
|
-
let queryBuilderVar = null;
|
|
1886
|
-
let entityName = null;
|
|
1887
|
-
const selectedFields = /* @__PURE__ */ new Set();
|
|
1888
|
-
const includes = {};
|
|
1889
|
-
let isPaged = false;
|
|
1890
|
-
let hasReturn = false;
|
|
1891
|
-
for (const statement of body.statements) {
|
|
1892
|
-
if (import_typescript10.default.isReturnStatement(statement)) {
|
|
1893
|
-
hasReturn = true;
|
|
1894
|
-
const returnExpr = statement.expression;
|
|
1895
|
-
if (returnExpr && import_typescript10.default.isCallExpression(returnExpr)) {
|
|
1896
|
-
const callExpr = returnExpr;
|
|
1897
|
-
if (import_typescript10.default.isIdentifier(callExpr.expression) && queryBuilderVar) {
|
|
1898
|
-
const varName = callExpr.expression.text;
|
|
1899
|
-
if (varName === queryBuilderVar) {
|
|
1900
|
-
const methodName = callExpr.expression.text;
|
|
1901
|
-
if (methodName === "executePaged") {
|
|
1902
|
-
isPaged = true;
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
if (import_typescript10.default.isPropertyAccessExpression(callExpr.expression) && queryBuilderVar) {
|
|
1907
|
-
const propAccess = callExpr.expression;
|
|
1908
|
-
if (import_typescript10.default.isIdentifier(propAccess.expression) && propAccess.expression.text === queryBuilderVar) {
|
|
1909
|
-
const methodName = propAccess.name.text;
|
|
1910
|
-
if (methodName === "executePaged") {
|
|
1911
|
-
isPaged = true;
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
continue;
|
|
1917
|
-
}
|
|
1918
|
-
if (!import_typescript10.default.isExpressionStatement(statement)) {
|
|
1919
|
-
if (import_typescript10.default.isVariableStatement(statement)) {
|
|
1920
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
1921
|
-
if (!import_typescript10.default.isIdentifier(declaration.name)) continue;
|
|
1922
|
-
const varName = declaration.name.text;
|
|
1923
|
-
const initializer = declaration.initializer;
|
|
1924
|
-
if (!initializer || !import_typescript10.default.isCallExpression(initializer)) continue;
|
|
1925
|
-
const opInfo = extractChainedOperation(initializer);
|
|
1926
|
-
if (opInfo && (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom")) {
|
|
1927
|
-
queryBuilderVar = varName;
|
|
1928
|
-
if (opInfo.entityName) {
|
|
1929
|
-
entityName = opInfo.entityName;
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
continue;
|
|
1935
|
-
}
|
|
1936
|
-
const expr = statement.expression;
|
|
1937
|
-
if (import_typescript10.default.isBinaryExpression(expr) && expr.operatorToken.kind === import_typescript10.default.SyntaxKind.EqualsToken) {
|
|
1938
|
-
if (!import_typescript10.default.isIdentifier(expr.left)) {
|
|
1939
|
-
continue;
|
|
1940
|
-
}
|
|
1941
|
-
const varName = expr.left.text;
|
|
1942
|
-
const rightSide = expr.right;
|
|
1943
|
-
if (import_typescript10.default.isCallExpression(rightSide)) {
|
|
1944
|
-
const opInfo = extractChainedOperation(rightSide);
|
|
1945
|
-
if (opInfo) {
|
|
1946
|
-
if (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom") {
|
|
1947
|
-
queryBuilderVar = varName;
|
|
1948
|
-
if (opInfo.entityName) {
|
|
1949
|
-
entityName = opInfo.entityName;
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
if ((opInfo.operation === "select" || opInfo.operation === "include") && queryBuilderVar === varName) {
|
|
1953
|
-
if (opInfo.operation === "select") {
|
|
1954
|
-
for (const field of opInfo.fields || []) {
|
|
1955
|
-
selectedFields.add(field);
|
|
1956
|
-
}
|
|
1957
|
-
} else if (opInfo.operation === "include" && opInfo.includeArg) {
|
|
1958
|
-
const parsedIncludes = parseIncludeObjectLiteral(opInfo.includeArg);
|
|
1959
|
-
if (parsedIncludes) {
|
|
1960
|
-
for (const [relName, relSchema] of Object.entries(parsedIncludes)) {
|
|
1961
|
-
includes[relName] = relSchema;
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
if (!hasReturn || !queryBuilderVar || !entityName) {
|
|
1971
|
-
return null;
|
|
1972
|
-
}
|
|
1973
|
-
return {
|
|
1974
|
-
entityName,
|
|
1975
|
-
selectedFields: Array.from(selectedFields),
|
|
1976
|
-
includes,
|
|
1977
|
-
isPaged
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
function extractChainedOperation(callExpr) {
|
|
1981
|
-
if (import_typescript10.default.isIdentifier(callExpr.expression)) {
|
|
1982
|
-
const methodName2 = callExpr.expression.text;
|
|
1983
|
-
if (methodName2 === "selectFromEntity" || methodName2 === "selectFrom") {
|
|
1984
|
-
const entityArg = callExpr.arguments[0];
|
|
1985
|
-
let entityName = null;
|
|
1986
|
-
if (import_typescript10.default.isIdentifier(entityArg)) {
|
|
1987
|
-
entityName = entityArg.text;
|
|
1988
|
-
} else if (import_typescript10.default.isPropertyAccessExpression(entityArg)) {
|
|
1989
|
-
entityName = entityArg.name.text;
|
|
1990
|
-
}
|
|
1991
|
-
return {
|
|
1992
|
-
operation: methodName2 === "selectFromEntity" ? "selectFromEntity" : "selectFrom",
|
|
1993
|
-
fields: null,
|
|
1994
|
-
includeArg: null,
|
|
1995
|
-
entityName
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
if (!import_typescript10.default.isPropertyAccessExpression(callExpr.expression)) {
|
|
2000
|
-
return null;
|
|
2001
|
-
}
|
|
2002
|
-
const propAccess = callExpr.expression;
|
|
2003
|
-
const methodName = propAccess.name.text;
|
|
2004
|
-
if (methodName === "select") {
|
|
2005
|
-
const fields = [];
|
|
2006
|
-
for (const arg of callExpr.arguments) {
|
|
2007
|
-
if (import_typescript10.default.isStringLiteral(arg)) {
|
|
2008
|
-
fields.push(arg.text);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
return {
|
|
2012
|
-
operation: "select",
|
|
2013
|
-
fields,
|
|
2014
|
-
includeArg: null,
|
|
2015
|
-
entityName: null
|
|
2016
|
-
};
|
|
2017
|
-
}
|
|
2018
|
-
if (methodName === "include") {
|
|
2019
|
-
return {
|
|
2020
|
-
operation: "include",
|
|
2021
|
-
fields: null,
|
|
2022
|
-
includeArg: callExpr.arguments[0] || null,
|
|
2023
|
-
entityName: null
|
|
2024
|
-
};
|
|
2025
|
-
}
|
|
2026
|
-
return null;
|
|
2027
|
-
}
|
|
2028
|
-
function parseIncludeObjectLiteral(arg) {
|
|
2029
|
-
if (!import_typescript10.default.isObjectLiteralExpression(arg)) {
|
|
2030
|
-
return null;
|
|
2031
|
-
}
|
|
2032
|
-
const includes = {};
|
|
2033
|
-
for (const prop of arg.properties) {
|
|
2034
|
-
if (!import_typescript10.default.isPropertyAssignment(prop) || !import_typescript10.default.isIdentifier(prop.name)) {
|
|
2035
|
-
continue;
|
|
2036
|
-
}
|
|
2037
|
-
const relationName = prop.name.text;
|
|
2038
|
-
const value = prop.initializer;
|
|
2039
|
-
if (value.kind === import_typescript10.default.SyntaxKind.TrueKeyword) {
|
|
2040
|
-
includes[relationName] = true;
|
|
2041
|
-
} else if (import_typescript10.default.isObjectLiteralExpression(value)) {
|
|
2042
|
-
const nestedSchema = parseNestedInclude(value, 0);
|
|
2043
|
-
if (nestedSchema) {
|
|
2044
|
-
includes[relationName] = nestedSchema;
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
return includes;
|
|
2049
|
-
}
|
|
2050
|
-
function parseNestedInclude(obj, depth) {
|
|
2051
|
-
const selectedFields = [];
|
|
2052
|
-
const includes = {};
|
|
2053
|
-
for (const prop of obj.properties) {
|
|
2054
|
-
if (!import_typescript10.default.isPropertyAssignment(prop) || !import_typescript10.default.isIdentifier(prop.name)) {
|
|
2055
|
-
continue;
|
|
2056
|
-
}
|
|
2057
|
-
const propName = prop.name.text;
|
|
2058
|
-
const value = prop.initializer;
|
|
2059
|
-
if (propName === "select" && import_typescript10.default.isArrayLiteralExpression(value)) {
|
|
2060
|
-
for (const element of value.elements) {
|
|
2061
|
-
if (import_typescript10.default.isStringLiteral(element)) {
|
|
2062
|
-
selectedFields.push(element.text);
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
} else if (propName === "include" && import_typescript10.default.isObjectLiteralExpression(value)) {
|
|
2066
|
-
const nestedIncludes = parseIncludeObjectLiteral(value);
|
|
2067
|
-
if (nestedIncludes) {
|
|
2068
|
-
for (const [relName, relSchema] of Object.entries(nestedIncludes)) {
|
|
2069
|
-
includes[relName] = relSchema;
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
return {
|
|
2075
|
-
entityName: "",
|
|
2076
|
-
selectedFields,
|
|
2077
|
-
includes,
|
|
2078
|
-
isPaged: false
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
|
-
function getMethodName(expression) {
|
|
2082
|
-
if (import_typescript10.default.isIdentifier(expression)) {
|
|
2083
|
-
return expression.text;
|
|
2084
|
-
}
|
|
2085
|
-
if (import_typescript10.default.isPropertyAccessExpression(expression)) {
|
|
2086
|
-
return expression.name.text;
|
|
2087
|
-
}
|
|
2088
|
-
return null;
|
|
2089
|
-
}
|
|
2090
|
-
function findReturnStatement(body) {
|
|
2091
|
-
let returnStatement = null;
|
|
2092
|
-
for (const statement of body.statements) {
|
|
2093
|
-
if (import_typescript10.default.isReturnStatement(statement)) {
|
|
2094
|
-
if (returnStatement !== null) {
|
|
2095
|
-
return null;
|
|
2096
|
-
}
|
|
2097
|
-
returnStatement = statement;
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
return returnStatement;
|
|
2101
|
-
}
|
|
2102
|
-
function analyzeReturnExpression(expression) {
|
|
2103
|
-
if (!expression) {
|
|
2104
|
-
return null;
|
|
2105
|
-
}
|
|
2106
|
-
if (import_typescript10.default.isCallExpression(expression)) {
|
|
2107
|
-
return buildCallChain(expression, null);
|
|
2108
|
-
}
|
|
2109
|
-
return null;
|
|
2110
|
-
}
|
|
2111
|
-
function buildCallChain(node, parent) {
|
|
2112
|
-
if (import_typescript10.default.isCallExpression(node)) {
|
|
2113
|
-
const callNode = {
|
|
2114
|
-
expression: node.expression,
|
|
2115
|
-
methodName: getMethodName(node.expression),
|
|
2116
|
-
arguments: node.arguments,
|
|
2117
|
-
parent
|
|
2118
|
-
};
|
|
2119
|
-
if (import_typescript10.default.isPropertyAccessExpression(node.expression)) {
|
|
2120
|
-
return buildCallChain(node.expression.expression, callNode);
|
|
2121
|
-
}
|
|
2122
|
-
return callNode;
|
|
2123
|
-
}
|
|
2124
|
-
return parent;
|
|
2125
|
-
}
|
|
2126
|
-
function parseQueryBuilderChain(chain, checker, options) {
|
|
2127
|
-
if (!chain) {
|
|
2128
|
-
return null;
|
|
2129
|
-
}
|
|
2130
|
-
const rootNode = findSelectFromEntityCall(chain);
|
|
2131
|
-
if (!rootNode) {
|
|
2132
|
-
return null;
|
|
2133
|
-
}
|
|
2134
|
-
const entityName = extractEntityName(rootNode, checker);
|
|
2135
|
-
if (!entityName) {
|
|
2136
|
-
return null;
|
|
2137
|
-
}
|
|
2138
|
-
const selectedFields = /* @__PURE__ */ new Set();
|
|
2139
|
-
const includes = {};
|
|
2140
|
-
let isPaged = false;
|
|
2141
|
-
let currentNode = chain;
|
|
2142
|
-
while (currentNode) {
|
|
2143
|
-
const methodName = currentNode.methodName;
|
|
2144
|
-
if (methodName === "select") {
|
|
2145
|
-
for (const arg of currentNode.arguments) {
|
|
2146
|
-
if (import_typescript10.default.isStringLiteral(arg)) {
|
|
2147
|
-
selectedFields.add(arg.text);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
} else if (methodName === "include") {
|
|
2151
|
-
parseIncludeArgument(currentNode.arguments[0], includes, checker, options, 0);
|
|
2152
|
-
} else if (methodName === "executePaged") {
|
|
2153
|
-
isPaged = true;
|
|
2154
|
-
}
|
|
2155
|
-
currentNode = currentNode.parent;
|
|
2156
|
-
}
|
|
2157
|
-
return {
|
|
2158
|
-
entityName,
|
|
2159
|
-
selectedFields: Array.from(selectedFields),
|
|
2160
|
-
includes,
|
|
2161
|
-
isPaged
|
|
2162
|
-
};
|
|
2163
|
-
}
|
|
2164
|
-
function findSelectFromEntityCall(chain) {
|
|
2165
|
-
let currentNode = chain;
|
|
2166
|
-
let lastNode = null;
|
|
2167
|
-
while (currentNode) {
|
|
2168
|
-
if (currentNode.methodName === "selectFromEntity" || currentNode.methodName === "selectFrom") {
|
|
2169
|
-
return currentNode;
|
|
2170
|
-
}
|
|
2171
|
-
lastNode = currentNode;
|
|
2172
|
-
currentNode = currentNode.parent;
|
|
2173
|
-
}
|
|
2174
|
-
return lastNode;
|
|
2175
|
-
}
|
|
2176
|
-
function extractEntityName(callNode, checker) {
|
|
2177
|
-
if (callNode.arguments.length === 0) {
|
|
2178
|
-
return null;
|
|
2179
|
-
}
|
|
2180
|
-
const entityArg = callNode.arguments[0];
|
|
2181
|
-
if (import_typescript10.default.isIdentifier(entityArg)) {
|
|
2182
|
-
return entityArg.text;
|
|
2183
|
-
}
|
|
2184
|
-
if (import_typescript10.default.isPropertyAccessExpression(entityArg)) {
|
|
2185
|
-
return entityArg.name.text;
|
|
2186
|
-
}
|
|
2187
|
-
return null;
|
|
2188
|
-
}
|
|
2189
|
-
function parseIncludeArgument(arg, includes, checker, options, depth) {
|
|
2190
|
-
if (!arg) {
|
|
2191
|
-
return;
|
|
2192
|
-
}
|
|
2193
|
-
if (import_typescript10.default.isObjectLiteralExpression(arg)) {
|
|
2194
|
-
for (const prop of arg.properties) {
|
|
2195
|
-
if (!import_typescript10.default.isPropertyAssignment(prop) || !import_typescript10.default.isIdentifier(prop.name)) {
|
|
2196
|
-
continue;
|
|
2197
|
-
}
|
|
2198
|
-
const relationName = prop.name.text;
|
|
2199
|
-
const value = prop.initializer;
|
|
2200
|
-
if (value.kind === import_typescript10.default.SyntaxKind.TrueKeyword) {
|
|
2201
|
-
includes[relationName] = true;
|
|
2202
|
-
} else if (import_typescript10.default.isObjectLiteralExpression(value)) {
|
|
2203
|
-
const maxDepth = options.maxDepth ?? 5;
|
|
2204
|
-
if (depth < maxDepth) {
|
|
2205
|
-
const nestedSchema = parseNestedInclude(value, depth + 1);
|
|
2206
|
-
if (nestedSchema) {
|
|
2207
|
-
includes[relationName] = nestedSchema;
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
// src/compiler/schema/queryBuilderSchemaBuilder.ts
|
|
2216
|
-
var import_typescript11 = require("typescript");
|
|
2217
|
-
function wrapInPaginatedResult(schema) {
|
|
2218
|
-
return {
|
|
2219
|
-
type: "object",
|
|
2220
|
-
properties: {
|
|
2221
|
-
items: {
|
|
2222
|
-
type: "array",
|
|
2223
|
-
items: schema
|
|
2224
|
-
},
|
|
2225
|
-
page: { type: "integer" },
|
|
2226
|
-
pageSize: { type: "integer" },
|
|
2227
|
-
totalItems: { type: "integer" }
|
|
2228
|
-
},
|
|
2229
|
-
required: ["items", "page", "pageSize", "totalItems"]
|
|
2230
|
-
};
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
// src/compiler/schema/openapi.ts
|
|
2234
|
-
var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
|
|
2235
|
-
function generateOpenAPI(controllers, checker, options = {}) {
|
|
2236
|
-
const components = /* @__PURE__ */ new Map();
|
|
2237
|
-
const ctx = {
|
|
2238
|
-
checker,
|
|
2239
|
-
components,
|
|
2240
|
-
typeStack: /* @__PURE__ */ new Set(),
|
|
2241
|
-
typeNameStack: [],
|
|
2242
|
-
mode: "response"
|
|
2243
|
-
};
|
|
2244
|
-
const paths = {};
|
|
2245
|
-
const { onProgress, onQueryBuilderProgress } = options;
|
|
2246
|
-
let totalOperations = 0;
|
|
2247
|
-
for (const controller of controllers) {
|
|
2248
|
-
totalOperations += controller.operations.length;
|
|
2249
|
-
}
|
|
2250
|
-
let currentOperation = 0;
|
|
2251
|
-
for (let i = 0; i < controllers.length; i++) {
|
|
2252
|
-
const controller = controllers[i];
|
|
2253
|
-
if (onProgress) {
|
|
2254
|
-
onProgress(`Processing controller ${controller.className}`, i + 1, controllers.length);
|
|
2255
|
-
}
|
|
2256
|
-
for (const operation of controller.operations) {
|
|
2257
|
-
const fullPath = convertToOpenApiPath(controller.basePath, operation.path);
|
|
2258
|
-
if (!paths[fullPath]) {
|
|
2259
|
-
paths[fullPath] = {};
|
|
2260
|
-
}
|
|
2261
|
-
const method = operation.httpMethod.toLowerCase();
|
|
2262
|
-
const analysisResult = analyzeQueryBuilderWithServiceCalls(
|
|
2263
|
-
operation.methodDeclaration,
|
|
2264
|
-
checker,
|
|
2265
|
-
null,
|
|
2266
|
-
// TODO: Pass program when available
|
|
2267
|
-
{},
|
|
2268
|
-
{
|
|
2269
|
-
methodName: operation.operationId,
|
|
2270
|
-
httpMethod: operation.httpMethod,
|
|
2271
|
-
path: operation.path,
|
|
2272
|
-
operationId: operation.operationId
|
|
2273
|
-
}
|
|
2274
|
-
);
|
|
2275
|
-
if (onQueryBuilderProgress) {
|
|
2276
|
-
currentOperation++;
|
|
2277
|
-
onQueryBuilderProgress({
|
|
2278
|
-
controller: controller.className,
|
|
2279
|
-
operation: operation.operationId,
|
|
2280
|
-
method: operation.httpMethod,
|
|
2281
|
-
path: operation.path,
|
|
2282
|
-
queryBuilderDetected: analysisResult.detected,
|
|
2283
|
-
entityName: analysisResult.schema?.entityName,
|
|
2284
|
-
selectedFields: analysisResult.schema?.selectedFields,
|
|
2285
|
-
isPaged: analysisResult.schema?.isPaged,
|
|
2286
|
-
current: currentOperation,
|
|
2287
|
-
total: totalOperations
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
const schemas = Object.fromEntries(components);
|
|
2294
|
-
if (onProgress) {
|
|
2295
|
-
onProgress("Generating and cleaning schemas", controllers.length, controllers.length);
|
|
2296
|
-
}
|
|
2297
|
-
cleanupMetalOrmWrappers(schemas, paths);
|
|
2298
|
-
return {
|
|
2299
|
-
openapi: "3.1.0",
|
|
2300
|
-
info: {
|
|
2301
|
-
title: options.title ?? "API",
|
|
2302
|
-
version: options.version ?? "1.0.0"
|
|
2303
|
-
},
|
|
2304
|
-
components: {
|
|
2305
|
-
schemas
|
|
2306
|
-
},
|
|
2307
|
-
paths
|
|
2308
|
-
};
|
|
2309
|
-
}
|
|
2310
|
-
function cleanupMetalOrmWrappers(schemas, paths) {
|
|
2311
|
-
const schemasToDelete = /* @__PURE__ */ new Set();
|
|
2312
|
-
for (const wrapperName of METAL_ORM_WRAPPER_NAMES2) {
|
|
2313
|
-
if (schemas[wrapperName]) {
|
|
2314
|
-
schemasToDelete.add(wrapperName);
|
|
2315
|
-
}
|
|
2316
|
-
if (schemas[`${wrapperName}Api`]) {
|
|
2317
|
-
schemasToDelete.add(`${wrapperName}Api`);
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
for (const schema of Object.values(schemas)) {
|
|
2321
|
-
cleanupSchemaRefs(schema, schemasToDelete);
|
|
2322
|
-
}
|
|
2323
|
-
for (const pathItem of Object.values(paths)) {
|
|
2324
|
-
cleanupPathItemRefs(pathItem, schemasToDelete);
|
|
2325
|
-
}
|
|
2326
|
-
for (const schemaName of schemasToDelete) {
|
|
2327
|
-
delete schemas[schemaName];
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
function cleanupSchemaRefs(schema, schemasToDelete) {
|
|
2331
|
-
if (typeof schema !== "object" || schema === null) {
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
|
-
if (schema.properties) {
|
|
2335
|
-
for (const propName of Object.keys(schema.properties)) {
|
|
2336
|
-
const propSchema = schema.properties[propName];
|
|
2337
|
-
if (propSchema.$ref && typeof propSchema.$ref === "string") {
|
|
2338
|
-
const refName = propSchema.$ref.replace("#/components/schemas/", "");
|
|
2339
|
-
if (schemasToDelete.has(refName)) {
|
|
2340
|
-
delete schema.properties[propName];
|
|
2341
|
-
if (schema.required && Array.isArray(schema.required)) {
|
|
2342
|
-
schema.required = schema.required.filter((r) => r !== propName);
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
} else {
|
|
2346
|
-
cleanupSchemaRefs(propSchema, schemasToDelete);
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
if (schema.items) {
|
|
2351
|
-
cleanupSchemaRefs(schema.items, schemasToDelete);
|
|
2352
|
-
}
|
|
2353
|
-
if (schema.allOf) {
|
|
2354
|
-
for (const item of schema.allOf) {
|
|
2355
|
-
cleanupSchemaRefs(item, schemasToDelete);
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
function cleanupPathItemRefs(pathItem, schemasToDelete) {
|
|
2360
|
-
if (typeof pathItem !== "object" || pathItem === null) {
|
|
2361
|
-
return;
|
|
2362
|
-
}
|
|
2363
|
-
for (const method of Object.keys(pathItem)) {
|
|
2364
|
-
const operation = pathItem[method];
|
|
2365
|
-
if (typeof operation !== "object" || operation === null) continue;
|
|
2366
|
-
if (operation.requestBody) {
|
|
2367
|
-
cleanupRequestBodyRefs(operation.requestBody, schemasToDelete);
|
|
2368
|
-
}
|
|
2369
|
-
if (operation.responses) {
|
|
2370
|
-
const responses = Object.values(operation.responses);
|
|
2371
|
-
for (const response of responses) {
|
|
2372
|
-
if (response.content) {
|
|
2373
|
-
const contentTypes = Object.values(response.content);
|
|
2374
|
-
for (const contentType of contentTypes) {
|
|
2375
|
-
if (contentType.schema) {
|
|
2376
|
-
cleanupSchemaRefs(contentType.schema, schemasToDelete);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
function cleanupRequestBodyRefs(requestBody, schemasToDelete) {
|
|
2385
|
-
if (typeof requestBody !== "object" || requestBody === null) return;
|
|
2386
|
-
if (requestBody.content) {
|
|
2387
|
-
const contentTypes = Object.values(requestBody.content);
|
|
2388
|
-
for (const contentType of contentTypes) {
|
|
2389
|
-
if (contentType.schema) {
|
|
2390
|
-
cleanupSchemaRefs(contentType.schema, schemasToDelete);
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
if (requestBody.properties) {
|
|
2395
|
-
for (const propName of Object.keys(requestBody.properties)) {
|
|
2396
|
-
const propSchema = requestBody.properties[propName];
|
|
2397
|
-
if (propSchema.$ref && typeof propSchema.$ref === "string") {
|
|
2398
|
-
const refName = propSchema.$ref.replace("#/components/schemas/", "");
|
|
2399
|
-
if (schemasToDelete.has(refName)) {
|
|
2400
|
-
delete requestBody.properties[propName];
|
|
2401
|
-
if (requestBody.required && Array.isArray(requestBody.required)) {
|
|
2402
|
-
requestBody.required = requestBody.required.filter((r) => r !== propName);
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
} else {
|
|
2406
|
-
cleanupSchemaRefs(propSchema, schemasToDelete);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
function convertToOpenApiPath(basePath, path4) {
|
|
2412
|
-
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
2413
|
-
const converted = path4.replace(/:([^/]+)/g, "{$1}");
|
|
2414
|
-
let fullPath = base + converted || "/";
|
|
2415
|
-
if (fullPath.endsWith("/") && fullPath !== "/") {
|
|
2416
|
-
fullPath = fullPath.slice(0, -1);
|
|
2417
|
-
}
|
|
2418
|
-
return fullPath;
|
|
2419
|
-
}
|
|
2420
|
-
function tryInferQueryBuilderSchema(operation, checker) {
|
|
2421
|
-
return analyzeQueryBuilderForSchema(operation.methodDeclaration, checker) ?? null;
|
|
2422
|
-
}
|
|
2423
|
-
function getEntityTypeFromReturnType(operation, checker) {
|
|
2424
|
-
const returnType = operation.returnType;
|
|
2425
|
-
const unwrapPromise2 = (type) => {
|
|
2426
|
-
const symbol2 = type.getSymbol();
|
|
2427
|
-
if (symbol2?.getName() === "Promise") {
|
|
2428
|
-
const typeArgs = type.typeArguments;
|
|
2429
|
-
if (typeArgs && typeArgs.length > 0) {
|
|
2430
|
-
return typeArgs[0];
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
return type;
|
|
2434
|
-
};
|
|
2435
|
-
const innerType = unwrapPromise2(returnType);
|
|
2436
|
-
const symbol = innerType.getSymbol();
|
|
2437
|
-
if (symbol?.getName() === "PaginatedResult") {
|
|
2438
|
-
const typeArgs = innerType.typeArguments;
|
|
2439
|
-
if (typeArgs && typeArgs.length > 0) {
|
|
2440
|
-
return typeArgs[0];
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
return null;
|
|
2444
|
-
}
|
|
2445
|
-
function filterSchemaByQueryBuilder(querySchema, operation, ctx) {
|
|
2446
|
-
const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
|
|
2447
|
-
if (!entityType) {
|
|
2448
|
-
return {};
|
|
2449
|
-
}
|
|
2450
|
-
const entitySchema = typeToJsonSchema(entityType, ctx);
|
|
2451
|
-
let baseSchema = entitySchema;
|
|
2452
|
-
if (entitySchema.$ref && entitySchema.$ref.startsWith("#/components/schemas/")) {
|
|
2453
|
-
const schemaName = entitySchema.$ref.replace("#/components/schemas/", "");
|
|
2454
|
-
const componentSchema = ctx.components.get(schemaName);
|
|
2455
|
-
if (componentSchema) {
|
|
2456
|
-
baseSchema = componentSchema;
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
if (!baseSchema.properties || Object.keys(baseSchema.properties).length === 0) {
|
|
2460
|
-
return {};
|
|
2461
|
-
}
|
|
2462
|
-
const filteredSchema = buildFilteredSchema(querySchema, baseSchema);
|
|
2463
|
-
if (querySchema.isPaged) {
|
|
2464
|
-
return wrapInPaginatedResult(filteredSchema);
|
|
2465
|
-
}
|
|
2466
|
-
return filteredSchema;
|
|
2467
|
-
}
|
|
2468
|
-
function buildFilteredSchema(querySchema, entitySchema) {
|
|
2469
|
-
const properties = {};
|
|
2470
|
-
const required = [];
|
|
2471
|
-
for (const field of querySchema.selectedFields) {
|
|
2472
|
-
if (entitySchema.properties?.[field]) {
|
|
2473
|
-
properties[field] = entitySchema.properties[field];
|
|
2474
|
-
if (entitySchema.required && entitySchema.required.includes(field)) {
|
|
2475
|
-
required.push(field);
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
for (const [relationName, includeSpec] of Object.entries(querySchema.includes)) {
|
|
2480
|
-
if (entitySchema.properties?.[relationName]) {
|
|
2481
|
-
properties[relationName] = {
|
|
2482
|
-
type: "object",
|
|
2483
|
-
properties: {
|
|
2484
|
-
id: { type: "integer" }
|
|
2485
|
-
},
|
|
2486
|
-
required: ["id"]
|
|
2487
|
-
};
|
|
2488
|
-
if (entitySchema.required && entitySchema.required.includes(relationName)) {
|
|
2489
|
-
required.push(relationName);
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
const schema = {
|
|
2494
|
-
type: "object",
|
|
2495
|
-
properties
|
|
2496
|
-
};
|
|
2497
|
-
if (required.length > 0) {
|
|
2498
|
-
schema.required = required;
|
|
2499
|
-
}
|
|
2500
|
-
return schema;
|
|
2501
|
-
}
|
|
2502
|
-
function buildOperation(operation, ctx, controllerConsumes) {
|
|
2503
|
-
const op = {
|
|
2504
|
-
operationId: operation.operationId,
|
|
2505
|
-
responses: {}
|
|
2506
|
-
};
|
|
2507
|
-
const parameters = [];
|
|
2508
|
-
buildPathParameters(operation, ctx, parameters);
|
|
2509
|
-
buildQueryParameters(operation, ctx, parameters);
|
|
2510
|
-
buildHeaderParameters(operation, ctx, parameters);
|
|
2511
|
-
buildCookieParameters(operation, ctx, parameters);
|
|
2512
|
-
if (parameters.length > 0) {
|
|
2513
|
-
op.parameters = parameters;
|
|
2514
|
-
}
|
|
2515
|
-
const responseCtx = { ...ctx, mode: "response" };
|
|
2516
|
-
let responseSchema;
|
|
2517
|
-
const querySchema = tryInferQueryBuilderSchema(operation, ctx.checker);
|
|
2518
|
-
if (querySchema) {
|
|
2519
|
-
const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
|
|
2520
|
-
if (entityType) {
|
|
2521
|
-
responseSchema = filterSchemaByQueryBuilder(querySchema, operation, responseCtx);
|
|
2522
|
-
} else {
|
|
2523
|
-
responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
2524
|
-
}
|
|
2525
|
-
} else {
|
|
2526
|
-
responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
2527
|
-
}
|
|
2528
|
-
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
2529
|
-
op.responses[status] = {
|
|
2530
|
-
description: status === 201 ? "Created" : "OK",
|
|
2531
|
-
content: {
|
|
2532
|
-
"application/json": {
|
|
2533
|
-
schema: responseSchema
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
};
|
|
2537
|
-
if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
|
|
2538
|
-
const bodyParam = operation.parameters[operation.bodyParamIndex];
|
|
2539
|
-
if (bodyParam) {
|
|
2540
|
-
const requestCtx = { ...ctx, mode: "request" };
|
|
2541
|
-
let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
|
|
2542
|
-
bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
|
|
2543
|
-
const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
|
|
2544
|
-
const requestBody = {
|
|
2545
|
-
required: !bodyParam.isOptional,
|
|
2546
|
-
content: {}
|
|
2547
|
-
};
|
|
2548
|
-
if (contentType === "multipart/form-data") {
|
|
2549
|
-
requestBody.content["multipart/form-data"] = {
|
|
2550
|
-
schema: bodySchema
|
|
2551
|
-
};
|
|
2552
|
-
} else {
|
|
2553
|
-
requestBody.content[contentType] = {
|
|
2554
|
-
schema: bodySchema
|
|
2555
|
-
};
|
|
2556
|
-
}
|
|
2557
|
-
op.requestBody = requestBody;
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
return op;
|
|
2561
|
-
}
|
|
2562
|
-
function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
2563
|
-
if (!schema.properties) return schema;
|
|
2564
|
-
const typeSymbol = bodyParam.type.getSymbol();
|
|
2565
|
-
if (!typeSymbol) return schema;
|
|
2566
|
-
const declarations = typeSymbol.getDeclarations();
|
|
2567
|
-
if (!declarations || declarations.length === 0) return schema;
|
|
2568
|
-
const classDecl = declarations[0];
|
|
2569
|
-
if (!import_typescript12.default.isClassDeclaration(classDecl)) return schema;
|
|
2570
|
-
const result = { ...schema };
|
|
2571
|
-
const props = { ...result.properties };
|
|
2572
|
-
for (const member of classDecl.members) {
|
|
2573
|
-
if (!import_typescript12.default.isPropertyDeclaration(member) || !member.name) continue;
|
|
2574
|
-
const propName = import_typescript12.default.isIdentifier(member.name) ? member.name.text : null;
|
|
2575
|
-
if (!propName) continue;
|
|
2576
|
-
if (!props[propName]) continue;
|
|
2577
|
-
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
2578
|
-
if (frags.length > 0) {
|
|
2579
|
-
props[propName] = mergeFragments(props[propName], ...frags);
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
result.properties = props;
|
|
2583
|
-
return result;
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
// src/compiler/manifest/emit.ts
|
|
2587
|
-
var import_typescript13 = __toESM(require("typescript"), 1);
|
|
2588
|
-
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
2589
|
-
const components = /* @__PURE__ */ new Map();
|
|
2590
|
-
const ctx = {
|
|
2591
|
-
checker,
|
|
2592
|
-
components,
|
|
2593
|
-
typeStack: /* @__PURE__ */ new Set(),
|
|
2594
|
-
typeNameStack: [],
|
|
2595
|
-
mode: "request"
|
|
2596
|
-
};
|
|
2597
|
-
const controllerEntries = controllers.map((ctrl) => ({
|
|
2598
|
-
controllerId: ctrl.className,
|
|
2599
|
-
basePath: ctrl.basePath,
|
|
2600
|
-
operations: ctrl.operations.map((op) => buildOperationEntry(op, ctx))
|
|
2601
|
-
}));
|
|
2602
|
-
const validationConfig = validationMode === "precompiled" ? { mode: "precompiled", precompiledModule: null } : validationMode === "none" ? { mode: "none", precompiledModule: null } : { mode: "ajv-runtime", precompiledModule: null };
|
|
2603
|
-
return {
|
|
2604
|
-
manifestVersion: 1,
|
|
2605
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2606
|
-
generator: {
|
|
2607
|
-
name: "adorn-api",
|
|
2608
|
-
version,
|
|
2609
|
-
typescript: import_typescript13.default.version
|
|
2610
|
-
},
|
|
2611
|
-
schemas: {
|
|
2612
|
-
kind: "openapi-3.1",
|
|
2613
|
-
file: "./openapi.json",
|
|
2614
|
-
componentsSchemasPointer: "/components/schemas"
|
|
2615
|
-
},
|
|
2616
|
-
validation: validationConfig,
|
|
2617
|
-
controllers: controllerEntries
|
|
2618
|
-
};
|
|
2619
|
-
}
|
|
2620
|
-
function resolveSchemaRef2(schema, components) {
|
|
2621
|
-
const ref = schema.$ref;
|
|
2622
|
-
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
2623
|
-
return schema;
|
|
2624
|
-
}
|
|
2625
|
-
const name = ref.replace("#/components/schemas/", "");
|
|
2626
|
-
const next = components.get(name);
|
|
2627
|
-
if (!next) return schema;
|
|
2628
|
-
return resolveSchemaRef2(next, components);
|
|
2629
|
-
}
|
|
2630
|
-
function resolveAndCollectObjectProps2(schema, components) {
|
|
2631
|
-
const resolved = resolveSchemaRef2(schema, components);
|
|
2632
|
-
const properties = {};
|
|
2633
|
-
const required = [];
|
|
2634
|
-
const processSchema = (s) => {
|
|
2635
|
-
const current = resolveSchemaRef2(s, components);
|
|
2636
|
-
if (current.properties) {
|
|
2637
|
-
for (const [key, val] of Object.entries(current.properties)) {
|
|
2638
|
-
if (!properties[key]) {
|
|
2639
|
-
properties[key] = val;
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
if (current.required) {
|
|
2644
|
-
for (const req of current.required) {
|
|
2645
|
-
if (!required.includes(req)) {
|
|
2646
|
-
required.push(req);
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
if (current.allOf) {
|
|
2651
|
-
for (const branch of current.allOf) {
|
|
2652
|
-
processSchema(branch);
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
};
|
|
2656
|
-
processSchema(resolved);
|
|
2657
|
-
return { properties, required };
|
|
2658
|
-
}
|
|
2659
|
-
function isDeepObjectSchema2(schema, components) {
|
|
2660
|
-
const resolved = resolveSchemaRef2(schema, components);
|
|
2661
|
-
if (resolved.type === "array") {
|
|
2662
|
-
return false;
|
|
2663
|
-
}
|
|
2664
|
-
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
2665
|
-
return true;
|
|
2666
|
-
}
|
|
2667
|
-
if (resolved.allOf) {
|
|
2668
|
-
for (const branch of resolved.allOf) {
|
|
2669
|
-
if (isDeepObjectSchema2(branch, components)) {
|
|
2670
|
-
return true;
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
return false;
|
|
2675
|
-
}
|
|
2676
|
-
function isObjectLikeSchema2(schema, components) {
|
|
2677
|
-
const resolved = resolveSchemaRef2(schema, components);
|
|
2678
|
-
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
2679
|
-
return true;
|
|
2680
|
-
}
|
|
2681
|
-
if (resolved.allOf) {
|
|
2682
|
-
for (const branch of resolved.allOf) {
|
|
2683
|
-
if (isObjectLikeSchema2(branch, components)) {
|
|
2684
|
-
return true;
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
if (resolved.type === "array" && resolved.items) {
|
|
2689
|
-
const itemsSchema = resolveSchemaRef2(resolved.items, components);
|
|
2690
|
-
return isObjectLikeSchema2(itemsSchema, components);
|
|
2691
|
-
}
|
|
2692
|
-
return false;
|
|
2693
|
-
}
|
|
2694
|
-
function buildOperationEntry(op, ctx) {
|
|
2695
|
-
const args = {
|
|
2696
|
-
body: null,
|
|
2697
|
-
path: [],
|
|
2698
|
-
query: [],
|
|
2699
|
-
headers: [],
|
|
2700
|
-
cookies: []
|
|
2701
|
-
};
|
|
2702
|
-
buildPathArgs(op, ctx, args);
|
|
2703
|
-
buildQueryArgs(op, ctx, args);
|
|
2704
|
-
buildHeaderArgs(op, ctx, args);
|
|
2705
|
-
buildCookieArgs(op, ctx, args);
|
|
2706
|
-
if (op.bodyParamIndex !== null) {
|
|
2707
|
-
const bodyParam = op.parameters[op.bodyParamIndex];
|
|
2708
|
-
if (bodyParam) {
|
|
2709
|
-
const bodySchema = typeToJsonSchema(bodyParam.type, ctx);
|
|
2710
|
-
const schemaRef2 = bodySchema.$ref ?? "#/components/schemas/InlineBody";
|
|
2711
|
-
args.body = {
|
|
2712
|
-
index: bodyParam.index,
|
|
2713
|
-
required: !bodyParam.isOptional,
|
|
2714
|
-
contentType: op.bodyContentType ?? "application/json",
|
|
2715
|
-
schemaRef: schemaRef2
|
|
2716
|
-
};
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
const responseSchema = typeToJsonSchema(op.returnType, ctx, op.returnTypeNode);
|
|
2720
|
-
const status = op.httpMethod === "POST" ? 201 : 200;
|
|
2721
|
-
let schemaRef = responseSchema.$ref;
|
|
2722
|
-
let isArray = false;
|
|
2723
|
-
if (!schemaRef && responseSchema.type === "array" && responseSchema.items?.$ref) {
|
|
2724
|
-
schemaRef = responseSchema.items.$ref;
|
|
2725
|
-
isArray = true;
|
|
2726
|
-
} else if (!schemaRef) {
|
|
2727
|
-
schemaRef = "#/components/schemas/InlineResponse";
|
|
2728
|
-
}
|
|
2729
|
-
return {
|
|
2730
|
-
operationId: op.operationId,
|
|
2731
|
-
http: {
|
|
2732
|
-
method: op.httpMethod,
|
|
2733
|
-
path: op.path
|
|
2734
|
-
},
|
|
2735
|
-
handler: {
|
|
2736
|
-
methodName: op.methodName
|
|
2737
|
-
},
|
|
2738
|
-
args,
|
|
2739
|
-
responses: [
|
|
2740
|
-
{
|
|
2741
|
-
status,
|
|
2742
|
-
contentType: "application/json",
|
|
2743
|
-
schemaRef,
|
|
2744
|
-
isArray
|
|
2745
|
-
}
|
|
2746
|
-
]
|
|
2747
|
-
};
|
|
2748
|
-
}
|
|
2749
|
-
function buildPathArgs(op, ctx, args) {
|
|
2750
|
-
for (const paramIndex of op.pathParamIndices) {
|
|
2751
|
-
const param = op.parameters[paramIndex];
|
|
2752
|
-
if (param) {
|
|
2753
|
-
const paramSchema = typeToJsonSchema(param.type, ctx);
|
|
2754
|
-
args.path.push({
|
|
2755
|
-
name: param.name,
|
|
2756
|
-
index: param.index,
|
|
2757
|
-
required: !param.isOptional,
|
|
2758
|
-
schemaRef: paramSchema.$ref ?? "#/components/schemas/InlinePathParam",
|
|
2759
|
-
schemaType: paramSchema.type
|
|
2760
|
-
});
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
function buildQueryArgs(op, ctx, args) {
|
|
2765
|
-
if (op.queryObjectParamIndex !== null) {
|
|
2766
|
-
const queryParam = op.parameters[op.queryObjectParamIndex];
|
|
2767
|
-
if (!queryParam) return;
|
|
2768
|
-
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
2769
|
-
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps2(querySchema, ctx.components);
|
|
2770
|
-
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
2771
|
-
const isRequired = queryRequired.includes(propName) ?? false;
|
|
2772
|
-
const isDeepObject = isDeepObjectSchema2(propSchema, ctx.components);
|
|
2773
|
-
const isObjectLike = isObjectLikeSchema2(propSchema, ctx.components);
|
|
2774
|
-
let schemaRef = propSchema.$ref;
|
|
2775
|
-
if (!schemaRef) {
|
|
2776
|
-
schemaRef = "#/components/schemas/InlineQueryParam";
|
|
2777
|
-
}
|
|
2778
|
-
args.query.push({
|
|
2779
|
-
name: propName,
|
|
2780
|
-
index: queryParam.index,
|
|
2781
|
-
required: isRequired,
|
|
2782
|
-
schemaRef,
|
|
2783
|
-
schemaType: propSchema.type,
|
|
2784
|
-
serialization: isDeepObject ? { style: "deepObject", explode: true } : void 0,
|
|
2785
|
-
content: !isDeepObject && isObjectLike ? "application/json" : void 0
|
|
2786
|
-
});
|
|
2787
|
-
}
|
|
2788
|
-
}
|
|
2789
|
-
for (const paramIndex of op.queryParamIndices) {
|
|
2790
|
-
const param = op.parameters[paramIndex];
|
|
2791
|
-
if (param) {
|
|
2792
|
-
const paramSchema = typeToJsonSchema(param.type, ctx);
|
|
2793
|
-
const isDeepObject = isDeepObjectSchema2(paramSchema, ctx.components);
|
|
2794
|
-
const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
|
|
2795
|
-
const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
|
|
2796
|
-
args.query.push({
|
|
2797
|
-
name: param.name,
|
|
2798
|
-
index: param.index,
|
|
2799
|
-
required: !param.isOptional,
|
|
2800
|
-
schemaRef,
|
|
2801
|
-
schemaType: paramSchema.type,
|
|
2802
|
-
serialization: isDeepObject ? { style: "deepObject", explode: true } : void 0,
|
|
2803
|
-
content: !isDeepObject && isObjectLike ? "application/json" : void 0
|
|
2804
|
-
});
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
function buildHeaderArgs(op, ctx, args) {
|
|
2809
|
-
if (op.headerObjectParamIndex === null) return;
|
|
2810
|
-
const headerParam = op.parameters[op.headerObjectParamIndex];
|
|
2811
|
-
if (!headerParam) return;
|
|
2812
|
-
const headerSchema = typeToJsonSchema(headerParam.type, ctx);
|
|
2813
|
-
if (!headerSchema.properties) return;
|
|
2814
|
-
for (const [propName, propSchema] of Object.entries(headerSchema.properties)) {
|
|
2815
|
-
const isRequired = headerSchema.required?.includes(propName) ?? false;
|
|
2816
|
-
let schemaRef = propSchema.$ref;
|
|
2817
|
-
if (!schemaRef) {
|
|
2818
|
-
schemaRef = "#/components/schemas/InlineHeaderParam";
|
|
2819
|
-
}
|
|
2820
|
-
args.headers.push({
|
|
2821
|
-
name: propName,
|
|
2822
|
-
index: headerParam.index,
|
|
2823
|
-
required: isRequired,
|
|
2824
|
-
schemaRef,
|
|
2825
|
-
schemaType: propSchema.type
|
|
2826
|
-
});
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
function buildCookieArgs(op, ctx, args) {
|
|
2830
|
-
if (op.cookieObjectParamIndex === null) return;
|
|
2831
|
-
const cookieParam = op.parameters[op.cookieObjectParamIndex];
|
|
2832
|
-
if (!cookieParam) return;
|
|
2833
|
-
const cookieSchema = typeToJsonSchema(cookieParam.type, ctx);
|
|
2834
|
-
if (!cookieSchema.properties) return;
|
|
2835
|
-
for (const [propName, propSchema] of Object.entries(cookieSchema.properties)) {
|
|
2836
|
-
const isRequired = cookieSchema.required?.includes(propName) ?? false;
|
|
2837
|
-
let schemaRef = propSchema.$ref;
|
|
2838
|
-
if (!schemaRef) {
|
|
2839
|
-
schemaRef = "#/components/schemas/InlineCookieParam";
|
|
2840
|
-
}
|
|
2841
|
-
args.cookies.push({
|
|
2842
|
-
name: propName,
|
|
2843
|
-
index: cookieParam.index,
|
|
2844
|
-
required: isRequired,
|
|
2845
|
-
schemaRef,
|
|
2846
|
-
schemaType: propSchema.type,
|
|
2847
|
-
serialization: { style: "form", explode: true }
|
|
2848
|
-
});
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
// src/compiler/validation/emitPrecompiledValidators.ts
|
|
2853
|
-
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
2854
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
2855
|
-
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
2856
|
-
var import_ajv = __toESM(require("ajv"), 1);
|
|
2857
|
-
var import_ajv_formats = __toESM(require("ajv-formats"), 1);
|
|
2858
|
-
var OAS_SCHEMA_ONLY = /* @__PURE__ */ new Set(["discriminator", "xml", "externalDocs", "example"]);
|
|
2859
|
-
function sanitizeSchemaForAjv(schema) {
|
|
2860
|
-
if (schema === null || typeof schema !== "object") return schema;
|
|
2861
|
-
if (Array.isArray(schema)) return schema.map(sanitizeSchemaForAjv);
|
|
2862
|
-
const out = {};
|
|
2863
|
-
for (const [k, v] of Object.entries(schema)) {
|
|
2864
|
-
if (OAS_SCHEMA_ONLY.has(k)) continue;
|
|
2865
|
-
if (k.startsWith("x-")) continue;
|
|
2866
|
-
out[k] = sanitizeSchemaForAjv(v);
|
|
2867
|
-
}
|
|
2868
|
-
return out;
|
|
2869
|
-
}
|
|
2870
|
-
function rewriteComponentRefs(schema) {
|
|
2871
|
-
if (schema === null || typeof schema !== "object") return schema;
|
|
2872
|
-
if (Array.isArray(schema)) return schema.map(rewriteComponentRefs);
|
|
2873
|
-
if (typeof schema.$ref === "string") {
|
|
2874
|
-
const ref = schema.$ref;
|
|
2875
|
-
const m = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
2876
|
-
if (m) {
|
|
2877
|
-
return { ...schema, $ref: m[1] };
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
const out = {};
|
|
2881
|
-
for (const [k, v] of Object.entries(schema)) {
|
|
2882
|
-
out[k] = rewriteComponentRefs(v);
|
|
2883
|
-
}
|
|
2884
|
-
return out;
|
|
2885
|
-
}
|
|
2886
|
-
function safeId(s) {
|
|
2887
|
-
return s.replace(/[^A-Za-z0-9_]/g, "_").replace(/^[^A-Za-z_]/, "_$&");
|
|
2888
|
-
}
|
|
2889
|
-
function schemaNameFromRef(schemaRef) {
|
|
2890
|
-
return schemaRef.replace(/^#\/components\/schemas\//, "");
|
|
2891
|
-
}
|
|
2892
|
-
async function emitPrecompiledValidators(opts) {
|
|
2893
|
-
const outDir = opts.outDir;
|
|
2894
|
-
const cjsPath = import_node_path2.default.join(outDir, "validators.cjs");
|
|
2895
|
-
const esmPath = import_node_path2.default.join(outDir, "validators.mjs");
|
|
2896
|
-
const metaPath = import_node_path2.default.join(outDir, "validators.meta.json");
|
|
2897
|
-
import_node_fs2.default.mkdirSync(outDir, { recursive: true });
|
|
2898
|
-
const schemas = [];
|
|
2899
|
-
for (const [name, sch] of Object.entries(opts.openapi.components?.schemas ?? {})) {
|
|
2900
|
-
const clean = rewriteComponentRefs(sanitizeSchemaForAjv(sch));
|
|
2901
|
-
schemas.push({ ...clean, $id: name });
|
|
2902
|
-
}
|
|
2903
|
-
const opIndex = {};
|
|
2904
|
-
for (const ctrl of opts.manifest.controllers ?? []) {
|
|
2905
|
-
for (const op of ctrl.operations ?? []) {
|
|
2906
|
-
const entry = { response: {} };
|
|
2907
|
-
opIndex[op.operationId] = entry;
|
|
2908
|
-
if (op.args.body?.schemaRef) {
|
|
2909
|
-
const typeName = schemaNameFromRef(op.args.body.schemaRef);
|
|
2910
|
-
const id = safeId(`op_${op.operationId}_body`);
|
|
2911
|
-
schemas.push({ $id: id, $ref: typeName });
|
|
2912
|
-
entry.body = id;
|
|
2913
|
-
}
|
|
2914
|
-
for (const r of op.responses ?? []) {
|
|
2915
|
-
if (!r.schemaRef) continue;
|
|
2916
|
-
const typeName = schemaNameFromRef(r.schemaRef);
|
|
2917
|
-
const key = `${r.status}|${r.contentType}`;
|
|
2918
|
-
const id = safeId(`op_${op.operationId}_res_${r.status}_${r.contentType}`);
|
|
2919
|
-
schemas.push({ $id: id, $ref: typeName });
|
|
2920
|
-
entry.response[key] = id;
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
const strictOpt = opts.strict === "off" ? false : opts.strict === "log" ? "log" : true;
|
|
2925
|
-
const ajv = new import_ajv.default.default({
|
|
2926
|
-
schemas,
|
|
2927
|
-
allErrors: true,
|
|
2928
|
-
strict: strictOpt,
|
|
2929
|
-
code: { source: true }
|
|
2930
|
-
});
|
|
2931
|
-
import_ajv_formats.default.default(ajv, { mode: opts.formatsMode ?? "full" });
|
|
2932
|
-
let cjs;
|
|
2933
|
-
const standaloneModule = require("ajv/dist/standalone");
|
|
2934
|
-
if (typeof standaloneModule === "function") {
|
|
2935
|
-
cjs = standaloneModule(ajv);
|
|
2936
|
-
} else if (standaloneModule && typeof standaloneModule.default === "function") {
|
|
2937
|
-
cjs = standaloneModule.default(ajv);
|
|
2938
|
-
} else {
|
|
2939
|
-
throw new Error("Unable to find standalone code generator in ajv/dist/standalone");
|
|
2940
|
-
}
|
|
2941
|
-
cjs += "\n\n// --- adorn-api operation lookup (generated) ---\n";
|
|
2942
|
-
cjs += "exports.validators = {\n";
|
|
2943
|
-
for (const [operationId, v] of Object.entries(opIndex)) {
|
|
2944
|
-
cjs += ` ${JSON.stringify(operationId)}: {
|
|
2945
|
-
`;
|
|
2946
|
-
cjs += ` body: ${v.body ? `exports[${JSON.stringify(v.body)}]` : "undefined"},
|
|
2947
|
-
`;
|
|
2948
|
-
cjs += " response: {\n";
|
|
2949
|
-
for (const [key, id] of Object.entries(v.response)) {
|
|
2950
|
-
cjs += ` ${JSON.stringify(key)}: exports[${JSON.stringify(id)}],
|
|
2951
|
-
`;
|
|
2952
|
-
}
|
|
2953
|
-
cjs += " }\n";
|
|
2954
|
-
cjs += " },\n";
|
|
2955
|
-
}
|
|
2956
|
-
cjs += "};\n";
|
|
2957
|
-
import_node_fs2.default.writeFileSync(cjsPath, cjs, "utf8");
|
|
2958
|
-
const esm = `// .adorn/validators.mjs (generated)
|
|
2959
|
-
import { createRequire } from "node:module";
|
|
2960
|
-
const require = createRequire(import.meta.url);
|
|
2961
|
-
const cjs = require("./validators.cjs");
|
|
2962
|
-
|
|
2963
|
-
export const validators = cjs.validators;
|
|
2964
|
-
|
|
2965
|
-
export function validateBody(operationId, data) {
|
|
2966
|
-
const v = validators?.[operationId]?.body;
|
|
2967
|
-
if (!v) return { ok: true, errors: null };
|
|
2968
|
-
const ok = v(data);
|
|
2969
|
-
return { ok, errors: ok ? null : v.errors };
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
export function validateResponse(operationId, status, contentType, data) {
|
|
2973
|
-
const key = String(status) + "|" + String(contentType);
|
|
2974
|
-
const v = validators?.[operationId]?.response?.[key];
|
|
2975
|
-
if (!v) return { ok: true, errors: null };
|
|
2976
|
-
const ok = v(data);
|
|
2977
|
-
return { ok, errors: ok ? null : v.errors };
|
|
2978
|
-
}
|
|
2979
|
-
`;
|
|
2980
|
-
import_node_fs2.default.writeFileSync(esmPath, esm, "utf8");
|
|
2981
|
-
const hash = import_node_crypto.default.createHash("sha256").update(cjs).digest("hex");
|
|
2982
|
-
import_node_fs2.default.writeFileSync(metaPath, JSON.stringify({ hash }, null, 2), "utf8");
|
|
2983
|
-
return { validatorsCjsPath: cjsPath, validatorsEsmPath: esmPath, hash };
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
// src/compiler/cache/isStale.ts
|
|
2987
|
-
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
2988
|
-
var import_node_path3 = __toESM(require("path"), 1);
|
|
2989
|
-
var import_meta = {};
|
|
2990
|
-
function readJson(p) {
|
|
2991
|
-
try {
|
|
2992
|
-
return JSON.parse(import_node_fs3.default.readFileSync(p, "utf8"));
|
|
2993
|
-
} catch {
|
|
2994
|
-
return null;
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
function statMtimeMs(p) {
|
|
2998
|
-
try {
|
|
2999
|
-
return import_node_fs3.default.statSync(p).mtimeMs;
|
|
3000
|
-
} catch {
|
|
3001
|
-
return null;
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
function ensureAbs(p) {
|
|
3005
|
-
return import_node_path3.default.isAbsolute(p) ? p : import_node_path3.default.resolve(p);
|
|
3006
|
-
}
|
|
3007
|
-
function collectTsconfigChain(tsconfigPathAbs) {
|
|
3008
|
-
const out = [];
|
|
3009
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3010
|
-
function visit(pAbs) {
|
|
3011
|
-
if (seen.has(pAbs)) return;
|
|
3012
|
-
seen.add(pAbs);
|
|
3013
|
-
out.push(pAbs);
|
|
3014
|
-
const raw = readJson(pAbs);
|
|
3015
|
-
const ext = raw?.extends;
|
|
3016
|
-
if (!ext) return;
|
|
3017
|
-
let resolved = null;
|
|
3018
|
-
if (ext.startsWith(".") || ext.startsWith("/") || /^[A-Za-z]:\\/.test(ext)) {
|
|
3019
|
-
resolved = ensureAbs(import_node_path3.default.resolve(import_node_path3.default.dirname(pAbs), ext));
|
|
3020
|
-
if (!resolved.endsWith(".json")) resolved += ".json";
|
|
3021
|
-
} else {
|
|
3022
|
-
try {
|
|
3023
|
-
const req = module.createRequire?.(import_meta.url) ?? require;
|
|
3024
|
-
resolved = req.resolve(ext);
|
|
3025
|
-
} catch {
|
|
3026
|
-
try {
|
|
3027
|
-
const req = module.createRequire?.(import_meta.url) ?? require;
|
|
3028
|
-
resolved = req.resolve(ext.endsWith(".json") ? ext : `${ext}.json`);
|
|
3029
|
-
} catch {
|
|
3030
|
-
resolved = null;
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
if (resolved) visit(resolved);
|
|
3035
|
-
}
|
|
3036
|
-
visit(tsconfigPathAbs);
|
|
3037
|
-
return out;
|
|
3038
|
-
}
|
|
3039
|
-
function findLockfile(startDir) {
|
|
3040
|
-
const names = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
3041
|
-
let dir = startDir;
|
|
3042
|
-
for (let i = 0; i < 20; i++) {
|
|
3043
|
-
for (const n of names) {
|
|
3044
|
-
const p = import_node_path3.default.join(dir, n);
|
|
3045
|
-
const mt = statMtimeMs(p);
|
|
3046
|
-
if (mt !== null) return { path: p, mtimeMs: mt };
|
|
3047
|
-
}
|
|
3048
|
-
const parent = import_node_path3.default.dirname(dir);
|
|
3049
|
-
if (parent === dir) break;
|
|
3050
|
-
dir = parent;
|
|
3051
|
-
}
|
|
3052
|
-
return null;
|
|
3053
|
-
}
|
|
3054
|
-
async function isStale(params) {
|
|
3055
|
-
const outDirAbs = ensureAbs(params.outDir);
|
|
3056
|
-
const tsconfigAbs = ensureAbs(params.project);
|
|
3057
|
-
const manifestPath = import_node_path3.default.join(outDirAbs, "manifest.json");
|
|
3058
|
-
const cachePath = import_node_path3.default.join(outDirAbs, "cache.json");
|
|
3059
|
-
if (!import_node_fs3.default.existsSync(manifestPath)) return { stale: true, reason: "missing-manifest" };
|
|
3060
|
-
const cache = readJson(cachePath);
|
|
3061
|
-
if (!cache) return { stale: true, reason: "missing-cache" };
|
|
3062
|
-
if (cache.generator.version !== params.adornVersion) {
|
|
3063
|
-
return { stale: true, reason: "generator-version-changed", detail: `${cache.generator.version} -> ${params.adornVersion}` };
|
|
3064
|
-
}
|
|
3065
|
-
if (ensureAbs(cache.project.tsconfigPath) !== tsconfigAbs) {
|
|
3066
|
-
return { stale: true, reason: "tsconfig-changed", detail: "different project path" };
|
|
3067
|
-
}
|
|
3068
|
-
const chain = collectTsconfigChain(tsconfigAbs);
|
|
3069
|
-
for (const cfg of chain) {
|
|
3070
|
-
const mt = statMtimeMs(cfg);
|
|
3071
|
-
if (mt === null) return { stale: true, reason: "config-missing", detail: cfg };
|
|
3072
|
-
const cachedMt = cache.project.configFiles[cfg];
|
|
3073
|
-
if (cachedMt === null || Math.abs(cachedMt - mt) > 1e-4) {
|
|
3074
|
-
return { stale: true, reason: "config-updated", detail: cfg };
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
if (cache.project.lockfile?.path) {
|
|
3078
|
-
const mt = statMtimeMs(cache.project.lockfile.path);
|
|
3079
|
-
if (mt === null) return { stale: true, reason: "lockfile-missing", detail: cache.project.lockfile.path };
|
|
3080
|
-
if (Math.abs(cache.project.lockfile.mtimeMs - mt) > 1e-4) {
|
|
3081
|
-
return { stale: true, reason: "lockfile-updated", detail: cache.project.lockfile.path };
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
for (const [file, cachedMt] of Object.entries(cache.inputs)) {
|
|
3085
|
-
const mt = statMtimeMs(file);
|
|
3086
|
-
if (mt === null) return { stale: true, reason: "input-missing", detail: file };
|
|
3087
|
-
if (Math.abs(cachedMt - mt) > 1e-4) return { stale: true, reason: "input-updated", detail: file };
|
|
3088
|
-
}
|
|
3089
|
-
return { stale: false, reason: "up-to-date" };
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
// src/compiler/cache/writeCache.ts
|
|
3093
|
-
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
3094
|
-
var import_node_path4 = __toESM(require("path"), 1);
|
|
3095
|
-
var import_typescript14 = __toESM(require("typescript"), 1);
|
|
3096
|
-
function statMtimeMs2(p) {
|
|
3097
|
-
return import_node_fs4.default.statSync(p).mtimeMs;
|
|
3098
|
-
}
|
|
3099
|
-
function ensureDir(p) {
|
|
3100
|
-
import_node_fs4.default.mkdirSync(p, { recursive: true });
|
|
3101
|
-
}
|
|
3102
|
-
function isProjectSourceFile(f) {
|
|
3103
|
-
if (f.includes(`${import_node_path4.default.sep}node_modules${import_node_path4.default.sep}`)) return false;
|
|
3104
|
-
if (f.includes(`${import_node_path4.default.sep}typescript${import_node_path4.default.sep}lib${import_node_path4.default.sep}`)) return false;
|
|
3105
|
-
return /\.(ts|tsx|mts|cts)$/.test(f);
|
|
3106
|
-
}
|
|
3107
|
-
function writeCache(params) {
|
|
3108
|
-
const outDirAbs = import_node_path4.default.isAbsolute(params.outDir) ? params.outDir : import_node_path4.default.resolve(params.outDir);
|
|
3109
|
-
ensureDir(outDirAbs);
|
|
3110
|
-
const configFiles = collectTsconfigChain(params.tsconfigAbs);
|
|
3111
|
-
const configMtimes = {};
|
|
3112
|
-
for (const cfg of configFiles) configMtimes[cfg] = statMtimeMs2(cfg);
|
|
3113
|
-
const lock = findLockfile(import_node_path4.default.dirname(params.tsconfigAbs));
|
|
3114
|
-
const inputs = {};
|
|
3115
|
-
for (const sf of params.program.getSourceFiles()) {
|
|
3116
|
-
const f = sf.fileName;
|
|
3117
|
-
if (!isProjectSourceFile(f)) continue;
|
|
3118
|
-
try {
|
|
3119
|
-
inputs[f] = statMtimeMs2(f);
|
|
3120
|
-
} catch {
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
const cache = {
|
|
3124
|
-
cacheVersion: 1,
|
|
3125
|
-
generator: {
|
|
3126
|
-
name: "adorn-api",
|
|
3127
|
-
version: params.adornVersion,
|
|
3128
|
-
typescript: import_typescript14.default.version
|
|
3129
|
-
},
|
|
3130
|
-
project: {
|
|
3131
|
-
tsconfigPath: params.tsconfigAbs,
|
|
3132
|
-
configFiles: configMtimes,
|
|
3133
|
-
lockfile: lock ?? null
|
|
3134
|
-
},
|
|
3135
|
-
inputs
|
|
3136
|
-
};
|
|
3137
|
-
import_node_fs4.default.writeFileSync(import_node_path4.default.join(outDirAbs, "cache.json"), JSON.stringify(cache, null, 2), "utf8");
|
|
3138
|
-
}
|
|
3139
|
-
|
|
3140
|
-
// src/cli/progress.ts
|
|
3141
|
-
var import_node_process = __toESM(require("process"), 1);
|
|
3142
|
-
var ProgressTracker = class {
|
|
3143
|
-
phases = /* @__PURE__ */ new Map();
|
|
3144
|
-
startTime;
|
|
3145
|
-
verbose = false;
|
|
3146
|
-
quiet = false;
|
|
3147
|
-
indentLevel = 0;
|
|
3148
|
-
constructor(options = {}) {
|
|
3149
|
-
this.verbose = options.verbose ?? false;
|
|
3150
|
-
this.quiet = options.quiet ?? false;
|
|
3151
|
-
this.startTime = performance.now();
|
|
3152
|
-
}
|
|
3153
|
-
/**
|
|
3154
|
-
* Start a new phase.
|
|
3155
|
-
*/
|
|
3156
|
-
startPhase(name, message) {
|
|
3157
|
-
this.phases.set(name, {
|
|
3158
|
-
name,
|
|
3159
|
-
startTime: performance.now(),
|
|
3160
|
-
status: "running",
|
|
3161
|
-
message
|
|
3162
|
-
});
|
|
3163
|
-
if (!this.quiet) {
|
|
3164
|
-
this.log(`\u25CF ${message || name}`);
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
/**
|
|
3168
|
-
* Complete a phase successfully.
|
|
3169
|
-
*/
|
|
3170
|
-
completePhase(name, message) {
|
|
3171
|
-
const phase = this.phases.get(name);
|
|
3172
|
-
if (phase) {
|
|
3173
|
-
phase.endTime = performance.now();
|
|
3174
|
-
phase.status = "completed";
|
|
3175
|
-
phase.message = message;
|
|
3176
|
-
}
|
|
3177
|
-
if (!this.quiet) {
|
|
3178
|
-
const elapsed = phase ? this.formatElapsed(phase.startTime, phase.endTime) : "";
|
|
3179
|
-
const status = this.verbose ? `\u2713 ${message || name} ${elapsed}` : `\u2713 ${message || name} ${elapsed}`;
|
|
3180
|
-
this.log(status);
|
|
3181
|
-
}
|
|
3182
|
-
}
|
|
3183
|
-
/**
|
|
3184
|
-
* Mark a phase as failed.
|
|
3185
|
-
*/
|
|
3186
|
-
failPhase(name, message) {
|
|
3187
|
-
const phase = this.phases.get(name);
|
|
3188
|
-
if (phase) {
|
|
3189
|
-
phase.endTime = performance.now();
|
|
3190
|
-
phase.status = "failed";
|
|
3191
|
-
phase.message = message;
|
|
3192
|
-
}
|
|
3193
|
-
if (!this.quiet) {
|
|
3194
|
-
this.log(`\u2717 ${message || name}`);
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
/**
|
|
3198
|
-
* Log a verbose message.
|
|
3199
|
-
*/
|
|
3200
|
-
verboseLog(message) {
|
|
3201
|
-
if (this.verbose && !this.quiet) {
|
|
3202
|
-
const elapsed = this.formatElapsed(this.startTime);
|
|
3203
|
-
this.log(`[${elapsed}] ${message}`);
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
/**
|
|
3207
|
-
* Log a regular message.
|
|
3208
|
-
*/
|
|
3209
|
-
log(message) {
|
|
3210
|
-
const indent = " ".repeat(this.indentLevel);
|
|
3211
|
-
import_node_process.default.stdout.write(indent + message + "\n");
|
|
3212
|
-
}
|
|
3213
|
-
/**
|
|
3214
|
-
* Log a sub-message (indented).
|
|
3215
|
-
*/
|
|
3216
|
-
logSub(message) {
|
|
3217
|
-
this.indentLevel++;
|
|
3218
|
-
this.log(message);
|
|
3219
|
-
this.indentLevel--;
|
|
3220
|
-
}
|
|
3221
|
-
/**
|
|
3222
|
-
* Get the total elapsed time in milliseconds.
|
|
3223
|
-
*/
|
|
3224
|
-
getTotalElapsed() {
|
|
3225
|
-
return performance.now() - this.startTime;
|
|
3226
|
-
}
|
|
3227
|
-
/**
|
|
3228
|
-
* Format elapsed time as a human-readable string.
|
|
3229
|
-
*/
|
|
3230
|
-
formatElapsed(startTime, endTime) {
|
|
3231
|
-
const elapsed = (endTime ?? performance.now()) - (startTime ?? this.startTime);
|
|
3232
|
-
if (elapsed < 1) {
|
|
3233
|
-
return `${(elapsed * 1e3).toFixed(0)}ms`;
|
|
3234
|
-
} else if (elapsed < 1e3) {
|
|
3235
|
-
return `${elapsed.toFixed(0)}ms`;
|
|
3236
|
-
} else {
|
|
3237
|
-
return `${(elapsed / 1e3).toFixed(2)}s`;
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
|
-
/**
|
|
3241
|
-
* Get all completed phases with their timings.
|
|
3242
|
-
*/
|
|
3243
|
-
getPhases() {
|
|
3244
|
-
return Array.from(this.phases.values());
|
|
3245
|
-
}
|
|
3246
|
-
/**
|
|
3247
|
-
* Print a build summary.
|
|
3248
|
-
*/
|
|
3249
|
-
printSummary(stats) {
|
|
3250
|
-
if (this.quiet) return;
|
|
3251
|
-
this.log("");
|
|
3252
|
-
this.log("Build Summary:");
|
|
3253
|
-
this.log(` Controllers: ${stats.controllers}`);
|
|
3254
|
-
this.log(` Operations: ${stats.operations}`);
|
|
3255
|
-
this.log(` Schemas: ${stats.schemas}`);
|
|
3256
|
-
this.log(` Source files: ${stats.sourceFiles}`);
|
|
3257
|
-
this.log(` Output dir: ${stats.artifactsWritten[0]?.split("/").slice(0, -1).join("/") || ".adorn"}`);
|
|
3258
|
-
this.log("");
|
|
3259
|
-
this.log("Timings:");
|
|
3260
|
-
for (const phase of this.phases.values()) {
|
|
3261
|
-
if (phase.status === "completed" && phase.endTime) {
|
|
3262
|
-
const elapsed = phase.endTime - phase.startTime;
|
|
3263
|
-
const timeStr = elapsed < 1 ? `${(elapsed * 1e3).toFixed(0)}ms` : elapsed < 1e3 ? `${elapsed.toFixed(0)}ms` : `${(elapsed / 1e3).toFixed(2)}s`;
|
|
3264
|
-
this.log(` ${phase.name.padEnd(20)} ${timeStr}`);
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3267
|
-
this.log(` ${"\u2500".repeat(21)}`);
|
|
3268
|
-
this.log(` Total time: ${this.formatElapsed()}`);
|
|
3269
|
-
this.log("");
|
|
3270
|
-
}
|
|
3271
|
-
/**
|
|
3272
|
-
* Print artifact list.
|
|
3273
|
-
*/
|
|
3274
|
-
printArtifacts(artifacts) {
|
|
3275
|
-
if (this.quiet) return;
|
|
3276
|
-
this.log("Written artifacts:");
|
|
3277
|
-
for (const artifact of artifacts) {
|
|
3278
|
-
const sizeStr = artifact.size ? ` (${artifact.size >= 1024 ? `${(artifact.size / 1024).toFixed(1)} KB` : `${artifact.size} B`})` : "";
|
|
3279
|
-
this.log(` \u251C\u2500\u2500 ${artifact.name}${sizeStr}`);
|
|
3280
|
-
}
|
|
3281
|
-
}
|
|
3282
|
-
};
|
|
3283
|
-
var Spinner = class {
|
|
3284
|
-
frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u28B0", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3285
|
-
interval;
|
|
3286
|
-
message;
|
|
3287
|
-
current = 0;
|
|
3288
|
-
total = 0;
|
|
3289
|
-
customStatus;
|
|
3290
|
-
frameIndex = 0;
|
|
3291
|
-
lastLineLength = 0;
|
|
3292
|
-
constructor(message = "") {
|
|
3293
|
-
this.message = message;
|
|
3294
|
-
}
|
|
3295
|
-
/**
|
|
3296
|
-
* Start the spinner.
|
|
3297
|
-
*/
|
|
3298
|
-
start() {
|
|
3299
|
-
this.interval = setInterval(() => {
|
|
3300
|
-
const frame = this.frames[this.frameIndex];
|
|
3301
|
-
let output;
|
|
3302
|
-
if (this.customStatus) {
|
|
3303
|
-
output = `\r${frame} ${this.customStatus}`;
|
|
3304
|
-
} else if (this.total > 0) {
|
|
3305
|
-
output = `\r${frame} ${this.message} (${this.current}/${this.total})`;
|
|
3306
|
-
} else {
|
|
3307
|
-
output = `\r${frame} ${this.message}`;
|
|
3308
|
-
}
|
|
3309
|
-
this.lastLineLength = output.length - 1;
|
|
3310
|
-
import_node_process.default.stdout.write(output);
|
|
3311
|
-
if (import_node_process.default.stdout.writable) {
|
|
3312
|
-
import_node_process.default.stdout.write("");
|
|
3313
|
-
}
|
|
3314
|
-
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
3315
|
-
}, 80);
|
|
3316
|
-
}
|
|
3317
|
-
/**
|
|
3318
|
-
* Set progress counters.
|
|
3319
|
-
*/
|
|
3320
|
-
setProgress(current, total) {
|
|
3321
|
-
this.current = current;
|
|
3322
|
-
this.total = total;
|
|
3323
|
-
}
|
|
3324
|
-
/**
|
|
3325
|
-
* Set a custom status message (overrides counters).
|
|
3326
|
-
*/
|
|
3327
|
-
setStatus(status) {
|
|
3328
|
-
this.customStatus = status;
|
|
3329
|
-
const frame = this.frames[this.frameIndex];
|
|
3330
|
-
const output = `\r${frame} ${status}`;
|
|
3331
|
-
const clearLength = Math.max(this.lastLineLength, output.length - 1);
|
|
3332
|
-
import_node_process.default.stdout.write("\r" + " ".repeat(clearLength) + "\r");
|
|
3333
|
-
this.lastLineLength = output.length - 1;
|
|
3334
|
-
import_node_process.default.stdout.write(output);
|
|
3335
|
-
if (import_node_process.default.stdout.writable) {
|
|
3336
|
-
import_node_process.default.stdout.write("");
|
|
3337
|
-
}
|
|
3338
|
-
}
|
|
3339
|
-
/**
|
|
3340
|
-
* Clear the custom status message.
|
|
3341
|
-
*/
|
|
3342
|
-
clearStatus() {
|
|
3343
|
-
this.customStatus = void 0;
|
|
3344
|
-
}
|
|
3345
|
-
/**
|
|
3346
|
-
* Stop the spinner with a completion message.
|
|
3347
|
-
*/
|
|
3348
|
-
stop(completedMessage) {
|
|
3349
|
-
if (this.interval) {
|
|
3350
|
-
clearInterval(this.interval);
|
|
3351
|
-
this.interval = void 0;
|
|
3352
|
-
}
|
|
3353
|
-
import_node_process.default.stdout.write("\r" + " ".repeat(this.lastLineLength) + "\r");
|
|
3354
|
-
if (completedMessage) {
|
|
3355
|
-
import_node_process.default.stdout.write(completedMessage + "\n");
|
|
3356
|
-
}
|
|
3357
|
-
if (import_node_process.default.stdout.writable) {
|
|
3358
|
-
import_node_process.default.stdout.write("");
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
/**
|
|
3362
|
-
* Stop the spinner with a failure message.
|
|
3363
|
-
*/
|
|
3364
|
-
fail(failedMessage) {
|
|
3365
|
-
this.stop();
|
|
3366
|
-
if (failedMessage) {
|
|
3367
|
-
import_node_process.default.stdout.write(`\u2717 ${failedMessage}
|
|
3368
|
-
`);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
};
|
|
3372
|
-
|
|
3373
|
-
// src/compiler/schema/partitioner.ts
|
|
3374
|
-
var DEFAULT_CONFIG = {
|
|
3375
|
-
strategy: "auto",
|
|
3376
|
-
threshold: 50,
|
|
3377
|
-
maxGroupSize: 50,
|
|
3378
|
-
complexityThreshold: 10,
|
|
3379
|
-
verbose: false
|
|
3380
|
-
};
|
|
3381
|
-
function calculateSchemaComplexity(schema) {
|
|
3382
|
-
let propertyCount = 0;
|
|
3383
|
-
let nestedDepth = 0;
|
|
3384
|
-
let refCount = 0;
|
|
3385
|
-
let hasUnion = false;
|
|
3386
|
-
let hasIntersection = false;
|
|
3387
|
-
let hasEnum = false;
|
|
3388
|
-
const jsonSize = JSON.stringify(schema).length;
|
|
3389
|
-
const analyze = (s, depth) => {
|
|
3390
|
-
if (!s || typeof s !== "object") return;
|
|
3391
|
-
nestedDepth = Math.max(nestedDepth, depth);
|
|
3392
|
-
if (s.type === "object" && s.properties) {
|
|
3393
|
-
propertyCount += Object.keys(s.properties).length;
|
|
3394
|
-
for (const prop of Object.values(s.properties)) {
|
|
3395
|
-
analyze(prop, depth + 1);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
if (s.$ref) refCount++;
|
|
3399
|
-
if (s.anyOf || s.oneOf) hasUnion = true;
|
|
3400
|
-
if (s.allOf) hasIntersection = true;
|
|
3401
|
-
if (s.enum) hasEnum = true;
|
|
3402
|
-
if (s.items) {
|
|
3403
|
-
analyze(s.items, depth + 1);
|
|
3404
|
-
}
|
|
3405
|
-
};
|
|
3406
|
-
analyze(schema, 0);
|
|
3407
|
-
const complexity = propertyCount * 1 + nestedDepth * 2 + refCount * 0.5 + (hasUnion ? 5 : 0) + (hasIntersection ? 5 : 0) + (hasEnum ? 1 : 0);
|
|
3408
|
-
return {
|
|
3409
|
-
propertyCount,
|
|
3410
|
-
nestedDepth,
|
|
3411
|
-
refCount,
|
|
3412
|
-
hasUnion,
|
|
3413
|
-
hasIntersection,
|
|
3414
|
-
hasEnum,
|
|
3415
|
-
jsonSize
|
|
3416
|
-
};
|
|
3417
|
-
}
|
|
3418
|
-
function countExternalRefs(schema, allSchemas) {
|
|
3419
|
-
let count = 0;
|
|
3420
|
-
const analyze = (s) => {
|
|
3421
|
-
if (!s || typeof s !== "object") return;
|
|
3422
|
-
if (s.$ref && typeof s.$ref === "string") {
|
|
3423
|
-
const refName = s.$ref.replace("#/components/schemas/", "");
|
|
3424
|
-
if (refName && allSchemas.has(refName)) {
|
|
3425
|
-
count++;
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
if (s.properties) {
|
|
3429
|
-
for (const prop of Object.values(s.properties)) {
|
|
3430
|
-
analyze(prop);
|
|
3431
|
-
}
|
|
3432
|
-
}
|
|
3433
|
-
if (s.items) analyze(s.items);
|
|
3434
|
-
if (s.anyOf) s.anyOf.forEach(analyze);
|
|
3435
|
-
if (s.oneOf) s.oneOf.forEach(analyze);
|
|
3436
|
-
if (s.allOf) s.allOf.forEach(analyze);
|
|
3437
|
-
};
|
|
3438
|
-
analyze(schema);
|
|
3439
|
-
return count;
|
|
3440
|
-
}
|
|
3441
|
-
function analyzeDependencyDensity(schemas) {
|
|
3442
|
-
let totalDeps = 0;
|
|
3443
|
-
let maxDeps = 0;
|
|
3444
|
-
for (const schema of schemas.values()) {
|
|
3445
|
-
const deps = countExternalRefs(schema, schemas);
|
|
3446
|
-
totalDeps += deps;
|
|
3447
|
-
maxDeps = Math.max(maxDeps, deps);
|
|
3448
|
-
}
|
|
3449
|
-
return {
|
|
3450
|
-
avgDeps: schemas.size > 0 ? totalDeps / schemas.size : 0,
|
|
3451
|
-
maxDeps
|
|
3452
|
-
};
|
|
3453
|
-
}
|
|
3454
|
-
function partitionByController(schemas, graph, config) {
|
|
3455
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3456
|
-
const sharedSchemas = /* @__PURE__ */ new Map();
|
|
3457
|
-
groups.set("_shared", sharedSchemas);
|
|
3458
|
-
const schemaUsage = /* @__PURE__ */ new Map();
|
|
3459
|
-
for (const [nodeId, node] of graph.nodes.entries()) {
|
|
3460
|
-
if (node.kind === "Operation") {
|
|
3461
|
-
const opNode = node;
|
|
3462
|
-
const returnType = opNode.operation?.returnType;
|
|
3463
|
-
if (returnType && schemas.has(returnType)) {
|
|
3464
|
-
if (!schemaUsage.has(returnType)) {
|
|
3465
|
-
schemaUsage.set(returnType, /* @__PURE__ */ new Set());
|
|
3466
|
-
}
|
|
3467
|
-
schemaUsage.get(returnType).add(node.metadata.name);
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
for (const [schemaName, schema] of schemas.entries()) {
|
|
3472
|
-
const usage = schemaUsage.get(schemaName);
|
|
3473
|
-
if (!usage || usage.size === 0) {
|
|
3474
|
-
sharedSchemas.set(schemaName, schema);
|
|
3475
|
-
} else if (usage.size === 1) {
|
|
3476
|
-
const controllerName = Array.from(usage)[0].split(":")[0] || "default";
|
|
3477
|
-
const groupName = controllerName.toLowerCase();
|
|
3478
|
-
if (!groups.has(groupName)) {
|
|
3479
|
-
groups.set(groupName, /* @__PURE__ */ new Map());
|
|
3480
|
-
}
|
|
3481
|
-
groups.get(groupName).set(schemaName, schema);
|
|
3482
|
-
} else {
|
|
3483
|
-
sharedSchemas.set(schemaName, schema);
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3486
|
-
return Array.from(groups.entries()).map(([name, schemaMap]) => {
|
|
3487
|
-
let totalComplexity = 0;
|
|
3488
|
-
const dependencies = [];
|
|
3489
|
-
for (const [schemaName, schema] of schemaMap.entries()) {
|
|
3490
|
-
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
3491
|
-
const deps = countExternalRefs(schema, schemas);
|
|
3492
|
-
for (let i = 0; i < deps; i++) {
|
|
3493
|
-
dependencies.push(schemaName);
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
return {
|
|
3497
|
-
name,
|
|
3498
|
-
schemas: schemaMap,
|
|
3499
|
-
complexity: totalComplexity,
|
|
3500
|
-
dependencies
|
|
3501
|
-
};
|
|
3502
|
-
});
|
|
3503
|
-
}
|
|
3504
|
-
function partitionByDependency(schemas, schemaGraph, config) {
|
|
3505
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3506
|
-
const sccs = schemaGraph.findStronglyConnectedComponents();
|
|
3507
|
-
const processed = /* @__PURE__ */ new Set();
|
|
3508
|
-
for (const scc of sccs) {
|
|
3509
|
-
if (scc.length === 1 && processed.has(scc[0])) continue;
|
|
3510
|
-
const groupSchemas = /* @__PURE__ */ new Map();
|
|
3511
|
-
const groupName = `dependent-${groups.size + 1}`;
|
|
3512
|
-
for (const nodeId of scc) {
|
|
3513
|
-
const node = schemaGraph.getGraph().nodes.get(nodeId);
|
|
3514
|
-
if (node && node.kind === "TypeDefinition") {
|
|
3515
|
-
const schemaName = node.metadata.name;
|
|
3516
|
-
if (schemas.has(schemaName)) {
|
|
3517
|
-
groupSchemas.set(schemaName, schemas.get(schemaName));
|
|
3518
|
-
processed.add(nodeId);
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
}
|
|
3522
|
-
if (groupSchemas.size > 0) {
|
|
3523
|
-
groups.set(groupName, groupSchemas);
|
|
3524
|
-
}
|
|
3525
|
-
}
|
|
3526
|
-
for (const [nodeId, node] of schemaGraph.getGraph().nodes.entries()) {
|
|
3527
|
-
if (processed.has(nodeId)) continue;
|
|
3528
|
-
if (node.kind !== "TypeDefinition") continue;
|
|
3529
|
-
const schemaName = node.metadata.name;
|
|
3530
|
-
if (!schemas.has(schemaName)) continue;
|
|
3531
|
-
const groupSchemas = /* @__PURE__ */ new Map();
|
|
3532
|
-
groupSchemas.set(schemaName, schemas.get(schemaName));
|
|
3533
|
-
groups.set(`standalone-${groups.size + 1}`, groupSchemas);
|
|
3534
|
-
processed.add(nodeId);
|
|
3535
|
-
}
|
|
3536
|
-
return Array.from(groups.entries()).map(([name, schemaMap]) => {
|
|
3537
|
-
let totalComplexity = 0;
|
|
3538
|
-
const dependencies = [];
|
|
3539
|
-
for (const [schemaName, schema] of schemaMap.entries()) {
|
|
3540
|
-
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
3541
|
-
const deps = countExternalRefs(schema, schemas);
|
|
3542
|
-
for (let i = 0; i < deps; i++) {
|
|
3543
|
-
dependencies.push(schemaName);
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
return {
|
|
3547
|
-
name,
|
|
3548
|
-
schemas: schemaMap,
|
|
3549
|
-
complexity: totalComplexity,
|
|
3550
|
-
dependencies
|
|
3551
|
-
};
|
|
3552
|
-
});
|
|
3553
|
-
}
|
|
3554
|
-
function partitionBySize(schemas, config) {
|
|
3555
|
-
const sortedSchemas = Array.from(schemas.entries()).sort((a, b) => {
|
|
3556
|
-
const complexityA = calculateSchemaComplexity(a[1]).propertyCount;
|
|
3557
|
-
const complexityB = calculateSchemaComplexity(b[1]).propertyCount;
|
|
3558
|
-
return complexityB - complexityA;
|
|
3559
|
-
});
|
|
3560
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3561
|
-
let currentGroup = /* @__PURE__ */ new Map();
|
|
3562
|
-
let currentCount = 0;
|
|
3563
|
-
let groupIndex = 1;
|
|
3564
|
-
for (const [schemaName, schema] of sortedSchemas) {
|
|
3565
|
-
if (currentCount >= config.maxGroupSize) {
|
|
3566
|
-
groups.set(`group-${groupIndex}`, currentGroup);
|
|
3567
|
-
currentGroup = /* @__PURE__ */ new Map();
|
|
3568
|
-
currentCount = 0;
|
|
3569
|
-
groupIndex++;
|
|
3570
|
-
}
|
|
3571
|
-
currentGroup.set(schemaName, schema);
|
|
3572
|
-
currentCount++;
|
|
3573
|
-
}
|
|
3574
|
-
if (currentCount > 0) {
|
|
3575
|
-
groups.set(`group-${groupIndex}`, currentGroup);
|
|
3576
|
-
}
|
|
3577
|
-
return Array.from(groups.entries()).map(([name, schemaMap]) => {
|
|
3578
|
-
let totalComplexity = 0;
|
|
3579
|
-
const dependencies = [];
|
|
3580
|
-
for (const [schemaName, schema] of schemaMap.entries()) {
|
|
3581
|
-
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
3582
|
-
const deps = countExternalRefs(schema, schemas);
|
|
3583
|
-
for (let i = 0; i < deps; i++) {
|
|
3584
|
-
dependencies.push(schemaName);
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
return {
|
|
3588
|
-
name,
|
|
3589
|
-
schemas: schemaMap,
|
|
3590
|
-
complexity: totalComplexity,
|
|
3591
|
-
dependencies
|
|
3592
|
-
};
|
|
3593
|
-
});
|
|
3594
|
-
}
|
|
3595
|
-
function determineBestStrategy(schemas, graph, schemaGraph, config) {
|
|
3596
|
-
const schemaCount = schemas.size;
|
|
3597
|
-
const { avgDeps } = analyzeDependencyDensity(schemas);
|
|
3598
|
-
let controllerGroups = 0;
|
|
3599
|
-
for (const node of graph.nodes.values()) {
|
|
3600
|
-
if (node.kind === "Controller") {
|
|
3601
|
-
controllerGroups++;
|
|
3602
|
-
}
|
|
3603
|
-
}
|
|
3604
|
-
if (schemaCount < config.threshold) {
|
|
3605
|
-
return "none";
|
|
3606
|
-
}
|
|
3607
|
-
if (avgDeps > 3) {
|
|
3608
|
-
return "dependency";
|
|
3609
|
-
}
|
|
3610
|
-
if (controllerGroups > 1 && avgDeps < 2) {
|
|
3611
|
-
return "controller";
|
|
3612
|
-
}
|
|
3613
|
-
return "size";
|
|
3614
|
-
}
|
|
3615
|
-
function partitionSchemas(schemas, graph, schemaGraph, config = {}) {
|
|
3616
|
-
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
3617
|
-
const schemaCount = schemas.size;
|
|
3618
|
-
let totalComplexity = 0;
|
|
3619
|
-
let totalSchemas = 0;
|
|
3620
|
-
const { avgDeps } = analyzeDependencyDensity(schemas);
|
|
3621
|
-
let controllerGroups = 0;
|
|
3622
|
-
for (const node of graph.nodes.values()) {
|
|
3623
|
-
if (node.kind === "Controller") {
|
|
3624
|
-
controllerGroups++;
|
|
3625
|
-
}
|
|
3626
|
-
}
|
|
3627
|
-
for (const schema of schemas.values()) {
|
|
3628
|
-
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
3629
|
-
totalSchemas++;
|
|
3630
|
-
}
|
|
3631
|
-
const avgComplexity = totalSchemas > 0 ? totalComplexity / totalSchemas : 0;
|
|
3632
|
-
let strategy = finalConfig.strategy;
|
|
3633
|
-
let recommendation = "";
|
|
3634
|
-
if (strategy === "auto") {
|
|
3635
|
-
strategy = determineBestStrategy(schemas, graph, schemaGraph, finalConfig);
|
|
3636
|
-
if (schemaCount < finalConfig.threshold) {
|
|
3637
|
-
recommendation = `Schema count (${schemaCount}) below threshold (${finalConfig.threshold}), single file optimal`;
|
|
3638
|
-
} else if (strategy === "dependency") {
|
|
3639
|
-
recommendation = `High dependency density (${avgDeps.toFixed(2)} avg refs/schema), using dependency-based partitioning`;
|
|
3640
|
-
} else if (strategy === "controller") {
|
|
3641
|
-
recommendation = `Found ${controllerGroups} controller groups with low coupling, using controller-based partitioning`;
|
|
3642
|
-
} else {
|
|
3643
|
-
recommendation = `Using size-based partitioning with max ${finalConfig.maxGroupSize} schemas per group`;
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
let groups = [];
|
|
3647
|
-
if (strategy === "none") {
|
|
3648
|
-
groups = [{
|
|
3649
|
-
name: "all",
|
|
3650
|
-
schemas: new Map(schemas),
|
|
3651
|
-
complexity: totalComplexity,
|
|
3652
|
-
dependencies: []
|
|
3653
|
-
}];
|
|
3654
|
-
recommendation = recommendation || "Single file mode (--split not specified)";
|
|
3655
|
-
} else if (strategy === "controller") {
|
|
3656
|
-
groups = partitionByController(schemas, graph, finalConfig);
|
|
3657
|
-
} else if (strategy === "dependency") {
|
|
3658
|
-
groups = partitionByDependency(schemas, schemaGraph, finalConfig);
|
|
3659
|
-
} else {
|
|
3660
|
-
groups = partitionBySize(schemas, finalConfig);
|
|
3661
|
-
}
|
|
3662
|
-
const shouldSplit = strategy !== "none" && schemaCount >= finalConfig.threshold;
|
|
3663
|
-
return {
|
|
3664
|
-
shouldSplit,
|
|
3665
|
-
strategy,
|
|
3666
|
-
groups,
|
|
3667
|
-
recommendation,
|
|
3668
|
-
metrics: {
|
|
3669
|
-
totalSchemas: schemaCount,
|
|
3670
|
-
averageComplexity: avgComplexity,
|
|
3671
|
-
avgDependencyDensity: avgDeps,
|
|
3672
|
-
controllerGroups
|
|
3673
|
-
}
|
|
3674
|
-
};
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
// src/compiler/schema/splitOpenapi.ts
|
|
3678
|
-
var import_node_fs5 = require("fs");
|
|
3679
|
-
var import_node_path5 = require("path");
|
|
3680
|
-
function sanitizeFilename(name) {
|
|
3681
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3682
|
-
}
|
|
3683
|
-
function getSchemaFilename(group) {
|
|
3684
|
-
const name = sanitizeFilename(group.name);
|
|
3685
|
-
return `schemas/${name}.json`;
|
|
3686
|
-
}
|
|
3687
|
-
function collectAllSchemas(groups) {
|
|
3688
|
-
const result = /* @__PURE__ */ new Map();
|
|
3689
|
-
for (const group of groups) {
|
|
3690
|
-
for (const [schemaName, schema] of group.schemas.entries()) {
|
|
3691
|
-
result.set(schemaName, { schema, group: group.name });
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
return result;
|
|
3695
|
-
}
|
|
3696
|
-
function convertToExternalRef(schema, schemaMap) {
|
|
3697
|
-
if (!schema || typeof schema !== "object") return schema;
|
|
3698
|
-
const result = { ...schema };
|
|
3699
|
-
if (schema.$ref && typeof schema.$ref === "string") {
|
|
3700
|
-
const refName = schema.$ref.replace("#/components/schemas/", "");
|
|
3701
|
-
if (refName && schemaMap.has(refName)) {
|
|
3702
|
-
const target = schemaMap.get(refName);
|
|
3703
|
-
const filename = sanitizeFilename(target.group);
|
|
3704
|
-
result.$ref = `schemas/${filename}.json#/components/schemas/${refName}`;
|
|
3705
|
-
}
|
|
3706
|
-
}
|
|
3707
|
-
const nestedProps = ["properties", "items", "additionalProperties"];
|
|
3708
|
-
for (const prop of nestedProps) {
|
|
3709
|
-
if (prop in result) {
|
|
3710
|
-
const value = result[prop];
|
|
3711
|
-
if (Array.isArray(value)) {
|
|
3712
|
-
result[prop] = value.map(
|
|
3713
|
-
(item) => typeof item === "object" ? convertToExternalRef(item, schemaMap) : item
|
|
3714
|
-
);
|
|
3715
|
-
} else if (typeof value === "object" && value !== null) {
|
|
3716
|
-
result[prop] = convertToExternalRef(value, schemaMap);
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
const arrayProps = ["anyOf", "oneOf", "allOf"];
|
|
3721
|
-
for (const prop of arrayProps) {
|
|
3722
|
-
if (prop in result && Array.isArray(result[prop])) {
|
|
3723
|
-
result[prop] = result[prop].map(
|
|
3724
|
-
(item) => typeof item === "object" ? convertToExternalRef(item, schemaMap) : item
|
|
3725
|
-
);
|
|
3726
|
-
}
|
|
3727
|
-
}
|
|
3728
|
-
return result;
|
|
3729
|
-
}
|
|
3730
|
-
function generateSchemaFileContent(group, schemaMap) {
|
|
3731
|
-
const content = {};
|
|
3732
|
-
for (const [schemaName, schema] of group.schemas.entries()) {
|
|
3733
|
-
content[schemaName] = convertToExternalRef(schema, schemaMap);
|
|
3734
|
-
}
|
|
3735
|
-
return content;
|
|
3736
|
-
}
|
|
3737
|
-
function generateSchemaIndex(groups, schemaMap) {
|
|
3738
|
-
const index = {
|
|
3739
|
-
schemas: {}
|
|
3740
|
-
};
|
|
3741
|
-
for (const [schemaName, { group }] of schemaMap.entries()) {
|
|
3742
|
-
const filename = sanitizeFilename(group);
|
|
3743
|
-
index.schemas[schemaName] = {
|
|
3744
|
-
$ref: `schemas/${filename}.json#/components/schemas/${schemaName}`
|
|
3745
|
-
};
|
|
3746
|
-
}
|
|
3747
|
-
return index;
|
|
3748
|
-
}
|
|
3749
|
-
function generateModularOpenAPI(openapi, partitioning, config) {
|
|
3750
|
-
const {
|
|
3751
|
-
outputDir,
|
|
3752
|
-
schemasDir = "schemas",
|
|
3753
|
-
createIndexFile = true,
|
|
3754
|
-
prettyPrint = true,
|
|
3755
|
-
onProgress
|
|
3756
|
-
} = config;
|
|
3757
|
-
const indent = prettyPrint ? 2 : 0;
|
|
3758
|
-
let totalSize = 0;
|
|
3759
|
-
const schemaFiles = [];
|
|
3760
|
-
(0, import_node_fs5.mkdirSync)(outputDir, { recursive: true });
|
|
3761
|
-
if (!partitioning.shouldSplit || partitioning.groups.length === 1) {
|
|
3762
|
-
if (onProgress) {
|
|
3763
|
-
onProgress("Writing single OpenAPI file", 1, 1);
|
|
3764
|
-
}
|
|
3765
|
-
const mainPath2 = (0, import_node_path5.resolve)(outputDir, "openapi.json");
|
|
3766
|
-
(0, import_node_fs5.writeFileSync)(mainPath2, JSON.stringify(openapi, null, indent));
|
|
3767
|
-
totalSize = Buffer.byteLength(JSON.stringify(openapi));
|
|
3768
|
-
return {
|
|
3769
|
-
mainSpec: mainPath2,
|
|
3770
|
-
schemaFiles: [],
|
|
3771
|
-
totalSize,
|
|
3772
|
-
splitEnabled: false
|
|
3773
|
-
};
|
|
3774
|
-
}
|
|
3775
|
-
const schemasPath = (0, import_node_path5.resolve)(outputDir, schemasDir);
|
|
3776
|
-
(0, import_node_fs5.mkdirSync)(schemasPath, { recursive: true });
|
|
3777
|
-
if (onProgress) {
|
|
3778
|
-
onProgress("Creating schemas directory", 0, partitioning.groups.length + 2);
|
|
3779
|
-
}
|
|
3780
|
-
const schemaMap = collectAllSchemas(partitioning.groups);
|
|
3781
|
-
const schemaToFile = /* @__PURE__ */ new Map();
|
|
3782
|
-
for (let i = 0; i < partitioning.groups.length; i++) {
|
|
3783
|
-
const group = partitioning.groups[i];
|
|
3784
|
-
if (onProgress) {
|
|
3785
|
-
onProgress(`Writing schema group ${group.name} (${group.schemas.size} schemas)`, i + 1, partitioning.groups.length + 2);
|
|
3786
|
-
}
|
|
3787
|
-
const filename = getSchemaFilename(group);
|
|
3788
|
-
const filePath = (0, import_node_path5.resolve)(outputDir, filename);
|
|
3789
|
-
const content = generateSchemaFileContent(group, schemaMap);
|
|
3790
|
-
(0, import_node_fs5.writeFileSync)(filePath, JSON.stringify(content, null, indent));
|
|
3791
|
-
for (const schemaName of group.schemas.keys()) {
|
|
3792
|
-
schemaToFile.set(schemaName, filename);
|
|
3793
|
-
}
|
|
3794
|
-
schemaFiles.push(filePath);
|
|
3795
|
-
totalSize += Buffer.byteLength(JSON.stringify(content));
|
|
3796
|
-
}
|
|
3797
|
-
let indexFile;
|
|
3798
|
-
if (createIndexFile) {
|
|
3799
|
-
if (onProgress) {
|
|
3800
|
-
onProgress("Generating index file", partitioning.groups.length + 1, partitioning.groups.length + 2);
|
|
3801
|
-
}
|
|
3802
|
-
const indexPath = (0, import_node_path5.resolve)(outputDir, "schemas/index.json");
|
|
3803
|
-
const indexContent = generateSchemaIndex(partitioning.groups, schemaMap);
|
|
3804
|
-
(0, import_node_fs5.writeFileSync)(indexPath, JSON.stringify(indexContent, null, indent));
|
|
3805
|
-
totalSize += Buffer.byteLength(JSON.stringify(indexContent));
|
|
3806
|
-
indexFile = indexPath;
|
|
3807
|
-
}
|
|
3808
|
-
if (onProgress) {
|
|
3809
|
-
onProgress("Generating main OpenAPI spec", partitioning.groups.length + 2, partitioning.groups.length + 2);
|
|
3810
|
-
}
|
|
3811
|
-
const mainSpec = generateMainSpec(openapi, schemaMap, schemaToFile);
|
|
3812
|
-
const mainPath = (0, import_node_path5.resolve)(outputDir, "openapi.json");
|
|
3813
|
-
(0, import_node_fs5.writeFileSync)(mainPath, JSON.stringify(mainSpec, null, indent));
|
|
3814
|
-
totalSize += Buffer.byteLength(JSON.stringify(mainSpec));
|
|
3815
|
-
return {
|
|
3816
|
-
mainSpec: mainPath,
|
|
3817
|
-
schemaFiles,
|
|
3818
|
-
indexFile,
|
|
3819
|
-
totalSize,
|
|
3820
|
-
splitEnabled: true
|
|
3821
|
-
};
|
|
3822
|
-
}
|
|
3823
|
-
function generateMainSpec(original, schemaMap, schemaToFile) {
|
|
3824
|
-
const schemas = {};
|
|
3825
|
-
for (const [schemaName, { group }] of schemaMap.entries()) {
|
|
3826
|
-
const filename = sanitizeFilename(group);
|
|
3827
|
-
schemas[schemaName] = {
|
|
3828
|
-
$ref: `schemas/${filename}.json#/components/schemas/${schemaName}`
|
|
3829
|
-
};
|
|
3830
|
-
}
|
|
3831
|
-
return {
|
|
3832
|
-
...original,
|
|
3833
|
-
components: {
|
|
3834
|
-
...original.components,
|
|
3835
|
-
schemas
|
|
3836
|
-
},
|
|
3837
|
-
"x-original-schemas": Object.keys(schemaMap).length,
|
|
3838
|
-
"x-split-enabled": true,
|
|
3839
|
-
"x-schema-files": Array.from(new Set(schemaToFile.values()))
|
|
3840
|
-
};
|
|
3841
|
-
}
|
|
3842
|
-
|
|
3843
|
-
// src/compiler/graph/types.ts
|
|
3844
|
-
function createGraph(tsVersion) {
|
|
3845
|
-
return {
|
|
3846
|
-
nodes: /* @__PURE__ */ new Map(),
|
|
3847
|
-
roots: /* @__PURE__ */ new Set(),
|
|
3848
|
-
version: "1.0.0",
|
|
3849
|
-
metadata: {
|
|
3850
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3851
|
-
generatedBy: "adorn-api-gems",
|
|
3852
|
-
tsVersion
|
|
3853
|
-
}
|
|
3854
|
-
};
|
|
3855
|
-
}
|
|
3856
|
-
function addNode(graph, node) {
|
|
3857
|
-
graph.nodes.set(node.id, node);
|
|
3858
|
-
}
|
|
3859
|
-
function addEdge(graph, sourceId, targetId, relation, properties) {
|
|
3860
|
-
const sourceNode = graph.nodes.get(sourceId);
|
|
3861
|
-
if (!sourceNode) {
|
|
3862
|
-
throw new Error(`Source node ${sourceId} not found`);
|
|
3863
|
-
}
|
|
3864
|
-
const targetExists = graph.nodes.has(targetId);
|
|
3865
|
-
if (!targetExists) {
|
|
3866
|
-
throw new Error(`Target node ${targetId} not found`);
|
|
3867
|
-
}
|
|
3868
|
-
sourceNode.edges.push({
|
|
3869
|
-
targetId,
|
|
3870
|
-
relation,
|
|
3871
|
-
properties
|
|
3872
|
-
});
|
|
3873
|
-
}
|
|
3874
|
-
function getEdgesByRelation(graph, relation) {
|
|
3875
|
-
const edges = [];
|
|
3876
|
-
for (const [id, node] of graph.nodes.entries()) {
|
|
3877
|
-
for (const edge of node.edges) {
|
|
3878
|
-
if (edge.relation === relation) {
|
|
3879
|
-
edges.push({ sourceId: id, edge });
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
return edges;
|
|
3884
|
-
}
|
|
3885
|
-
|
|
3886
|
-
// src/compiler/graph/builder.ts
|
|
3887
|
-
var import_typescript15 = __toESM(require("typescript"), 1);
|
|
3888
|
-
|
|
3889
|
-
// src/compiler/graph/schemaGraph.ts
|
|
3890
|
-
var SchemaGraph = class {
|
|
3891
|
-
graph;
|
|
3892
|
-
adjacency = /* @__PURE__ */ new Map();
|
|
3893
|
-
reverseAdjacency = /* @__PURE__ */ new Map();
|
|
3894
|
-
constructor(graph) {
|
|
3895
|
-
this.graph = graph;
|
|
3896
|
-
this.buildAdjacencyLists();
|
|
3897
|
-
}
|
|
3898
|
-
/**
|
|
3899
|
-
* Build adjacency lists for faster traversal
|
|
3900
|
-
*/
|
|
3901
|
-
buildAdjacencyLists() {
|
|
3902
|
-
for (const [id, node] of this.graph.nodes.entries()) {
|
|
3903
|
-
this.adjacency.set(id, /* @__PURE__ */ new Set());
|
|
3904
|
-
this.reverseAdjacency.set(id, /* @__PURE__ */ new Set());
|
|
3905
|
-
}
|
|
3906
|
-
for (const [sourceId, node] of this.graph.nodes.entries()) {
|
|
3907
|
-
for (const edge of node.edges) {
|
|
3908
|
-
this.adjacency.get(sourceId)?.add(edge.targetId);
|
|
3909
|
-
this.reverseAdjacency.get(edge.targetId)?.add(sourceId);
|
|
3910
|
-
}
|
|
3911
|
-
}
|
|
3912
|
-
}
|
|
3913
|
-
/**
|
|
3914
|
-
* Find all nodes that use a given type
|
|
3915
|
-
*/
|
|
3916
|
-
findTypeUsages(typeId) {
|
|
3917
|
-
const usages = [];
|
|
3918
|
-
const usesEdges = getEdgesByRelation(this.graph, "uses");
|
|
3919
|
-
for (const { sourceId, edge } of usesEdges) {
|
|
3920
|
-
if (edge.targetId === typeId) {
|
|
3921
|
-
usages.push(sourceId);
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
return usages;
|
|
3925
|
-
}
|
|
3926
|
-
/**
|
|
3927
|
-
* Detect cycles in the dependency graph
|
|
3928
|
-
*/
|
|
3929
|
-
detectCycles() {
|
|
3930
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3931
|
-
const recursionStack = /* @__PURE__ */ new Set();
|
|
3932
|
-
const cycles = [];
|
|
3933
|
-
for (const nodeId of this.graph.nodes.keys()) {
|
|
3934
|
-
if (!visited.has(nodeId)) {
|
|
3935
|
-
this.detectCyclesDFS(nodeId, visited, recursionStack, [], cycles);
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
3938
|
-
return {
|
|
3939
|
-
hasCycles: cycles.length > 0,
|
|
3940
|
-
cycles,
|
|
3941
|
-
cycleCount: cycles.length
|
|
3942
|
-
};
|
|
3943
|
-
}
|
|
3944
|
-
/**
|
|
3945
|
-
* Depth-first search for cycle detection
|
|
3946
|
-
*/
|
|
3947
|
-
detectCyclesDFS(nodeId, visited, recursionStack, path4, cycles) {
|
|
3948
|
-
visited.add(nodeId);
|
|
3949
|
-
recursionStack.add(nodeId);
|
|
3950
|
-
path4.push(nodeId);
|
|
3951
|
-
const neighbors = this.adjacency.get(nodeId) || /* @__PURE__ */ new Set();
|
|
3952
|
-
for (const neighbor of neighbors) {
|
|
3953
|
-
if (!visited.has(neighbor)) {
|
|
3954
|
-
this.detectCyclesDFS(neighbor, visited, recursionStack, path4, cycles);
|
|
3955
|
-
} else if (recursionStack.has(neighbor)) {
|
|
3956
|
-
const cycleStart = path4.indexOf(neighbor);
|
|
3957
|
-
cycles.push([...path4.slice(cycleStart), neighbor]);
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
recursionStack.delete(nodeId);
|
|
3961
|
-
path4.pop();
|
|
3962
|
-
}
|
|
3963
|
-
/**
|
|
3964
|
-
* Find strongly connected components using Tarjan's algorithm
|
|
3965
|
-
*/
|
|
3966
|
-
findStronglyConnectedComponents() {
|
|
3967
|
-
let index = 0;
|
|
3968
|
-
const stack = [];
|
|
3969
|
-
const indices = /* @__PURE__ */ new Map();
|
|
3970
|
-
const lowlinks = /* @__PURE__ */ new Map();
|
|
3971
|
-
const onStack = /* @__PURE__ */ new Set();
|
|
3972
|
-
const sccs = [];
|
|
3973
|
-
const strongConnect = (v) => {
|
|
3974
|
-
indices.set(v, index);
|
|
3975
|
-
lowlinks.set(v, index);
|
|
3976
|
-
index++;
|
|
3977
|
-
stack.push(v);
|
|
3978
|
-
onStack.add(v);
|
|
3979
|
-
const neighbors = this.adjacency.get(v) || /* @__PURE__ */ new Set();
|
|
3980
|
-
for (const w of neighbors) {
|
|
3981
|
-
if (!indices.has(w)) {
|
|
3982
|
-
strongConnect(w);
|
|
3983
|
-
lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
|
|
3984
|
-
} else if (onStack.has(w)) {
|
|
3985
|
-
lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
|
|
3986
|
-
}
|
|
3987
|
-
}
|
|
3988
|
-
if (lowlinks.get(v) === indices.get(v)) {
|
|
3989
|
-
const scc = [];
|
|
3990
|
-
let w;
|
|
3991
|
-
do {
|
|
3992
|
-
w = stack.pop();
|
|
3993
|
-
onStack.delete(w);
|
|
3994
|
-
scc.push(w);
|
|
3995
|
-
} while (w !== v);
|
|
3996
|
-
sccs.push(scc);
|
|
3997
|
-
}
|
|
3998
|
-
};
|
|
3999
|
-
for (const nodeId of this.graph.nodes.keys()) {
|
|
4000
|
-
if (!indices.has(nodeId)) {
|
|
4001
|
-
strongConnect(nodeId);
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
return sccs;
|
|
4005
|
-
}
|
|
4006
|
-
/**
|
|
4007
|
-
* Topological sort of the graph
|
|
4008
|
-
*/
|
|
4009
|
-
topologicalSort() {
|
|
4010
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
4011
|
-
for (const nodeId of this.graph.nodes.keys()) {
|
|
4012
|
-
inDegree.set(nodeId, 0);
|
|
4013
|
-
}
|
|
4014
|
-
for (const [sourceId, node] of this.graph.nodes.entries()) {
|
|
4015
|
-
for (const edge of node.edges) {
|
|
4016
|
-
inDegree.set(
|
|
4017
|
-
edge.targetId,
|
|
4018
|
-
(inDegree.get(edge.targetId) || 0) + 1
|
|
4019
|
-
);
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
const queue = [];
|
|
4023
|
-
for (const [nodeId, degree] of inDegree.entries()) {
|
|
4024
|
-
if (degree === 0) {
|
|
4025
|
-
queue.push(nodeId);
|
|
4026
|
-
}
|
|
4027
|
-
}
|
|
4028
|
-
const sorted = [];
|
|
4029
|
-
while (queue.length > 0) {
|
|
4030
|
-
const current = queue.shift();
|
|
4031
|
-
sorted.push(current);
|
|
4032
|
-
const neighbors = this.adjacency.get(current) || /* @__PURE__ */ new Set();
|
|
4033
|
-
for (const neighbor of neighbors) {
|
|
4034
|
-
inDegree.set(neighbor, inDegree.get(neighbor) - 1);
|
|
4035
|
-
if (inDegree.get(neighbor) === 0) {
|
|
4036
|
-
queue.push(neighbor);
|
|
4037
|
-
}
|
|
4038
|
-
}
|
|
4039
|
-
}
|
|
4040
|
-
return sorted;
|
|
4041
|
-
}
|
|
4042
|
-
/**
|
|
4043
|
-
* Find nodes reachable from a given start node
|
|
4044
|
-
*/
|
|
4045
|
-
findReachable(startNodeId) {
|
|
4046
|
-
const reachable = /* @__PURE__ */ new Set();
|
|
4047
|
-
const visited = /* @__PURE__ */ new Set();
|
|
4048
|
-
const queue = [startNodeId];
|
|
4049
|
-
while (queue.length > 0) {
|
|
4050
|
-
const current = queue.shift();
|
|
4051
|
-
if (visited.has(current)) continue;
|
|
4052
|
-
visited.add(current);
|
|
4053
|
-
reachable.add(current);
|
|
4054
|
-
const neighbors = this.adjacency.get(current) || /* @__PURE__ */ new Set();
|
|
4055
|
-
for (const neighbor of neighbors) {
|
|
4056
|
-
if (!visited.has(neighbor)) {
|
|
4057
|
-
queue.push(neighbor);
|
|
4058
|
-
}
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
|
-
return reachable;
|
|
4062
|
-
}
|
|
4063
|
-
/**
|
|
4064
|
-
* Find shortest path between two nodes (BFS)
|
|
4065
|
-
*/
|
|
4066
|
-
findShortestPath(fromId, toId) {
|
|
4067
|
-
const visited = /* @__PURE__ */ new Set();
|
|
4068
|
-
const previous = /* @__PURE__ */ new Map();
|
|
4069
|
-
const queue = [fromId];
|
|
4070
|
-
visited.add(fromId);
|
|
4071
|
-
while (queue.length > 0) {
|
|
4072
|
-
const current = queue.shift();
|
|
4073
|
-
if (current === toId) {
|
|
4074
|
-
return this.reconstructPath(previous, toId);
|
|
4075
|
-
}
|
|
4076
|
-
const neighbors = this.adjacency.get(current) || /* @__PURE__ */ new Set();
|
|
4077
|
-
for (const neighbor of neighbors) {
|
|
4078
|
-
if (!visited.has(neighbor)) {
|
|
4079
|
-
visited.add(neighbor);
|
|
4080
|
-
previous.set(neighbor, current);
|
|
4081
|
-
queue.push(neighbor);
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
return null;
|
|
4086
|
-
}
|
|
4087
|
-
/**
|
|
4088
|
-
* Reconstruct path from previous map
|
|
4089
|
-
*/
|
|
4090
|
-
reconstructPath(previous, toId) {
|
|
4091
|
-
const path4 = [toId];
|
|
4092
|
-
let current = toId;
|
|
4093
|
-
while (current !== void 0) {
|
|
4094
|
-
current = previous.get(current);
|
|
4095
|
-
if (current !== void 0) {
|
|
4096
|
-
path4.unshift(current);
|
|
4097
|
-
}
|
|
4098
|
-
}
|
|
4099
|
-
return path4;
|
|
4100
|
-
}
|
|
4101
|
-
/**
|
|
4102
|
-
* Get nodes grouped by their depth from roots
|
|
4103
|
-
*/
|
|
4104
|
-
getDepthGroups() {
|
|
4105
|
-
const depths = /* @__PURE__ */ new Map();
|
|
4106
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4107
|
-
for (const rootId of this.graph.roots) {
|
|
4108
|
-
const queue = [rootId];
|
|
4109
|
-
depths.set(rootId, 0);
|
|
4110
|
-
while (queue.length > 0) {
|
|
4111
|
-
const current = queue.shift();
|
|
4112
|
-
const currentDepth = depths.get(current);
|
|
4113
|
-
for (const neighbor of this.adjacency.get(current) || /* @__PURE__ */ new Set()) {
|
|
4114
|
-
const newDepth = currentDepth + 1;
|
|
4115
|
-
if (!depths.has(neighbor) || depths.get(neighbor) > newDepth) {
|
|
4116
|
-
depths.set(neighbor, newDepth);
|
|
4117
|
-
queue.push(neighbor);
|
|
4118
|
-
}
|
|
4119
|
-
}
|
|
4120
|
-
}
|
|
4121
|
-
}
|
|
4122
|
-
for (const [nodeId, depth] of depths.entries()) {
|
|
4123
|
-
if (!groups.has(depth)) {
|
|
4124
|
-
groups.set(depth, []);
|
|
4125
|
-
}
|
|
4126
|
-
groups.get(depth).push(nodeId);
|
|
4127
|
-
}
|
|
4128
|
-
return groups;
|
|
4129
|
-
}
|
|
4130
|
-
/**
|
|
4131
|
-
* Get the underlying graph
|
|
4132
|
-
*/
|
|
4133
|
-
getGraph() {
|
|
4134
|
-
return this.graph;
|
|
4135
|
-
}
|
|
4136
|
-
};
|
|
4137
|
-
|
|
4138
|
-
// src/cli.ts
|
|
4139
|
-
var import_typescript16 = __toESM(require("typescript"), 1);
|
|
4140
|
-
var import_node_process2 = __toESM(require("process"), 1);
|
|
4141
|
-
var import_meta2 = {};
|
|
4142
|
-
var ADORN_VERSION = (() => {
|
|
4143
|
-
const tryReadPackageJson = (filePath) => {
|
|
4144
|
-
try {
|
|
4145
|
-
const pkg = JSON.parse((0, import_node_fs6.readFileSync)(filePath, "utf-8"));
|
|
4146
|
-
return pkg.version ?? null;
|
|
4147
|
-
} catch {
|
|
4148
|
-
return null;
|
|
4149
|
-
}
|
|
4150
|
-
};
|
|
4151
|
-
const potentialPaths = [];
|
|
4152
|
-
try {
|
|
4153
|
-
const importMetaUrl = import_meta2?.url;
|
|
4154
|
-
if (importMetaUrl && typeof importMetaUrl === "string" && importMetaUrl.length > 0) {
|
|
4155
|
-
const cliDir = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
4156
|
-
potentialPaths.push(
|
|
4157
|
-
(0, import_node_path6.resolve)(cliDir, "..", "package.json"),
|
|
4158
|
-
(0, import_node_path6.resolve)(cliDir, "package.json")
|
|
4159
|
-
);
|
|
4160
|
-
}
|
|
4161
|
-
} catch {
|
|
4162
|
-
}
|
|
4163
|
-
const cwd = import_node_process2.default.cwd();
|
|
4164
|
-
potentialPaths.push(
|
|
4165
|
-
(0, import_node_path6.resolve)(cwd, "package.json"),
|
|
4166
|
-
(0, import_node_path6.resolve)(cwd, "node_modules", "adorn-api", "package.json"),
|
|
4167
|
-
(0, import_node_path6.resolve)(cwd, "..", "package.json"),
|
|
4168
|
-
(0, import_node_path6.resolve)(cwd, "..", "..", "package.json")
|
|
4169
|
-
);
|
|
4170
|
-
for (const pkgPath of potentialPaths) {
|
|
4171
|
-
const version = tryReadPackageJson(pkgPath);
|
|
4172
|
-
if (version) {
|
|
4173
|
-
return version;
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
return "0.0.0";
|
|
4177
|
-
})();
|
|
4178
|
-
function log(msg, options) {
|
|
4179
|
-
if (options?.indent) {
|
|
4180
|
-
import_node_process2.default.stdout.write(" " + msg + "\n");
|
|
4181
|
-
} else {
|
|
4182
|
-
import_node_process2.default.stdout.write(msg + "\n");
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
function formatBytes(bytes) {
|
|
4186
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
4187
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
4188
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4189
|
-
}
|
|
4190
|
-
function getFileSize(path4) {
|
|
4191
|
-
try {
|
|
4192
|
-
return (0, import_node_fs6.statSync)(path4).size;
|
|
4193
|
-
} catch {
|
|
4194
|
-
return void 0;
|
|
4195
|
-
}
|
|
4196
|
-
}
|
|
4197
|
-
function sanitizeForJson(obj) {
|
|
4198
|
-
if (obj === null || obj === void 0) return obj;
|
|
4199
|
-
if (typeof obj !== "object") return obj;
|
|
4200
|
-
if (Array.isArray(obj)) {
|
|
4201
|
-
return obj.map((item) => sanitizeForJson(item));
|
|
4202
|
-
}
|
|
4203
|
-
const result = {};
|
|
4204
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
4205
|
-
if (key.startsWith("__@") || key.startsWith("[")) continue;
|
|
4206
|
-
if (typeof value === "function") continue;
|
|
4207
|
-
if (value !== null && typeof value === "object") {
|
|
4208
|
-
const typeName = value.constructor?.name;
|
|
4209
|
-
if (typeName && !["Object", "Array", "String", "Number", "Boolean", "Date", "RegExp"].includes(typeName)) {
|
|
4210
|
-
continue;
|
|
4211
|
-
}
|
|
4212
|
-
}
|
|
4213
|
-
result[key] = sanitizeForJson(value);
|
|
4214
|
-
}
|
|
4215
|
-
return result;
|
|
4216
|
-
}
|
|
4217
|
-
function buildControllerGraph(controllers) {
|
|
4218
|
-
const graph = createGraph(import_typescript16.default.version);
|
|
4219
|
-
const nodeMap = /* @__PURE__ */ new Map();
|
|
4220
|
-
for (const ctrl of controllers) {
|
|
4221
|
-
const nodeId = `Controller:${ctrl.className}`;
|
|
4222
|
-
const node = {
|
|
4223
|
-
id: nodeId,
|
|
4224
|
-
kind: "Controller",
|
|
4225
|
-
metadata: {
|
|
4226
|
-
name: ctrl.className,
|
|
4227
|
-
sourceLocation: { filePath: "", line: 0, column: 0 },
|
|
4228
|
-
tags: /* @__PURE__ */ new Set(),
|
|
4229
|
-
annotations: /* @__PURE__ */ new Map()
|
|
4230
|
-
},
|
|
4231
|
-
edges: [],
|
|
4232
|
-
controller: {
|
|
4233
|
-
basePath: ctrl.basePath
|
|
4234
|
-
}
|
|
4235
|
-
};
|
|
4236
|
-
addNode(graph, node);
|
|
4237
|
-
nodeMap.set(nodeId, node);
|
|
4238
|
-
}
|
|
4239
|
-
let opIndex = 0;
|
|
4240
|
-
for (const ctrl of controllers) {
|
|
4241
|
-
for (const op of ctrl.operations) {
|
|
4242
|
-
const nodeId = `Operation:${op.operationId}`;
|
|
4243
|
-
const node = {
|
|
4244
|
-
id: nodeId,
|
|
4245
|
-
kind: "Operation",
|
|
4246
|
-
metadata: {
|
|
4247
|
-
name: op.operationId,
|
|
4248
|
-
sourceLocation: { filePath: "", line: 0, column: 0 },
|
|
4249
|
-
tags: /* @__PURE__ */ new Set(),
|
|
4250
|
-
annotations: /* @__PURE__ */ new Map()
|
|
4251
|
-
},
|
|
4252
|
-
edges: [],
|
|
4253
|
-
operation: {
|
|
4254
|
-
httpMethod: op.httpMethod,
|
|
4255
|
-
path: op.path,
|
|
4256
|
-
operationId: op.operationId,
|
|
4257
|
-
returnType: op.returnType || ""
|
|
4258
|
-
}
|
|
4259
|
-
};
|
|
4260
|
-
addNode(graph, node);
|
|
4261
|
-
nodeMap.set(nodeId, node);
|
|
4262
|
-
const ctrlNode = nodeMap.get(`Controller:${ctrl.className}`);
|
|
4263
|
-
if (ctrlNode) {
|
|
4264
|
-
addEdge(graph, ctrlNode.id, node.id, "contains");
|
|
4265
|
-
}
|
|
4266
|
-
opIndex++;
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
for (const ctrl of controllers) {
|
|
4270
|
-
for (const op of ctrl.operations) {
|
|
4271
|
-
if (op.returnType && !nodeMap.has(op.returnType)) {
|
|
4272
|
-
const node = {
|
|
4273
|
-
id: op.returnType,
|
|
4274
|
-
kind: "TypeDefinition",
|
|
4275
|
-
metadata: {
|
|
4276
|
-
name: op.returnType,
|
|
4277
|
-
sourceLocation: { filePath: "", line: 0, column: 0 },
|
|
4278
|
-
tags: /* @__PURE__ */ new Set(),
|
|
4279
|
-
annotations: /* @__PURE__ */ new Map()
|
|
4280
|
-
},
|
|
4281
|
-
edges: [],
|
|
4282
|
-
typeDef: {
|
|
4283
|
-
isGeneric: false,
|
|
4284
|
-
properties: /* @__PURE__ */ new Map()
|
|
4285
|
-
}
|
|
4286
|
-
};
|
|
4287
|
-
addNode(graph, node);
|
|
4288
|
-
nodeMap.set(op.returnType, node);
|
|
4289
|
-
const opNodeId = `Operation:${op.operationId}`;
|
|
4290
|
-
const opNode = nodeMap.get(opNodeId);
|
|
4291
|
-
if (opNode) {
|
|
4292
|
-
addEdge(graph, opNode.id, node.id, "uses");
|
|
4293
|
-
}
|
|
4294
|
-
}
|
|
4295
|
-
}
|
|
4296
|
-
}
|
|
4297
|
-
return graph;
|
|
4298
|
-
}
|
|
4299
|
-
async function buildCommand(args) {
|
|
4300
|
-
const progress = new ProgressTracker({ verbose: args.includes("--verbose"), quiet: args.includes("--quiet") });
|
|
4301
|
-
const projectIndex = args.indexOf("-p");
|
|
4302
|
-
const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
|
|
4303
|
-
const outputDir = args.includes("--output") ? args[args.indexOf("--output") + 1] : ".adorn";
|
|
4304
|
-
const ifStale = args.includes("--if-stale");
|
|
4305
|
-
const validationModeIndex = args.indexOf("--validation-mode");
|
|
4306
|
-
const validationMode = validationModeIndex !== -1 ? args[validationModeIndex + 1] : "ajv-runtime";
|
|
4307
|
-
const verbose = args.includes("--verbose");
|
|
4308
|
-
const quiet = args.includes("--quiet");
|
|
4309
|
-
const split = args.includes("--split");
|
|
4310
|
-
const showQueryBuilder = args.includes("--show-query-builder");
|
|
4311
|
-
const splitStrategyIndex = args.indexOf("--split-strategy");
|
|
4312
|
-
const splitStrategy = splitStrategyIndex !== -1 ? args[splitStrategyIndex + 1] : void 0;
|
|
4313
|
-
const splitThresholdIndex = args.indexOf("--split-threshold");
|
|
4314
|
-
const splitThreshold = splitThresholdIndex !== -1 ? parseInt(args[splitThresholdIndex + 1], 10) : 50;
|
|
4315
|
-
if (validationMode !== "none" && validationMode !== "ajv-runtime" && validationMode !== "precompiled") {
|
|
4316
|
-
console.error(`Invalid validation mode: ${validationMode}. Valid values: none, ajv-runtime, precompiled`);
|
|
4317
|
-
import_node_process2.default.exit(1);
|
|
4318
|
-
}
|
|
4319
|
-
const outputPath = (0, import_node_path6.resolve)(outputDir);
|
|
4320
|
-
if (!quiet) {
|
|
4321
|
-
log(`adorn-api v${ADORN_VERSION} - Building API artifacts`);
|
|
4322
|
-
log("");
|
|
4323
|
-
}
|
|
4324
|
-
if (ifStale) {
|
|
4325
|
-
progress.startPhase("staleness-check", "Checking for stale artifacts");
|
|
4326
|
-
const stale = await isStale({
|
|
4327
|
-
outDir: outputDir,
|
|
4328
|
-
project: projectPath,
|
|
4329
|
-
adornVersion: ADORN_VERSION,
|
|
4330
|
-
typescriptVersion: import_typescript16.default.version
|
|
4331
|
-
});
|
|
4332
|
-
if (!stale.stale) {
|
|
4333
|
-
progress.completePhase("staleness-check");
|
|
4334
|
-
if (!quiet) {
|
|
4335
|
-
log("adorn-api: artifacts up-to-date");
|
|
4336
|
-
}
|
|
4337
|
-
return;
|
|
4338
|
-
}
|
|
4339
|
-
progress.completePhase("staleness-check", `Artifacts stale (${stale.reason})`);
|
|
4340
|
-
if (verbose) {
|
|
4341
|
-
progress.verboseLog(`Stale reason: ${stale.detail || stale.reason}`);
|
|
4342
|
-
}
|
|
4343
|
-
} else {
|
|
4344
|
-
progress.startPhase("configuration", "Initializing build");
|
|
4345
|
-
progress.completePhase("configuration", "Build forced (--if-stale not used)");
|
|
4346
|
-
}
|
|
4347
|
-
progress.startPhase("program", "Loading TypeScript configuration");
|
|
4348
|
-
if (verbose) {
|
|
4349
|
-
progress.verboseLog(`Loading ${projectPath}`);
|
|
4350
|
-
}
|
|
4351
|
-
const { program, checker, sourceFiles } = createProgramFromConfig(projectPath);
|
|
4352
|
-
const projectSourceFiles = sourceFiles.filter((sf) => !sf.fileName.includes("node_modules"));
|
|
4353
|
-
progress.completePhase("program");
|
|
4354
|
-
if (verbose) {
|
|
4355
|
-
progress.verboseLog(`Found ${projectSourceFiles.length} source files`);
|
|
4356
|
-
}
|
|
4357
|
-
progress.startPhase("scan", "Scanning for controllers");
|
|
4358
|
-
const controllers = scanControllers(sourceFiles, checker);
|
|
4359
|
-
if (controllers.length === 0) {
|
|
4360
|
-
console.warn("No controllers found!");
|
|
4361
|
-
import_node_process2.default.exit(1);
|
|
4362
|
-
}
|
|
4363
|
-
const totalOperations = controllers.reduce((sum, ctrl) => sum + ctrl.operations.length, 0);
|
|
4364
|
-
progress.completePhase("scan", `Found ${controllers.length} controller(s) with ${totalOperations} operation(s)`);
|
|
4365
|
-
if (verbose) {
|
|
4366
|
-
for (const ctrl of controllers) {
|
|
4367
|
-
progress.verboseLog(`Controller: ${ctrl.className} (${ctrl.basePath}) - ${ctrl.operations.length} operations`);
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
progress.startPhase("openapi", "Generating OpenAPI schema");
|
|
4371
|
-
const queryBuilderStats = {
|
|
4372
|
-
totalOperations,
|
|
4373
|
-
detected: 0,
|
|
4374
|
-
fallback: 0,
|
|
4375
|
-
operations: []
|
|
4376
|
-
};
|
|
4377
|
-
const openapiSpinner = new Spinner("Processing schemas");
|
|
4378
|
-
if (!quiet) openapiSpinner.start();
|
|
4379
|
-
const openapi = generateOpenAPI(controllers, checker, {
|
|
4380
|
-
title: "API",
|
|
4381
|
-
version: "1.0.0",
|
|
4382
|
-
onProgress: (message, current, total) => {
|
|
4383
|
-
if (!quiet) {
|
|
4384
|
-
openapiSpinner.setStatus(`${message} (${current}/${total})`);
|
|
4385
|
-
}
|
|
4386
|
-
},
|
|
4387
|
-
onQueryBuilderProgress: (info) => {
|
|
4388
|
-
if (info.queryBuilderDetected) {
|
|
4389
|
-
queryBuilderStats.detected++;
|
|
4390
|
-
} else {
|
|
4391
|
-
queryBuilderStats.fallback++;
|
|
4392
|
-
}
|
|
4393
|
-
queryBuilderStats.operations.push({
|
|
4394
|
-
operationId: info.operation,
|
|
4395
|
-
method: info.method,
|
|
4396
|
-
path: info.path,
|
|
4397
|
-
detected: info.queryBuilderDetected,
|
|
4398
|
-
entityName: info.entityName,
|
|
4399
|
-
selectedFields: info.selectedFields,
|
|
4400
|
-
isPaged: info.isPaged
|
|
4401
|
-
});
|
|
4402
|
-
if (showQueryBuilder || verbose) {
|
|
4403
|
-
if (info.queryBuilderDetected) {
|
|
4404
|
-
const fieldsStr = info.selectedFields && info.selectedFields.length > 0 ? ` [select: ${info.selectedFields.join(",")}]` : "";
|
|
4405
|
-
const pagedStr = info.isPaged ? " (paged)" : "";
|
|
4406
|
-
progress.verboseLog(` \u2713 Query builder: ${info.method} ${info.path} \u2192 ${info.entityName}${fieldsStr}${pagedStr}`);
|
|
4407
|
-
} else {
|
|
4408
|
-
progress.verboseLog(` \u25CB No query builder: ${info.method} ${info.path} \u2192 using full entity schema`);
|
|
4409
|
-
}
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
});
|
|
4413
|
-
if (!quiet) {
|
|
4414
|
-
openapiSpinner.setStatus(`Processed ${controllers.length} controllers, ${totalOperations} operations`);
|
|
4415
|
-
}
|
|
4416
|
-
if (!quiet) openapiSpinner.stop();
|
|
4417
|
-
const schemaCount = Object.keys(openapi.components?.schemas || {}).length;
|
|
4418
|
-
let splitEnabled = false;
|
|
4419
|
-
if (split && schemaCount >= splitThreshold) {
|
|
4420
|
-
progress.verboseLog(`Schema count (${schemaCount}) >= threshold (${splitThreshold}), analyzing for auto-split...`);
|
|
4421
|
-
const graph = buildControllerGraph(controllers);
|
|
4422
|
-
const schemaGraph = new SchemaGraph(graph);
|
|
4423
|
-
const schemasMap = new Map(Object.entries(openapi.components?.schemas || {}));
|
|
4424
|
-
const strategy = splitStrategy || "auto";
|
|
4425
|
-
const partitioning = partitionSchemas(schemasMap, graph, schemaGraph, {
|
|
4426
|
-
strategy,
|
|
4427
|
-
threshold: splitThreshold,
|
|
4428
|
-
verbose
|
|
4429
|
-
});
|
|
4430
|
-
splitEnabled = partitioning.shouldSplit;
|
|
4431
|
-
if (splitEnabled) {
|
|
4432
|
-
progress.verboseLog(`Partitioning result: ${partitioning.strategy} strategy`);
|
|
4433
|
-
progress.verboseLog(`Recommendation: ${partitioning.recommendation}`);
|
|
4434
|
-
if (!quiet) {
|
|
4435
|
-
log(` Auto-split enabled: ${partitioning.strategy} strategy`);
|
|
4436
|
-
}
|
|
4437
|
-
const splitSpinner = new Spinner("Writing split schema files");
|
|
4438
|
-
if (!quiet) splitSpinner.start();
|
|
4439
|
-
generateModularOpenAPI(openapi, partitioning, {
|
|
4440
|
-
outputDir: outputPath,
|
|
4441
|
-
schemasDir: "schemas",
|
|
4442
|
-
createIndexFile: true,
|
|
4443
|
-
prettyPrint: true,
|
|
4444
|
-
onProgress: (step, index, total) => {
|
|
4445
|
-
if (!quiet) {
|
|
4446
|
-
progress.logSub(`${step} (${index}/${total})`);
|
|
4447
|
-
}
|
|
4448
|
-
}
|
|
4449
|
-
});
|
|
4450
|
-
if (!quiet) splitSpinner.stop();
|
|
4451
|
-
if (!quiet) {
|
|
4452
|
-
log(` Schema groups: ${partitioning.groups.length}`);
|
|
4453
|
-
}
|
|
4454
|
-
} else {
|
|
4455
|
-
if (!quiet) {
|
|
4456
|
-
log(` Auto-split not needed: ${partitioning.recommendation}`);
|
|
4457
|
-
}
|
|
4458
|
-
}
|
|
4459
|
-
} else if (!split) {
|
|
4460
|
-
if (!quiet) {
|
|
4461
|
-
log(` Splitting disabled (--split not specified)`);
|
|
4462
|
-
}
|
|
4463
|
-
} else {
|
|
4464
|
-
if (!quiet) {
|
|
4465
|
-
log(` Schema count (${schemaCount}) below threshold (${splitThreshold}), single file mode`);
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
progress.completePhase("openapi", `Generated ${schemaCount} schema(s)${splitEnabled ? " (split into groups)" : ""}`);
|
|
4469
|
-
if (showQueryBuilder && totalOperations > 0) {
|
|
4470
|
-
log("");
|
|
4471
|
-
log("Query Builder Analysis:");
|
|
4472
|
-
log(` Operations analyzed: ${totalOperations}`);
|
|
4473
|
-
log(` Patterns detected: ${queryBuilderStats.detected} (${Math.round(queryBuilderStats.detected / totalOperations * 100)}%)`);
|
|
4474
|
-
log(` Full schemas used: ${queryBuilderStats.fallback} (${Math.round(queryBuilderStats.fallback / totalOperations * 100)}%)`);
|
|
4475
|
-
if (queryBuilderStats.detected > 0) {
|
|
4476
|
-
const totalFields = queryBuilderStats.operations.filter((op) => op.detected && op.selectedFields).reduce((sum, op) => sum + (op.selectedFields?.length || 0), 0);
|
|
4477
|
-
log(` Fields selected: ${totalFields} total (avg ${Math.round(totalFields / queryBuilderStats.detected)} per query)`);
|
|
4478
|
-
}
|
|
4479
|
-
}
|
|
4480
|
-
progress.startPhase("manifest", "Generating manifest");
|
|
4481
|
-
const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
|
|
4482
|
-
progress.completePhase("manifest");
|
|
4483
|
-
progress.startPhase("write", "Writing artifacts");
|
|
4484
|
-
(0, import_node_fs6.mkdirSync)(outputPath, { recursive: true });
|
|
4485
|
-
const openapiPath = (0, import_node_path6.resolve)(outputPath, "openapi.json");
|
|
4486
|
-
const manifestPath = (0, import_node_path6.resolve)(outputPath, "manifest.json");
|
|
4487
|
-
if (!splitEnabled) {
|
|
4488
|
-
(0, import_node_fs6.writeFileSync)(openapiPath, JSON.stringify(sanitizeForJson(openapi), null, 2));
|
|
4489
|
-
}
|
|
4490
|
-
(0, import_node_fs6.writeFileSync)(manifestPath, JSON.stringify(manifest, null, 2));
|
|
4491
|
-
const artifacts = [
|
|
4492
|
-
{ name: "openapi.json", size: getFileSize(openapiPath) },
|
|
4493
|
-
{ name: "manifest.json", size: getFileSize(manifestPath) }
|
|
4494
|
-
];
|
|
4495
|
-
if (splitEnabled) {
|
|
4496
|
-
const schemasDir = (0, import_node_path6.resolve)(outputPath, "schemas");
|
|
4497
|
-
if ((0, import_node_fs6.existsSync)(schemasDir)) {
|
|
4498
|
-
const fs4 = await import("fs");
|
|
4499
|
-
const files = fs4.readdirSync(schemasDir);
|
|
4500
|
-
for (const file of files) {
|
|
4501
|
-
const filePath = (0, import_node_path6.resolve)(schemasDir, file);
|
|
4502
|
-
artifacts.push({ name: `schemas/${file}`, size: getFileSize(filePath) });
|
|
4503
|
-
}
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
if (verbose) {
|
|
4507
|
-
for (const artifact of artifacts) {
|
|
4508
|
-
progress.verboseLog(`Written: ${artifact.name} (${formatBytes(artifact.size || 0)})`);
|
|
4509
|
-
}
|
|
4510
|
-
}
|
|
4511
|
-
if (validationMode === "precompiled") {
|
|
4512
|
-
progress.startPhase("validators", "Generating precompiled validators");
|
|
4513
|
-
const manifestObj = JSON.parse((0, import_node_fs6.readFileSync)(manifestPath, "utf-8"));
|
|
4514
|
-
const spinner = new Spinner("Generating validators...");
|
|
4515
|
-
if (!quiet) spinner.start();
|
|
4516
|
-
await emitPrecompiledValidators({
|
|
4517
|
-
outDir: outputPath,
|
|
4518
|
-
openapi,
|
|
4519
|
-
manifest: manifestObj,
|
|
4520
|
-
strict: "off",
|
|
4521
|
-
formatsMode: "full"
|
|
4522
|
-
});
|
|
4523
|
-
if (!quiet) spinner.stop();
|
|
4524
|
-
manifestObj.validation = {
|
|
4525
|
-
mode: "precompiled",
|
|
4526
|
-
precompiledModule: "./validators.mjs"
|
|
4527
|
-
};
|
|
4528
|
-
(0, import_node_fs6.writeFileSync)(manifestPath, JSON.stringify(manifestObj, null, 2));
|
|
4529
|
-
const validatorsCjsPath = (0, import_node_path6.resolve)(outputPath, "validators.cjs");
|
|
4530
|
-
const validatorsMjsPath = (0, import_node_path6.resolve)(outputPath, "validators.mjs");
|
|
4531
|
-
const validatorsMetaPath = (0, import_node_path6.resolve)(outputPath, "validators.meta.json");
|
|
4532
|
-
artifacts.push(
|
|
4533
|
-
{ name: "validators.cjs", size: getFileSize(validatorsCjsPath) },
|
|
4534
|
-
{ name: "validators.mjs", size: getFileSize(validatorsMjsPath) },
|
|
4535
|
-
{ name: "validators.meta.json", size: getFileSize(validatorsMetaPath) }
|
|
4536
|
-
);
|
|
4537
|
-
progress.completePhase("validators");
|
|
4538
|
-
if (verbose) {
|
|
4539
|
-
progress.verboseLog("Precompiled validators generated successfully");
|
|
4540
|
-
}
|
|
4541
|
-
}
|
|
4542
|
-
progress.startPhase("cache", "Writing cache");
|
|
4543
|
-
writeCache({
|
|
4544
|
-
outDir: outputDir,
|
|
4545
|
-
tsconfigAbs: (0, import_node_path6.resolve)(projectPath),
|
|
4546
|
-
program,
|
|
4547
|
-
adornVersion: ADORN_VERSION
|
|
4548
|
-
});
|
|
4549
|
-
const cachePath = (0, import_node_path6.resolve)(outputPath, "cache.json");
|
|
4550
|
-
artifacts.push({ name: "cache.json", size: getFileSize(cachePath) });
|
|
4551
|
-
progress.completePhase("cache");
|
|
4552
|
-
if (verbose) {
|
|
4553
|
-
progress.verboseLog(`Written: cache.json (${formatBytes(getFileSize(cachePath) || 0)})`);
|
|
4554
|
-
}
|
|
4555
|
-
const stats = {
|
|
4556
|
-
controllers: controllers.length,
|
|
4557
|
-
operations: totalOperations,
|
|
4558
|
-
schemas: schemaCount,
|
|
4559
|
-
sourceFiles: projectSourceFiles.length,
|
|
4560
|
-
artifactsWritten: artifacts.map((a) => a.name),
|
|
4561
|
-
splitEnabled,
|
|
4562
|
-
queryBuilder: {
|
|
4563
|
-
detected: queryBuilderStats.detected,
|
|
4564
|
-
fallback: queryBuilderStats.fallback,
|
|
4565
|
-
total: totalOperations
|
|
4566
|
-
}
|
|
4567
|
-
};
|
|
4568
|
-
progress.printSummary(stats);
|
|
4569
|
-
progress.printArtifacts(artifacts);
|
|
4570
|
-
}
|
|
4571
|
-
function cleanCommand(args) {
|
|
4572
|
-
const quiet = args.includes("--quiet");
|
|
4573
|
-
const outputDir = args.includes("--output") ? args[args.indexOf("--output") + 1] : ".adorn";
|
|
4574
|
-
const outputPath = (0, import_node_path6.resolve)(outputDir);
|
|
4575
|
-
if ((0, import_node_fs6.existsSync)(outputPath)) {
|
|
4576
|
-
(0, import_node_fs6.rmSync)(outputPath, { recursive: true, force: true });
|
|
4577
|
-
}
|
|
4578
|
-
if (!quiet) {
|
|
4579
|
-
log(`adorn-api: cleaned ${outputDir}`);
|
|
4580
|
-
}
|
|
4581
|
-
}
|
|
4582
|
-
var command = import_node_process2.default.argv[2];
|
|
4583
|
-
if (command === "build") {
|
|
4584
|
-
buildCommand(import_node_process2.default.argv.slice(3)).catch((err) => {
|
|
4585
|
-
console.error(err);
|
|
4586
|
-
import_node_process2.default.exit(1);
|
|
4587
|
-
});
|
|
4588
|
-
} else if (command === "clean") {
|
|
4589
|
-
cleanCommand(import_node_process2.default.argv.slice(3));
|
|
4590
|
-
} else {
|
|
4591
|
-
console.log(`
|
|
4592
|
-
adorn-api CLI v${ADORN_VERSION}
|
|
4593
|
-
|
|
4594
|
-
Commands:
|
|
4595
|
-
build Generate OpenAPI and manifest from TypeScript source
|
|
4596
|
-
clean Remove generated artifacts
|
|
4597
|
-
|
|
4598
|
-
Options:
|
|
4599
|
-
-p <path> Path to tsconfig.json (default: ./tsconfig.json)
|
|
4600
|
-
--output <dir> Output directory (default: .adorn)
|
|
4601
|
-
--if-stale Only rebuild if artifacts are stale
|
|
4602
|
-
--validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
|
|
4603
|
-
--split Enable automatic schema splitting (default: disabled)
|
|
4604
|
-
--split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
|
|
4605
|
-
--split-threshold <num> Schema count threshold for auto-split (default: 50)
|
|
4606
|
-
--verbose Show detailed progress information
|
|
4607
|
-
--quiet Suppress non-essential output
|
|
4608
|
-
--show-query-builder Show query builder inspection details and statistics
|
|
4609
|
-
|
|
4610
|
-
Examples:
|
|
4611
|
-
adorn-api build -p ./tsconfig.json --output .adorn
|
|
4612
|
-
adorn-api build --if-stale
|
|
4613
|
-
adorn-api build --validation-mode precompiled
|
|
4614
|
-
adorn-api build --verbose
|
|
4615
|
-
adorn-api build --show-query-builder # Show query builder analysis details
|
|
4616
|
-
adorn-api build --split # Enable split mode
|
|
4617
|
-
adorn-api build --split-strategy controller # Force controller-based splitting
|
|
4618
|
-
adorn-api build --split-threshold 100 # Increase threshold to 100
|
|
4619
|
-
adorn-api clean
|
|
4620
|
-
`);
|
|
4621
|
-
}
|
|
4622
|
-
//# sourceMappingURL=cli.cjs.map
|