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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { SchemaNode, SchemaSource } from "./schema";
|
|
2
|
+
import type { Constructor, DtoConstructor, HttpMethod } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface FieldMeta {
|
|
5
|
+
schema: SchemaNode;
|
|
6
|
+
optional?: boolean;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DtoMeta {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
fields: Record<string, FieldMeta>;
|
|
14
|
+
additionalProperties?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface InputMeta {
|
|
18
|
+
schema: SchemaSource;
|
|
19
|
+
description?: string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
contentType?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResponseMeta {
|
|
25
|
+
status: number;
|
|
26
|
+
schema?: SchemaSource;
|
|
27
|
+
description?: string;
|
|
28
|
+
contentType?: string;
|
|
29
|
+
error?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RouteMeta {
|
|
33
|
+
httpMethod: HttpMethod;
|
|
34
|
+
path: string;
|
|
35
|
+
handlerName: string | symbol;
|
|
36
|
+
summary?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
body?: InputMeta;
|
|
40
|
+
query?: InputMeta;
|
|
41
|
+
params?: InputMeta;
|
|
42
|
+
headers?: InputMeta;
|
|
43
|
+
responses: ResponseMeta[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ControllerMeta {
|
|
47
|
+
basePath: string;
|
|
48
|
+
controller: Constructor;
|
|
49
|
+
routes: RouteMeta[];
|
|
50
|
+
tags?: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dtoStore = new Map<DtoConstructor, DtoMeta>();
|
|
54
|
+
const controllerStore = new Map<Constructor, ControllerMeta>();
|
|
55
|
+
|
|
56
|
+
export const META_KEY: unique symbol = Symbol.for("adorn.metadata");
|
|
57
|
+
|
|
58
|
+
export interface DecoratorMetadata {
|
|
59
|
+
[META_KEY]?: AdornMetadata;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface AdornMetadata {
|
|
63
|
+
dtoFields?: Record<string, FieldMeta>;
|
|
64
|
+
dtoOptions?: {
|
|
65
|
+
name?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
additionalProperties?: boolean;
|
|
68
|
+
};
|
|
69
|
+
routes?: RouteMetaInput[];
|
|
70
|
+
controllerOptions?: {
|
|
71
|
+
path?: string;
|
|
72
|
+
tags?: string[];
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RouteMetaInput extends Partial<RouteMeta> {
|
|
77
|
+
handlerName: string | symbol;
|
|
78
|
+
responses: ResponseMeta[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getAdornMetadata(metadata: DecoratorMetadata): AdornMetadata {
|
|
82
|
+
if (!metadata[META_KEY]) {
|
|
83
|
+
metadata[META_KEY] = {};
|
|
84
|
+
}
|
|
85
|
+
return metadata[META_KEY]!;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function registerDto(dto: DtoConstructor, meta: DtoMeta): void {
|
|
89
|
+
dtoStore.set(dto, meta);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getDtoMeta(dto: DtoConstructor): DtoMeta | undefined {
|
|
93
|
+
return dtoStore.get(dto);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getAllDtos(): Array<[DtoConstructor, DtoMeta]> {
|
|
97
|
+
return Array.from(dtoStore.entries());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function registerController(meta: ControllerMeta): void {
|
|
101
|
+
controllerStore.set(meta.controller, meta);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getControllerMeta(controller: Constructor): ControllerMeta | undefined {
|
|
105
|
+
return controllerStore.get(controller);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getAllControllers(): Array<[Constructor, ControllerMeta]> {
|
|
109
|
+
return Array.from(controllerStore.entries());
|
|
110
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type { JsonSchema, SchemaBuildContext } from "./schema-builder";
|
|
2
|
+
import type { ControllerMeta, InputMeta, ResponseMeta } from "./metadata";
|
|
3
|
+
import type { Constructor, DtoConstructor } from "./types";
|
|
4
|
+
import {
|
|
5
|
+
createSchemaContext,
|
|
6
|
+
buildSchemaFromSource
|
|
7
|
+
} from "./schema-builder";
|
|
8
|
+
import { getAllControllers, getDtoMeta } from "./metadata";
|
|
9
|
+
import type { SchemaNode, SchemaSource } from "./schema";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* OpenAPI document information.
|
|
13
|
+
*/
|
|
14
|
+
export interface OpenApiInfo {
|
|
15
|
+
/** API title */
|
|
16
|
+
title: string;
|
|
17
|
+
/** API version */
|
|
18
|
+
version: string;
|
|
19
|
+
/** API description */
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* OpenAPI server information.
|
|
25
|
+
*/
|
|
26
|
+
export interface OpenApiServer {
|
|
27
|
+
/** Server URL */
|
|
28
|
+
url: string;
|
|
29
|
+
/** Server description */
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for building OpenAPI documents.
|
|
35
|
+
*/
|
|
36
|
+
export interface OpenApiOptions {
|
|
37
|
+
/** OpenAPI document information */
|
|
38
|
+
info: OpenApiInfo;
|
|
39
|
+
/** Array of servers */
|
|
40
|
+
servers?: OpenApiServer[];
|
|
41
|
+
/** Array of controllers to include */
|
|
42
|
+
controllers?: Constructor[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* OpenAPI document structure.
|
|
47
|
+
*/
|
|
48
|
+
export interface OpenApiDocument {
|
|
49
|
+
/** OpenAPI specification version */
|
|
50
|
+
openapi: "3.1.0";
|
|
51
|
+
/** JSON Schema dialect */
|
|
52
|
+
jsonSchemaDialect: string;
|
|
53
|
+
/** API information */
|
|
54
|
+
info: OpenApiInfo;
|
|
55
|
+
/** API servers */
|
|
56
|
+
servers?: OpenApiServer[];
|
|
57
|
+
/** API paths */
|
|
58
|
+
paths: Record<string, Record<string, unknown>>;
|
|
59
|
+
/** Reusable components */
|
|
60
|
+
components: {
|
|
61
|
+
/** Schema definitions */
|
|
62
|
+
schemas: Record<string, JsonSchema>;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builds an OpenAPI document from controllers.
|
|
68
|
+
* @param options - OpenAPI build options
|
|
69
|
+
* @returns OpenAPI document
|
|
70
|
+
*/
|
|
71
|
+
export function buildOpenApi(options: OpenApiOptions): OpenApiDocument {
|
|
72
|
+
const context = createSchemaContext();
|
|
73
|
+
|
|
74
|
+
const controllers = filterControllers(options.controllers);
|
|
75
|
+
const paths: Record<string, Record<string, unknown>> = {};
|
|
76
|
+
|
|
77
|
+
for (const controller of controllers) {
|
|
78
|
+
const tagFallback = controller.meta.tags ?? [controller.meta.controller.name];
|
|
79
|
+
for (const route of controller.meta.routes) {
|
|
80
|
+
const fullPath = joinPaths(controller.meta.basePath, route.path);
|
|
81
|
+
const openApiPath = expressPathToOpenApi(fullPath);
|
|
82
|
+
const pathItem = (paths[openApiPath] ??= {});
|
|
83
|
+
|
|
84
|
+
const parameters = [
|
|
85
|
+
...buildParameters("path", route.params, context),
|
|
86
|
+
...buildParameters("query", route.query, context),
|
|
87
|
+
...buildParameters("header", route.headers, context)
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const responses = buildResponses(route.responses, context);
|
|
91
|
+
|
|
92
|
+
pathItem[route.httpMethod] = {
|
|
93
|
+
operationId: `${controller.meta.controller.name}.${String(route.handlerName)}`,
|
|
94
|
+
summary: route.summary,
|
|
95
|
+
description: route.description,
|
|
96
|
+
tags: route.tags ?? tagFallback,
|
|
97
|
+
parameters: parameters.length ? parameters : undefined,
|
|
98
|
+
requestBody: buildRequestBody(route.body, context),
|
|
99
|
+
responses
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
openapi: "3.1.0",
|
|
106
|
+
jsonSchemaDialect: "https://spec.openapis.org/oas/3.1/dialect/base",
|
|
107
|
+
info: options.info,
|
|
108
|
+
servers: options.servers,
|
|
109
|
+
paths,
|
|
110
|
+
components: {
|
|
111
|
+
schemas: context.components
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildRequestBody(
|
|
117
|
+
body: InputMeta | undefined,
|
|
118
|
+
context: SchemaBuildContext
|
|
119
|
+
): Record<string, unknown> | undefined {
|
|
120
|
+
if (!body) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const contentType = body.contentType ?? "application/json";
|
|
124
|
+
const schema = buildSchemaFromSource(body.schema, context);
|
|
125
|
+
return {
|
|
126
|
+
required: body.required ?? true,
|
|
127
|
+
content: {
|
|
128
|
+
[contentType]: { schema }
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildResponses(
|
|
134
|
+
responses: ResponseMeta[],
|
|
135
|
+
context: SchemaBuildContext
|
|
136
|
+
): Record<string, unknown> {
|
|
137
|
+
const output: Record<string, unknown> = {};
|
|
138
|
+
for (const response of responses) {
|
|
139
|
+
const contentType = response.contentType ?? "application/json";
|
|
140
|
+
output[String(response.status)] = {
|
|
141
|
+
description: response.description ?? getDefaultStatusDescription(response.status),
|
|
142
|
+
content: response.schema
|
|
143
|
+
? {
|
|
144
|
+
[contentType]: {
|
|
145
|
+
schema: buildSchemaFromSource(response.schema, context)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
: undefined
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return output;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getDefaultStatusDescription(status: number): string {
|
|
155
|
+
switch (status) {
|
|
156
|
+
case 200:
|
|
157
|
+
return "OK";
|
|
158
|
+
case 201:
|
|
159
|
+
return "Created";
|
|
160
|
+
case 202:
|
|
161
|
+
return "Accepted";
|
|
162
|
+
case 204:
|
|
163
|
+
return "No Content";
|
|
164
|
+
case 400:
|
|
165
|
+
return "Bad Request";
|
|
166
|
+
case 401:
|
|
167
|
+
return "Unauthorized";
|
|
168
|
+
case 403:
|
|
169
|
+
return "Forbidden";
|
|
170
|
+
case 404:
|
|
171
|
+
return "Not Found";
|
|
172
|
+
case 409:
|
|
173
|
+
return "Conflict";
|
|
174
|
+
case 422:
|
|
175
|
+
return "Unprocessable Entity";
|
|
176
|
+
case 500:
|
|
177
|
+
return "Internal Server Error";
|
|
178
|
+
case 503:
|
|
179
|
+
return "Service Unavailable";
|
|
180
|
+
default:
|
|
181
|
+
return "OK";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildParameters(
|
|
186
|
+
location: "path" | "query" | "header",
|
|
187
|
+
input: InputMeta | undefined,
|
|
188
|
+
context: SchemaBuildContext
|
|
189
|
+
): Array<Record<string, unknown>> {
|
|
190
|
+
if (!input) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const fieldEntries = extractFields(input.schema);
|
|
194
|
+
if (!fieldEntries.length) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return fieldEntries.map((entry) => ({
|
|
198
|
+
name: entry.name,
|
|
199
|
+
in: location,
|
|
200
|
+
required: location === "path" ? true : entry.required,
|
|
201
|
+
description: entry.description,
|
|
202
|
+
schema: buildSchemaFromSource(entry.schema, context)
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function extractFields(
|
|
207
|
+
schema: SchemaSource
|
|
208
|
+
): Array<{ name: string; schema: SchemaSource; required: boolean; description?: string }> {
|
|
209
|
+
if (isSchemaNode(schema)) {
|
|
210
|
+
if (schema.kind === "object" && schema.properties) {
|
|
211
|
+
const required = new Set(schema.required ?? []);
|
|
212
|
+
return Object.entries(schema.properties).map(([name, value]) => ({
|
|
213
|
+
name,
|
|
214
|
+
schema: value,
|
|
215
|
+
required: required.has(name),
|
|
216
|
+
description: value.description
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
return [
|
|
220
|
+
{
|
|
221
|
+
name: "value",
|
|
222
|
+
schema,
|
|
223
|
+
required: !schema.optional,
|
|
224
|
+
description: schema.description
|
|
225
|
+
}
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const dtoMeta = getDtoMetaSafe(schema);
|
|
230
|
+
return Object.entries(dtoMeta.fields).map(([name, field]) => ({
|
|
231
|
+
name,
|
|
232
|
+
schema: field.schema,
|
|
233
|
+
required: !(field.optional ?? field.schema.optional ?? false),
|
|
234
|
+
description: field.description ?? field.schema.description
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function getDtoMetaSafe(dto: DtoConstructor): {
|
|
239
|
+
fields: Record<string, { schema: SchemaNode; optional?: boolean; description?: string }>;
|
|
240
|
+
} {
|
|
241
|
+
const meta = getDtoMeta(dto);
|
|
242
|
+
if (!meta) {
|
|
243
|
+
throw new Error(`DTO "${dto.name}" is missing @Dto decorator.`);
|
|
244
|
+
}
|
|
245
|
+
return meta;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function filterControllers(
|
|
249
|
+
controllers: Constructor[] | undefined
|
|
250
|
+
): Array<{ meta: ControllerMeta }> {
|
|
251
|
+
const allControllers = getAllControllers();
|
|
252
|
+
if (!controllers?.length) {
|
|
253
|
+
return allControllers.map(([, meta]) => ({ meta }));
|
|
254
|
+
}
|
|
255
|
+
const set = new Set(controllers);
|
|
256
|
+
return allControllers
|
|
257
|
+
.filter(([controller]) => set.has(controller))
|
|
258
|
+
.map(([, meta]) => ({ meta }));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function joinPaths(basePath: string, routePath: string): string {
|
|
262
|
+
const base = basePath ? basePath.replace(/\/+$/, "") : "";
|
|
263
|
+
const route = routePath ? routePath.replace(/^\/+/, "") : "";
|
|
264
|
+
if (!base && !route) {
|
|
265
|
+
return "/";
|
|
266
|
+
}
|
|
267
|
+
if (!base) {
|
|
268
|
+
return `/${route}`;
|
|
269
|
+
}
|
|
270
|
+
if (!route) {
|
|
271
|
+
return base.startsWith("/") ? base : `/${base}`;
|
|
272
|
+
}
|
|
273
|
+
return `${base.startsWith("/") ? base : `/${base}`}/${route}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function expressPathToOpenApi(path: string): string {
|
|
277
|
+
return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isSchemaNode(value: unknown): value is SchemaNode {
|
|
281
|
+
return !!value && typeof value === "object" && "kind" in (value as SchemaNode);
|
|
282
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { JsonPrimitive, SchemaNode, SchemaSource } from "./schema";
|
|
2
|
+
import type { DtoConstructor } from "./types";
|
|
3
|
+
import { getDtoMeta } from "./metadata";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JSON Schema type.
|
|
7
|
+
*/
|
|
8
|
+
export type JsonSchema = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Context for building schemas.
|
|
12
|
+
*/
|
|
13
|
+
export interface SchemaBuildContext {
|
|
14
|
+
/** Schema components */
|
|
15
|
+
components: Record<string, JsonSchema>;
|
|
16
|
+
/** Set of seen DTOs to avoid circular references */
|
|
17
|
+
seen: Set<DtoConstructor>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new schema building context.
|
|
22
|
+
* @returns Schema build context
|
|
23
|
+
*/
|
|
24
|
+
export function createSchemaContext(): SchemaBuildContext {
|
|
25
|
+
return {
|
|
26
|
+
components: {},
|
|
27
|
+
seen: new Set()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Builds a JSON schema from a schema source.
|
|
33
|
+
* @param source - Schema source
|
|
34
|
+
* @param context - Schema build context
|
|
35
|
+
* @returns JSON schema
|
|
36
|
+
*/
|
|
37
|
+
export function buildSchemaFromSource(source: SchemaSource, context: SchemaBuildContext): JsonSchema {
|
|
38
|
+
if (isSchemaNode(source)) {
|
|
39
|
+
return buildSchemaFromNode(source, context);
|
|
40
|
+
}
|
|
41
|
+
return buildSchemaFromDto(source, context);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Builds a JSON schema from a DTO constructor.
|
|
46
|
+
* @param dto - DTO constructor
|
|
47
|
+
* @param context - Schema build context
|
|
48
|
+
* @returns JSON schema reference
|
|
49
|
+
*/
|
|
50
|
+
export function buildSchemaFromDto(dto: DtoConstructor, context: SchemaBuildContext): JsonSchema {
|
|
51
|
+
const name = ensureDtoComponent(dto, context);
|
|
52
|
+
return { $ref: `#/components/schemas/${name}` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Ensures a DTO component is registered in the schema context.
|
|
57
|
+
* @param dto - DTO constructor
|
|
58
|
+
* @param context - Schema build context
|
|
59
|
+
* @returns Component name
|
|
60
|
+
*/
|
|
61
|
+
export function ensureDtoComponent(dto: DtoConstructor, context: SchemaBuildContext): string {
|
|
62
|
+
const dtoMeta = getDtoMeta(dto);
|
|
63
|
+
if (!dtoMeta) {
|
|
64
|
+
throw new Error(`DTO "${dto.name}" is missing @Dto decorator.`);
|
|
65
|
+
}
|
|
66
|
+
if (context.seen.has(dto)) {
|
|
67
|
+
return dtoMeta.name;
|
|
68
|
+
}
|
|
69
|
+
context.seen.add(dto);
|
|
70
|
+
context.components[dtoMeta.name] = buildDtoSchema(dtoMeta, context);
|
|
71
|
+
return dtoMeta.name;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Builds a JSON schema from DTO metadata.
|
|
76
|
+
* @param meta - DTO metadata
|
|
77
|
+
* @param context - Schema build context
|
|
78
|
+
* @returns JSON schema
|
|
79
|
+
*/
|
|
80
|
+
export function buildDtoSchema(
|
|
81
|
+
meta: { name: string; description?: string; fields: Record<string, { schema: SchemaNode; optional?: boolean; description?: string }>; additionalProperties?: boolean },
|
|
82
|
+
context: SchemaBuildContext
|
|
83
|
+
): JsonSchema {
|
|
84
|
+
const properties: Record<string, JsonSchema> = {};
|
|
85
|
+
const required: string[] = [];
|
|
86
|
+
|
|
87
|
+
for (const [name, field] of Object.entries(meta.fields)) {
|
|
88
|
+
const schema = buildSchemaFromNode(field.schema, context);
|
|
89
|
+
if (field.description && !schema.description) {
|
|
90
|
+
schema.description = field.description;
|
|
91
|
+
}
|
|
92
|
+
properties[name] = schema;
|
|
93
|
+
const isOptional = field.optional ?? field.schema.optional ?? false;
|
|
94
|
+
if (!isOptional) {
|
|
95
|
+
required.push(name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const dtoSchema: JsonSchema = {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties,
|
|
102
|
+
additionalProperties: meta.additionalProperties ?? false
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (required.length) {
|
|
106
|
+
dtoSchema.required = required;
|
|
107
|
+
}
|
|
108
|
+
if (meta.description) {
|
|
109
|
+
dtoSchema.description = meta.description;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return dtoSchema;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Builds a JSON schema from a schema node.
|
|
117
|
+
* @param node - Schema node
|
|
118
|
+
* @param context - Schema build context
|
|
119
|
+
* @returns JSON schema
|
|
120
|
+
*/
|
|
121
|
+
export function buildSchemaFromNode(node: SchemaNode, context: SchemaBuildContext): JsonSchema {
|
|
122
|
+
let schema: JsonSchema;
|
|
123
|
+
switch (node.kind) {
|
|
124
|
+
case "string":
|
|
125
|
+
schema = {
|
|
126
|
+
type: "string",
|
|
127
|
+
format: node.format,
|
|
128
|
+
minLength: node.minLength,
|
|
129
|
+
maxLength: node.maxLength,
|
|
130
|
+
pattern: node.pattern
|
|
131
|
+
};
|
|
132
|
+
break;
|
|
133
|
+
case "number":
|
|
134
|
+
case "integer":
|
|
135
|
+
schema = {
|
|
136
|
+
type: node.kind,
|
|
137
|
+
minimum: node.minimum,
|
|
138
|
+
maximum: node.maximum,
|
|
139
|
+
exclusiveMinimum: node.exclusiveMinimum,
|
|
140
|
+
exclusiveMaximum: node.exclusiveMaximum,
|
|
141
|
+
multipleOf: node.multipleOf
|
|
142
|
+
};
|
|
143
|
+
break;
|
|
144
|
+
case "boolean":
|
|
145
|
+
schema = { type: "boolean" };
|
|
146
|
+
break;
|
|
147
|
+
case "array":
|
|
148
|
+
schema = {
|
|
149
|
+
type: "array",
|
|
150
|
+
items: buildSchemaFromNode(node.items, context),
|
|
151
|
+
minItems: node.minItems,
|
|
152
|
+
maxItems: node.maxItems,
|
|
153
|
+
uniqueItems: node.uniqueItems
|
|
154
|
+
};
|
|
155
|
+
break;
|
|
156
|
+
case "object":
|
|
157
|
+
schema = {
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: node.properties
|
|
160
|
+
? mapObjectSchemas(node.properties, context)
|
|
161
|
+
: undefined,
|
|
162
|
+
required: node.required,
|
|
163
|
+
additionalProperties:
|
|
164
|
+
typeof node.additionalProperties === "object"
|
|
165
|
+
? buildSchemaFromNode(node.additionalProperties, context)
|
|
166
|
+
: node.additionalProperties,
|
|
167
|
+
minProperties: node.minProperties,
|
|
168
|
+
maxProperties: node.maxProperties
|
|
169
|
+
};
|
|
170
|
+
break;
|
|
171
|
+
case "enum":
|
|
172
|
+
schema = {
|
|
173
|
+
enum: node.values,
|
|
174
|
+
...inferEnumType(node.values)
|
|
175
|
+
};
|
|
176
|
+
break;
|
|
177
|
+
case "literal":
|
|
178
|
+
schema = {
|
|
179
|
+
const: node.value,
|
|
180
|
+
...inferLiteralType(node.value)
|
|
181
|
+
};
|
|
182
|
+
break;
|
|
183
|
+
case "union":
|
|
184
|
+
schema = {
|
|
185
|
+
anyOf: node.anyOf.map((entry) => buildSchemaFromNode(entry, context))
|
|
186
|
+
};
|
|
187
|
+
break;
|
|
188
|
+
case "record":
|
|
189
|
+
schema = {
|
|
190
|
+
type: "object",
|
|
191
|
+
additionalProperties: buildSchemaFromNode(node.values, context)
|
|
192
|
+
};
|
|
193
|
+
break;
|
|
194
|
+
case "ref":
|
|
195
|
+
schema = buildSchemaFromDto(node.dto, context);
|
|
196
|
+
break;
|
|
197
|
+
case "any":
|
|
198
|
+
schema = {};
|
|
199
|
+
break;
|
|
200
|
+
case "null":
|
|
201
|
+
schema = { type: "null" };
|
|
202
|
+
break;
|
|
203
|
+
default:
|
|
204
|
+
schema = {};
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
schema = applyBaseOptions(schema, node);
|
|
209
|
+
schema = applyNullable(schema, node);
|
|
210
|
+
return stripUndefined(schema);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function mapObjectSchemas(
|
|
214
|
+
properties: Record<string, SchemaNode>,
|
|
215
|
+
context: SchemaBuildContext
|
|
216
|
+
): Record<string, JsonSchema> {
|
|
217
|
+
const mapped: Record<string, JsonSchema> = {};
|
|
218
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
219
|
+
mapped[key] = buildSchemaFromNode(value, context);
|
|
220
|
+
}
|
|
221
|
+
return mapped;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function applyBaseOptions(schema: JsonSchema, node: SchemaNode): JsonSchema {
|
|
225
|
+
if (node.description) schema.description = node.description;
|
|
226
|
+
if (node.title) schema.title = node.title;
|
|
227
|
+
if (node.default !== undefined) schema.default = node.default;
|
|
228
|
+
if (node.examples) schema.examples = node.examples;
|
|
229
|
+
if (node.deprecated !== undefined) schema.deprecated = node.deprecated;
|
|
230
|
+
if (node.readOnly !== undefined) schema.readOnly = node.readOnly;
|
|
231
|
+
if (node.writeOnly !== undefined) schema.writeOnly = node.writeOnly;
|
|
232
|
+
return schema;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function applyNullable(schema: JsonSchema, node: SchemaNode): JsonSchema {
|
|
236
|
+
if (!node.nullable) {
|
|
237
|
+
return schema;
|
|
238
|
+
}
|
|
239
|
+
if ("$ref" in schema) {
|
|
240
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
241
|
+
}
|
|
242
|
+
if (schema.type && typeof schema.type === "string") {
|
|
243
|
+
return { ...schema, type: [schema.type, "null"] };
|
|
244
|
+
}
|
|
245
|
+
if (Array.isArray(schema.type)) {
|
|
246
|
+
const types = new Set(schema.type);
|
|
247
|
+
types.add("null");
|
|
248
|
+
return { ...schema, type: Array.from(types) };
|
|
249
|
+
}
|
|
250
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function inferEnumType(values: JsonPrimitive[]): JsonSchema {
|
|
254
|
+
const types = new Set<string>();
|
|
255
|
+
for (const value of values) {
|
|
256
|
+
if (value === null) {
|
|
257
|
+
types.add("null");
|
|
258
|
+
} else {
|
|
259
|
+
types.add(typeof value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!types.size) {
|
|
263
|
+
return {};
|
|
264
|
+
}
|
|
265
|
+
const typeArray = Array.from(types);
|
|
266
|
+
return typeArray.length === 1 ? { type: typeArray[0] } : { type: typeArray };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function inferLiteralType(value: JsonPrimitive): JsonSchema {
|
|
270
|
+
if (value === null) {
|
|
271
|
+
return { type: "null" };
|
|
272
|
+
}
|
|
273
|
+
return { type: typeof value };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function stripUndefined(schema: JsonSchema): JsonSchema {
|
|
277
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
278
|
+
if (value === undefined) {
|
|
279
|
+
delete schema[key];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return schema;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isSchemaNode(value: unknown): value is SchemaNode {
|
|
286
|
+
return !!value && typeof value === "object" && "kind" in (value as SchemaNode);
|
|
287
|
+
}
|