@zudojs/openapi 0.0.1 → 1.1.0

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.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -21
  3. package/dist/index.d.ts +26 -21
  4. package/dist/index.js +31 -13
  5. package/dist/openApiComponents/index.d.ts +2 -2
  6. package/dist/openApiComponents/index.js +1 -1
  7. package/dist/openApiConstants/index.d.ts +1 -1
  8. package/dist/openApiConstants/index.js +1 -1
  9. package/dist/openApiConstants/openApiConstants.core.d.ts +15 -3
  10. package/dist/openApiConstants/openApiConstants.core.js +22 -3
  11. package/dist/openApiDocument/index.d.ts +1 -1
  12. package/dist/openApiDocument/index.js +1 -1
  13. package/dist/openApiDocument/openApiDocument.builder.d.ts +39 -52
  14. package/dist/openApiDocument/openApiDocument.builder.js +73 -72
  15. package/dist/openApiErrors/index.d.ts +2 -2
  16. package/dist/openApiErrors/index.js +2 -1
  17. package/dist/openApiErrors/openApiError.base.d.ts +9 -0
  18. package/dist/openApiErrors/openApiError.base.js +7 -0
  19. package/dist/openApiErrors/openApiError.types.d.ts +34 -14
  20. package/dist/openApiErrors/openApiError.types.js +83 -35
  21. package/dist/openApiHttp/index.d.ts +2 -2
  22. package/dist/openApiHttp/index.js +2 -2
  23. package/dist/openApiHttp/openApiHttpAdapter.core.d.ts +127 -25
  24. package/dist/openApiHttp/openApiHttpAdapter.core.js +212 -16
  25. package/dist/openApiRegistry/openApiRegistry.core.d.ts +38 -5
  26. package/dist/openApiRegistry/openApiRegistry.core.js +167 -66
  27. package/dist/openApiRegistry/openApiRegistry.type.d.ts +18 -13
  28. package/dist/openApiRegistry/openApiRegistry.type.js +1 -2
  29. package/dist/openApiRouting/index.d.ts +2 -2
  30. package/dist/openApiRouting/index.js +1 -1
  31. package/dist/openApiRouting/routeConverter.core.d.ts +25 -7
  32. package/dist/openApiRouting/routeConverter.core.js +104 -58
  33. package/dist/openApiRouting/routeMetadata.type.d.ts +40 -17
  34. package/dist/openApiRouting/routeMetadata.type.js +9 -0
  35. package/dist/openApiRouting/routeScanner.core.d.ts +17 -47
  36. package/dist/openApiRouting/routeScanner.core.js +40 -55
  37. package/dist/openApiSchema/index.d.ts +4 -4
  38. package/dist/openApiSchema/index.js +2 -2
  39. package/dist/openApiSchema/references.core.d.ts +18 -1
  40. package/dist/openApiSchema/references.core.js +21 -1
  41. package/dist/openApiSchema/schemaConverter.core.d.ts +51 -3
  42. package/dist/openApiSchema/schemaConverter.core.js +475 -118
  43. package/dist/openApiSchema/schemaRegistry.core.d.ts +27 -10
  44. package/dist/openApiSchema/schemaRegistry.core.js +33 -14
  45. package/dist/openApiSerialization/openApiSerializer.core.d.ts +10 -4
  46. package/dist/openApiSerialization/openApiSerializer.core.js +135 -9
  47. package/dist/openApiTypes/index.d.ts +1 -1
  48. package/dist/openApiTypes/openApiDocument.type.d.ts +16 -0
  49. package/dist/openApiTypes/openApiSchema.type.d.ts +24 -1
  50. package/dist/openApiTypes/openApiSchema.type.js +3 -0
  51. package/dist/openApiUi/index.d.ts +8 -0
  52. package/dist/openApiUi/index.js +8 -0
  53. package/dist/openApiUi/openApiUi.brand.d.ts +26 -0
  54. package/dist/openApiUi/openApiUi.brand.js +44 -0
  55. package/dist/openApiUi/openApiUi.core.d.ts +47 -0
  56. package/dist/openApiUi/openApiUi.core.js +143 -0
  57. package/dist/openApiValidation/index.d.ts +2 -2
  58. package/dist/openApiValidation/index.js +1 -1
  59. package/dist/openApiValidation/openApiValidator.core.d.ts +33 -11
  60. package/dist/openApiValidation/openApiValidator.core.js +274 -66
  61. package/package.json +25 -18
  62. package/dist/.tsbuildinfo +0 -1
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/index.js.map +0 -1
  65. package/dist/openApiComponents/index.d.ts.map +0 -1
  66. package/dist/openApiComponents/index.js.map +0 -1
  67. package/dist/openApiConstants/index.d.ts.map +0 -1
  68. package/dist/openApiConstants/index.js.map +0 -1
  69. package/dist/openApiConstants/openApiConstants.core.d.ts.map +0 -1
  70. package/dist/openApiConstants/openApiConstants.core.js.map +0 -1
  71. package/dist/openApiDocument/index.d.ts.map +0 -1
  72. package/dist/openApiDocument/index.js.map +0 -1
  73. package/dist/openApiDocument/openApiDocument.builder.d.ts.map +0 -1
  74. package/dist/openApiDocument/openApiDocument.builder.js.map +0 -1
  75. package/dist/openApiErrors/index.d.ts.map +0 -1
  76. package/dist/openApiErrors/index.js.map +0 -1
  77. package/dist/openApiErrors/openApiError.base.d.ts.map +0 -1
  78. package/dist/openApiErrors/openApiError.base.js.map +0 -1
  79. package/dist/openApiErrors/openApiError.core.d.ts.map +0 -1
  80. package/dist/openApiErrors/openApiError.core.js.map +0 -1
  81. package/dist/openApiErrors/openApiError.types.d.ts.map +0 -1
  82. package/dist/openApiErrors/openApiError.types.js.map +0 -1
  83. package/dist/openApiHttp/index.d.ts.map +0 -1
  84. package/dist/openApiHttp/index.js.map +0 -1
  85. package/dist/openApiHttp/openApiHttpAdapter.core.d.ts.map +0 -1
  86. package/dist/openApiHttp/openApiHttpAdapter.core.js.map +0 -1
  87. package/dist/openApiRegistry/index.d.ts.map +0 -1
  88. package/dist/openApiRegistry/index.js.map +0 -1
  89. package/dist/openApiRegistry/openApiRegistry.core.d.ts.map +0 -1
  90. package/dist/openApiRegistry/openApiRegistry.core.js.map +0 -1
  91. package/dist/openApiRegistry/openApiRegistry.type.d.ts.map +0 -1
  92. package/dist/openApiRegistry/openApiRegistry.type.js.map +0 -1
  93. package/dist/openApiRouting/index.d.ts.map +0 -1
  94. package/dist/openApiRouting/index.js.map +0 -1
  95. package/dist/openApiRouting/routeConverter.core.d.ts.map +0 -1
  96. package/dist/openApiRouting/routeConverter.core.js.map +0 -1
  97. package/dist/openApiRouting/routeMetadata.type.d.ts.map +0 -1
  98. package/dist/openApiRouting/routeMetadata.type.js.map +0 -1
  99. package/dist/openApiRouting/routeScanner.core.d.ts.map +0 -1
  100. package/dist/openApiRouting/routeScanner.core.js.map +0 -1
  101. package/dist/openApiSchema/index.d.ts.map +0 -1
  102. package/dist/openApiSchema/index.js.map +0 -1
  103. package/dist/openApiSchema/references.core.d.ts.map +0 -1
  104. package/dist/openApiSchema/references.core.js.map +0 -1
  105. package/dist/openApiSchema/schemaConverter.core.d.ts.map +0 -1
  106. package/dist/openApiSchema/schemaConverter.core.js.map +0 -1
  107. package/dist/openApiSchema/schemaRegistry.core.d.ts.map +0 -1
  108. package/dist/openApiSchema/schemaRegistry.core.js.map +0 -1
  109. package/dist/openApiSerialization/index.d.ts.map +0 -1
  110. package/dist/openApiSerialization/index.js.map +0 -1
  111. package/dist/openApiSerialization/openApiSerializer.core.d.ts.map +0 -1
  112. package/dist/openApiSerialization/openApiSerializer.core.js.map +0 -1
  113. package/dist/openApiTypes/index.d.ts.map +0 -1
  114. package/dist/openApiTypes/index.js.map +0 -1
  115. package/dist/openApiTypes/openApiComponent.type.d.ts.map +0 -1
  116. package/dist/openApiTypes/openApiComponent.type.js.map +0 -1
  117. package/dist/openApiTypes/openApiDocument.type.d.ts.map +0 -1
  118. package/dist/openApiTypes/openApiDocument.type.js.map +0 -1
  119. package/dist/openApiTypes/openApiPath.type.d.ts.map +0 -1
  120. package/dist/openApiTypes/openApiPath.type.js.map +0 -1
  121. package/dist/openApiTypes/openApiSchema.type.d.ts.map +0 -1
  122. package/dist/openApiTypes/openApiSchema.type.js.map +0 -1
  123. package/dist/openApiTypes/openApiSecurity.type.d.ts.map +0 -1
  124. package/dist/openApiTypes/openApiSecurity.type.js.map +0 -1
  125. package/dist/openApiTypes/openApiTypes.core.d.ts.map +0 -1
  126. package/dist/openApiTypes/openApiTypes.core.js.map +0 -1
  127. package/dist/openApiValidation/index.d.ts.map +0 -1
  128. package/dist/openApiValidation/index.js.map +0 -1
  129. package/dist/openApiValidation/openApiValidator.core.d.ts.map +0 -1
  130. package/dist/openApiValidation/openApiValidator.core.js.map +0 -1
@@ -1,27 +1,5 @@
1
- /**
2
- * Converts a Zudojs-style route path to an OpenAPI path template.
3
- *
4
- * Example:
5
- * "/users/:id" -> "/users/{id}"
6
- * "/users/:id?": throws (optional path params not supported in OpenAPI)
7
- */
8
- export function toOpenAPIPath(path) {
9
- if (path.includes(":")) {
10
- const segments = path.split("/");
11
- const openApiSegments = segments.map((segment) => {
12
- if (segment.startsWith(":") && segment.endsWith("?")) {
13
- throw new Error(`Optional path parameter "${segment}" is not supported in OpenAPI. ` +
14
- `Use separate routes or a query parameter instead.`);
15
- }
16
- if (segment.startsWith(":")) {
17
- return `{${segment.slice(1)}}`;
18
- }
19
- return segment;
20
- });
21
- return openApiSegments.join("/");
22
- }
23
- return path;
24
- }
1
+ import { OpenAPIRouteError } from "../openApiErrors/openApiError.types.js";
2
+ import { PATH_TEMPLATE_PARAMETER } from "../openApiConstants/openApiConstants.core.js";
25
3
  /**
26
4
  * Maps Zudojs HTTP methods to OpenAPI methods.
27
5
  */
@@ -35,49 +13,117 @@ export const ZUDOLIB_TO_OPENAPI_METHODS = [
35
13
  "patch",
36
14
  "trace",
37
15
  ];
16
+ /** True when `method` is one an OpenAPI path item can carry. */
17
+ export function isOpenAPIMethod(method) {
18
+ return ZUDOLIB_TO_OPENAPI_METHODS.includes(method.toLowerCase());
19
+ }
20
+ /** A `:name` path segment, optionally suffixed with `?`. */
21
+ const COLON_PARAMETER = /^:([A-Za-z0-9_]+)(\?)?$/;
22
+ /**
23
+ * Converts a Zudojs-style route path to an OpenAPI path template.
24
+ *
25
+ * Example:
26
+ * `/users/:id` → `/users/{id}`
27
+ * `/users/{id}` → unchanged (already a template)
28
+ * `/users/:id?` → throws (OpenAPI has no optional path parameters)
29
+ * `/files/*` → throws (OpenAPI has no wildcard paths)
30
+ *
31
+ * Anything it cannot convert throws rather than passing through: an
32
+ * unconvertible segment that reaches the document produces an invalid path
33
+ * template, and the failure then surfaces in whatever consumes the spec
34
+ * rather than at the route that caused it.
35
+ */
36
+ export function toOpenAPIPath(path) {
37
+ if (!path.startsWith("/")) {
38
+ throw new OpenAPIRouteError(`Route path "${path}" must start with "/" to be a valid OpenAPI path.`, { metadata: { path } });
39
+ }
40
+ const segments = path.split("/").map((segment) => {
41
+ if (segment === "")
42
+ return segment;
43
+ if (segment === "*" || segment.startsWith("*")) {
44
+ throw new OpenAPIRouteError(`Wildcard path segment "${segment}" in "${path}" is not supported in OpenAPI. ` +
45
+ `Declare the concrete paths, or document it as a single templated parameter.`, { metadata: { path, segment } });
46
+ }
47
+ const colon = COLON_PARAMETER.exec(segment);
48
+ if (colon) {
49
+ if (colon[2] === "?") {
50
+ throw new OpenAPIRouteError(`Optional path parameter "${segment}" in "${path}" is not supported in OpenAPI. ` +
51
+ `Use separate routes or a query parameter instead.`, { metadata: { path, segment } });
52
+ }
53
+ return `{${colon[1]}}`;
54
+ }
55
+ if (segment.startsWith(":")) {
56
+ throw new OpenAPIRouteError(`Path parameter "${segment}" in "${path}" uses a syntax OpenAPI cannot express ` +
57
+ `(typed or pattern-constrained parameters). Use a plain ":name" segment and ` +
58
+ `describe the constraint with a parameter schema.`, { metadata: { path, segment } });
59
+ }
60
+ return segment;
61
+ });
62
+ return segments.join("/");
63
+ }
64
+ /** Extracts the parameter names from an OpenAPI path template. */
65
+ export function extractPathParameters(path) {
66
+ const names = [];
67
+ for (const match of path.matchAll(PATH_TEMPLATE_PARAMETER)) {
68
+ const name = match[1];
69
+ if (name !== undefined)
70
+ names.push(name);
71
+ }
72
+ return names;
73
+ }
74
+ function toParameter(parameter) {
75
+ return {
76
+ name: parameter.name,
77
+ in: parameter.in,
78
+ ...(parameter.description ? { description: parameter.description } : {}),
79
+ // A path parameter is required by the specification, so declaring one
80
+ // that is not required is a document that cannot validate.
81
+ required: parameter.in === "path" ? true : (parameter.required ?? false),
82
+ ...(parameter.deprecated ? { deprecated: true } : {}),
83
+ ...(parameter.schema !== undefined ? { schema: parameter.schema } : {}),
84
+ ...(parameter.example !== undefined ? { example: parameter.example } : {}),
85
+ };
86
+ }
87
+ /**
88
+ * Builds the `responses` object for an operation.
89
+ *
90
+ * Every documented response is carried through. Only when a route documents
91
+ * none at all is a `200` synthesized, because `responses` is required.
92
+ */
93
+ export function buildResponses(metadata) {
94
+ const declared = metadata?.openapi?.responses;
95
+ if (declared && Object.keys(declared).length > 0) {
96
+ return Object.freeze({ ...declared });
97
+ }
98
+ return Object.freeze({ "200": { description: "OK" } });
99
+ }
38
100
  /**
39
101
  * Converts a route with metadata into an OpenAPI operation.
40
102
  */
41
103
  export function convertRouteToOpenAPI(method, path, metadata) {
104
+ if (!isOpenAPIMethod(method)) {
105
+ throw new OpenAPIRouteError(`HTTP method "${method}" has no OpenAPI path item field. ` +
106
+ `Supported: ${ZUDOLIB_TO_OPENAPI_METHODS.join(", ")}.`, { metadata: { method, path } });
107
+ }
42
108
  const openApiPath = toOpenAPIPath(path);
43
- const openApiMetadata = metadata?.openapi;
109
+ const meta = metadata?.openapi;
44
110
  const operation = {
45
- responses: openApiMetadata?.responses
46
- ? {
47
- "200": {
48
- description: "OK",
49
- ...(openApiMetadata.responses["200"] ?? {}),
50
- },
51
- }
52
- : { "200": { description: "OK" } },
53
- ...(openApiMetadata?.operationId
54
- ? { operationId: openApiMetadata.operationId }
55
- : {}),
56
- ...(openApiMetadata?.summary ? { summary: openApiMetadata.summary } : {}),
57
- ...(openApiMetadata?.description
58
- ? { description: openApiMetadata.description }
59
- : {}),
60
- ...(openApiMetadata?.tags?.length
61
- ? { tags: [...openApiMetadata.tags] }
62
- : {}),
63
- ...(openApiMetadata?.deprecated !== undefined
64
- ? { deprecated: openApiMetadata.deprecated }
65
- : {}),
66
- ...(openApiMetadata?.parameters?.length
67
- ? { parameters: [...openApiMetadata.parameters] }
68
- : {}),
69
- ...(openApiMetadata?.requestBody
70
- ? { requestBody: openApiMetadata.requestBody }
71
- : {}),
72
- ...(openApiMetadata?.security?.length
73
- ? { security: [...openApiMetadata.security] }
74
- : {}),
75
- ...(openApiMetadata?.servers?.length
76
- ? { servers: [...openApiMetadata.servers] }
111
+ ...(meta?.operationId ? { operationId: meta.operationId } : {}),
112
+ ...(meta?.summary ? { summary: meta.summary } : {}),
113
+ ...(meta?.description ? { description: meta.description } : {}),
114
+ ...(meta?.tags?.length ? { tags: [...meta.tags] } : {}),
115
+ ...(meta?.deprecated !== undefined ? { deprecated: meta.deprecated } : {}),
116
+ ...(meta?.parameters?.length
117
+ ? { parameters: meta.parameters.map(toParameter) }
77
118
  : {}),
119
+ ...(meta?.requestBody ? { requestBody: meta.requestBody } : {}),
120
+ ...(meta?.security?.length ? { security: [...meta.security] } : {}),
121
+ ...(meta?.servers?.length ? { servers: [...meta.servers] } : {}),
122
+ ...(meta?.externalDocs ? { externalDocs: meta.externalDocs } : {}),
123
+ responses: buildResponses(metadata),
78
124
  };
79
125
  return {
80
- method,
126
+ method: method.toLowerCase(),
81
127
  path: openApiPath,
82
128
  operation,
83
129
  };
@@ -1,22 +1,15 @@
1
- import type { OpenAPIRequestBody } from "../openApiTypes/openApiTypes.core.js";
2
1
  /**
3
- * Metadata attached to a route for OpenAPI generation.
2
+ * Route metadata consumed by the OpenAPI generator.
3
+ *
4
+ * This is the single definition of these shapes. They were previously
5
+ * declared in both this file and the scanner, and had already drifted —
6
+ * `requestBody` was typed one way in one copy and `unknown` in the other,
7
+ * so which one a consumer got depended on the import path they happened to
8
+ * use.
4
9
  */
5
- export interface RouteOpenAPIMetadata {
6
- readonly operationId?: string;
7
- readonly summary?: string;
8
- readonly description?: string;
9
- readonly tags?: readonly string[];
10
- readonly deprecated?: boolean;
11
- readonly parameters?: readonly RouteParameterMetadata[];
12
- readonly requestBody?: OpenAPIRequestBody;
13
- readonly responses?: Record<string, unknown>;
14
- readonly security?: readonly Record<string, readonly string[]>[];
15
- readonly servers?: readonly {
16
- readonly url: string;
17
- readonly description?: string;
18
- }[];
19
- }
10
+ import type { OpenAPIExternalDocumentation, OpenAPIRequestBody, OpenAPIResponse, OpenAPISecurityRequirement, OpenAPIServer } from "../openApiTypes/openApiTypes.core.js";
11
+ /** HTTP methods an OpenAPI path item can carry. */
12
+ export type OpenAPIHttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
20
13
  /**
21
14
  * Parameter metadata for OpenAPI generation.
22
15
  */
@@ -29,10 +22,40 @@ export interface RouteParameterMetadata {
29
22
  readonly schema?: unknown;
30
23
  readonly example?: unknown;
31
24
  }
25
+ /**
26
+ * Metadata attached to a route for OpenAPI generation.
27
+ */
28
+ export interface RouteOpenAPIMetadata {
29
+ readonly operationId?: string;
30
+ readonly summary?: string;
31
+ readonly description?: string;
32
+ readonly tags?: readonly string[];
33
+ readonly deprecated?: boolean;
34
+ readonly parameters?: readonly RouteParameterMetadata[];
35
+ readonly requestBody?: OpenAPIRequestBody;
36
+ /**
37
+ * Responses keyed by status code, `default`, or a `2XX`-style range.
38
+ * Every entry reaches the document — this is not a 200-only field.
39
+ */
40
+ readonly responses?: Readonly<Record<string, OpenAPIResponse>>;
41
+ readonly security?: readonly OpenAPISecurityRequirement[];
42
+ readonly servers?: readonly OpenAPIServer[];
43
+ readonly externalDocs?: OpenAPIExternalDocumentation;
44
+ /** Excludes the route from the generated document. */
45
+ readonly hidden?: boolean;
46
+ }
32
47
  /**
33
48
  * Route metadata container.
34
49
  */
35
50
  export interface RouteMetadata {
36
51
  readonly openapi?: RouteOpenAPIMetadata;
37
52
  }
53
+ /**
54
+ * Route information accepted by the scanner.
55
+ */
56
+ export interface RouteInfo {
57
+ readonly method: OpenAPIHttpMethod;
58
+ readonly path: string;
59
+ readonly metadata?: RouteMetadata;
60
+ }
38
61
  //# sourceMappingURL=routeMetadata.type.d.ts.map
@@ -1,2 +1,11 @@
1
+ /**
2
+ * Route metadata consumed by the OpenAPI generator.
3
+ *
4
+ * This is the single definition of these shapes. They were previously
5
+ * declared in both this file and the scanner, and had already drifted —
6
+ * `requestBody` was typed one way in one copy and `unknown` in the other,
7
+ * so which one a consumer got depended on the import path they happened to
8
+ * use.
9
+ */
1
10
  export {};
2
11
  //# sourceMappingURL=routeMetadata.type.js.map
@@ -1,56 +1,26 @@
1
1
  import type { OpenAPIRoute } from "../openApiRegistry/openApiRegistry.type.js";
2
+ import type { RouteInfo } from "./routeMetadata.type.js";
2
3
  /**
3
- * Metadata attached to a route for OpenAPI generation.
4
- */
5
- export interface RouteOpenAPIMetadata {
6
- readonly operationId?: string;
7
- readonly summary?: string;
8
- readonly description?: string;
9
- readonly tags?: readonly string[];
10
- readonly deprecated?: boolean;
11
- readonly parameters?: readonly RouteParameterMetadata[];
12
- readonly requestBody?: unknown;
13
- readonly responses?: Record<string, unknown>;
14
- readonly security?: readonly Record<string, readonly string[]>[];
15
- readonly servers?: readonly {
16
- readonly url: string;
17
- readonly description?: string;
18
- }[];
19
- }
20
- /**
21
- * Parameter metadata for OpenAPI generation.
22
- */
23
- export interface RouteParameterMetadata {
24
- readonly name: string;
25
- readonly in: "query" | "header" | "path" | "cookie";
26
- readonly description?: string;
27
- readonly required?: boolean;
28
- readonly deprecated?: boolean;
29
- readonly schema?: unknown;
30
- readonly example?: unknown;
31
- }
32
- /**
33
- * Route metadata container.
34
- */
35
- export interface RouteMetadata {
36
- readonly openapi?: RouteOpenAPIMetadata;
37
- }
38
- /**
39
- * Route information for scanning.
40
- */
41
- export interface RouteInfo {
42
- readonly method: "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
43
- readonly path: string;
44
- readonly metadata?: {
45
- readonly openapi?: RouteOpenAPIMetadata;
46
- };
47
- }
48
- /**
49
- * Default OpenAPI route scanner implementation.
4
+ * Collects routes and converts them into OpenAPI operations.
5
+ *
6
+ * Duplicates are rejected on the way in. Accepting them and letting the
7
+ * registry throw during generation reported the problem far from the
8
+ * `addRoute` call that caused it, and only after some routes had already been
9
+ * registered.
50
10
  */
51
11
  export declare class OpenAPIRouteScannerImpl {
52
12
  private readonly routes;
13
+ /** Registers a route. */
53
14
  addRoute(route: RouteInfo): void;
15
+ /** Registers a route, replacing any existing one for the same method+path. */
16
+ setRoute(route: RouteInfo): void;
17
+ /** True when a route is registered for this method and path. */
18
+ hasRoute(method: string, path: string): boolean;
19
+ /** Removes a route. Returns whether one was removed. */
20
+ removeRoute(method: string, path: string): boolean;
21
+ /** Number of registered routes. */
22
+ get size(): number;
23
+ /** Converts every registered route into an OpenAPI operation. */
54
24
  scan(): readonly OpenAPIRoute[];
55
25
  clear(): void;
56
26
  }
@@ -1,71 +1,56 @@
1
- import { toOpenAPIPath } from "./routeConverter.core.js";
1
+ import { convertRouteToOpenAPI } from "./routeConverter.core.js";
2
+ import { OpenAPIRouteError } from "../openApiErrors/openApiError.types.js";
2
3
  /**
3
- * Default OpenAPI route scanner implementation.
4
+ * Collects routes and converts them into OpenAPI operations.
5
+ *
6
+ * Duplicates are rejected on the way in. Accepting them and letting the
7
+ * registry throw during generation reported the problem far from the
8
+ * `addRoute` call that caused it, and only after some routes had already been
9
+ * registered.
4
10
  */
5
11
  export class OpenAPIRouteScannerImpl {
6
- routes = [];
12
+ routes = new Map();
13
+ /** Registers a route. */
7
14
  addRoute(route) {
8
- this.routes.push(route);
15
+ const key = `${route.method.toLowerCase()}:${route.path}`;
16
+ if (this.routes.has(key)) {
17
+ throw new OpenAPIRouteError(`Route ${route.method.toUpperCase()} ${route.path} is already registered.`, { metadata: { method: route.method, path: route.path } });
18
+ }
19
+ this.routes.set(key, route);
20
+ }
21
+ /** Registers a route, replacing any existing one for the same method+path. */
22
+ setRoute(route) {
23
+ this.routes.set(`${route.method.toLowerCase()}:${route.path}`, route);
24
+ }
25
+ /** True when a route is registered for this method and path. */
26
+ hasRoute(method, path) {
27
+ return this.routes.has(`${method.toLowerCase()}:${path}`);
28
+ }
29
+ /** Removes a route. Returns whether one was removed. */
30
+ removeRoute(method, path) {
31
+ return this.routes.delete(`${method.toLowerCase()}:${path}`);
32
+ }
33
+ /** Number of registered routes. */
34
+ get size() {
35
+ return this.routes.size;
9
36
  }
37
+ /** Converts every registered route into an OpenAPI operation. */
10
38
  scan() {
11
39
  const result = [];
12
- for (const route of this.routes) {
13
- const openApiPath = toOpenAPIPath(route.path);
14
- const response200 = {
15
- "200": {
16
- description: "OK",
17
- ...(route.metadata?.openapi?.responses?.["200"]
18
- ? route.metadata.openapi.responses["200"]
19
- : {}),
20
- },
21
- };
22
- const operation = {
23
- ...(route.metadata?.openapi?.operationId
24
- ? { operationId: route.metadata.openapi.operationId }
25
- : {}),
26
- ...(route.metadata?.openapi?.summary
27
- ? { summary: route.metadata.openapi.summary }
28
- : {}),
29
- ...(route.metadata?.openapi?.description
30
- ? { description: route.metadata.openapi.description }
31
- : {}),
32
- ...(route.metadata?.openapi?.tags?.length
33
- ? { tags: [...route.metadata.openapi.tags] }
34
- : {}),
35
- ...(route.metadata?.openapi?.deprecated !== undefined
36
- ? { deprecated: route.metadata.openapi.deprecated }
37
- : {}),
38
- ...(route.metadata?.openapi?.parameters?.length
39
- ? {
40
- parameters: [
41
- ...route.metadata.openapi.parameters,
42
- ],
43
- }
44
- : {}),
45
- ...(route.metadata?.openapi?.requestBody
46
- ? {
47
- requestBody: route.metadata.openapi
48
- .requestBody,
49
- }
50
- : {}),
51
- ...(route.metadata?.openapi?.security?.length
52
- ? { security: [...route.metadata.openapi.security] }
53
- : {}),
54
- ...(route.metadata?.openapi?.servers?.length
55
- ? { servers: [...route.metadata.openapi.servers] }
56
- : {}),
57
- responses: response200,
58
- };
40
+ for (const route of this.routes.values()) {
41
+ if (route.metadata?.openapi?.hidden === true)
42
+ continue;
43
+ const converted = convertRouteToOpenAPI(route.method, route.path, route.metadata);
59
44
  result.push({
60
- method: route.method,
61
- path: openApiPath,
62
- operation: operation,
45
+ method: converted.method,
46
+ path: converted.path,
47
+ operation: converted.operation,
63
48
  });
64
49
  }
65
50
  return result;
66
51
  }
67
52
  clear() {
68
- this.routes.length = 0;
53
+ this.routes.clear();
69
54
  }
70
55
  }
71
56
  //# sourceMappingURL=routeScanner.core.js.map
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Schema conversion and registry for OpenAPI generation.
5
5
  */
6
- export type { SchemaConverter, SchemaConversionResult, } from "./schemaConverter.core.js";
7
- export { convertSchema, createSchemaConverter, } from "./schemaConverter.core.js";
8
- export type { SchemaRegistry } from "./schemaRegistry.core.js";
6
+ export type { SchemaConverter, SchemaConversionResult, SchemaConversionOptions, } from "./schemaConverter.core.js";
7
+ export { convertSchema, createSchemaConverter, isVersion31, } from "./schemaConverter.core.js";
8
+ export type { SchemaRegistry, SchemaRegistryOptions, } from "./schemaRegistry.core.js";
9
9
  export { SchemaRegistryImpl } from "./schemaRegistry.core.js";
10
- export { createComponentReference } from "./references.core.js";
10
+ export { createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, type ComponentSection, } from "./references.core.js";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Schema conversion and registry for OpenAPI generation.
5
5
  */
6
- export { convertSchema, createSchemaConverter, } from "./schemaConverter.core.js";
6
+ export { convertSchema, createSchemaConverter, isVersion31, } from "./schemaConverter.core.js";
7
7
  export { SchemaRegistryImpl } from "./schemaRegistry.core.js";
8
- export { createComponentReference } from "./references.core.js";
8
+ export { createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, } from "./references.core.js";
9
9
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,26 @@
1
1
  import type { OpenAPIReference } from "../openApiTypes/openApiTypes.core.js";
2
+ /** The component sections a `$ref` may point into. */
3
+ export type ComponentSection = "schemas" | "responses" | "parameters" | "requestBodies" | "headers" | "examples" | "securitySchemes" | "links" | "callbacks";
4
+ /**
5
+ * Escapes a component name for use inside a JSON Pointer.
6
+ *
7
+ * RFC 6901 reserves `~` and `/`; a component called `Order/Line` produces a
8
+ * pointer that resolves to the wrong place — or nowhere — unless they are
9
+ * escaped. The `~0` substitution must come first, or the `~1` it introduces
10
+ * would be escaped again.
11
+ */
12
+ export declare function escapeJsonPointerSegment(segment: string): string;
13
+ /** Reverses {@link escapeJsonPointerSegment}. */
14
+ export declare function unescapeJsonPointerSegment(segment: string): string;
2
15
  /**
3
16
  * Creates an OpenAPI component reference.
4
17
  *
18
+ * This is the single `$ref` builder in the package — the registry and the
19
+ * schema registry both delegate here, so escaping cannot be right in one
20
+ * place and missing in another.
21
+ *
5
22
  * @param section - The component section (e.g., "schemas", "responses")
6
23
  * @param name - The component name
7
24
  */
8
- export declare function createComponentReference(section: string, name: string): OpenAPIReference;
25
+ export declare function createComponentReference(section: ComponentSection | string, name: string): OpenAPIReference;
9
26
  //# sourceMappingURL=references.core.d.ts.map
@@ -1,12 +1,32 @@
1
+ import { COMPONENT_REF_PREFIX } from "../openApiConstants/openApiConstants.core.js";
2
+ /**
3
+ * Escapes a component name for use inside a JSON Pointer.
4
+ *
5
+ * RFC 6901 reserves `~` and `/`; a component called `Order/Line` produces a
6
+ * pointer that resolves to the wrong place — or nowhere — unless they are
7
+ * escaped. The `~0` substitution must come first, or the `~1` it introduces
8
+ * would be escaped again.
9
+ */
10
+ export function escapeJsonPointerSegment(segment) {
11
+ return segment.replace(/~/g, "~0").replace(/\//g, "~1");
12
+ }
13
+ /** Reverses {@link escapeJsonPointerSegment}. */
14
+ export function unescapeJsonPointerSegment(segment) {
15
+ return segment.replace(/~1/g, "/").replace(/~0/g, "~");
16
+ }
1
17
  /**
2
18
  * Creates an OpenAPI component reference.
3
19
  *
20
+ * This is the single `$ref` builder in the package — the registry and the
21
+ * schema registry both delegate here, so escaping cannot be right in one
22
+ * place and missing in another.
23
+ *
4
24
  * @param section - The component section (e.g., "schemas", "responses")
5
25
  * @param name - The component name
6
26
  */
7
27
  export function createComponentReference(section, name) {
8
28
  return {
9
- $ref: `#/components/${section}/${name}`,
29
+ $ref: `${COMPONENT_REF_PREFIX}/${escapeJsonPointerSegment(section)}/${escapeJsonPointerSegment(name)}`,
10
30
  };
11
31
  }
12
32
  //# sourceMappingURL=references.core.js.map
@@ -1,11 +1,59 @@
1
1
  import type { OpenAPISchema } from "../openApiTypes/openApiTypes.core.js";
2
+ /**
3
+ * Converts `@zudojs/schema` schemas into OpenAPI schema objects.
4
+ *
5
+ * The conversion reads the schema classes' runtime fields directly rather
6
+ * than importing them, so this package stays usable with any object that
7
+ * follows the same shape. That structural coupling is the reason every field
8
+ * name below is named in one place and covered by tests: a rename in
9
+ * `@zudojs/schema` is not a compile error here, it is a silently empty
10
+ * document.
11
+ *
12
+ * Field names as of `@zudojs/schema@0.1.0`:
13
+ * object `_config.shape`, `_config.requiredKeys` (a Set), `_config.unknownKeys`
14
+ * array `_config.itemSchema`, `_config.min`, `_config.max`, `_config.length`
15
+ * string `_config.min|max|length|pattern|format`
16
+ * number `_config.min|max|int|gt|lt|multipleOf`
17
+ * union `_schemas` intersection `_left` / `_right`
18
+ * enum `_values` literal `_expected`
19
+ * optional `_inner` nullable `_inner`
20
+ * default `_inner`, `_defaultValue` refine `_inner`
21
+ * transform `_base` lazy `_factory` / `_inner`
22
+ * record `_keySchema`, `_valueSchema` tuple `_schemas`
23
+ * map `_keySchema`, `_valueSchema` set `_valueSchema`
24
+ * metadata `_metadata` (description, example, title, deprecated)
25
+ */
2
26
  export interface SchemaConversionResult {
3
27
  readonly schema: OpenAPISchema;
4
28
  readonly warnings: readonly string[];
5
29
  }
6
- export declare function convertSchema(input: unknown, _visited?: Set<object>): SchemaConversionResult;
30
+ /** Options controlling how a schema is converted. */
31
+ export interface SchemaConversionOptions {
32
+ /**
33
+ * Target specification version. 3.1 expresses nullability as
34
+ * `type: [t, "null"]`; 3.0 uses `nullable: true`, which 3.1 removed.
35
+ */
36
+ readonly version?: string;
37
+ /**
38
+ * Depth at which conversion stops descending. Guards against a recursive
39
+ * schema whose `lazy` wrapper resolves to itself. Default: 32.
40
+ */
41
+ readonly maxDepth?: number;
42
+ }
43
+ /** True when the document being produced follows OpenAPI 3.1 or later. */
44
+ export declare function isVersion31(version: string): boolean;
45
+ /**
46
+ * Converts a schema into an OpenAPI schema object.
47
+ *
48
+ * Warnings describe everything that could not be represented exactly. They
49
+ * are part of the result rather than a side channel, because silently
50
+ * emitting `{}` for an unsupported construct is how a specification ends up
51
+ * documenting nothing.
52
+ */
53
+ export declare function convertSchema(input: unknown, options?: SchemaConversionOptions): SchemaConversionResult;
7
54
  export interface SchemaConverter {
8
- convert(input: unknown, visited?: Set<object>): SchemaConversionResult;
55
+ convert(input: unknown, options?: SchemaConversionOptions): SchemaConversionResult;
9
56
  }
10
- export declare function createSchemaConverter(): SchemaConverter;
57
+ /** Creates a converter bound to a specification version. */
58
+ export declare function createSchemaConverter(defaults?: SchemaConversionOptions): SchemaConverter;
11
59
  //# sourceMappingURL=schemaConverter.core.d.ts.map