vovk 3.5.1 → 3.7.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 (113) hide show
  1. package/dist/client/create-rpc.d.ts +13 -0
  2. package/dist/client/create-rpc.js +147 -0
  3. package/dist/client/default-handler.d.ts +6 -0
  4. package/dist/client/default-handler.js +25 -0
  5. package/dist/client/default-stream-handler.d.ts +16 -0
  6. package/dist/client/default-stream-handler.js +282 -0
  7. package/dist/client/fetcher.d.ts +1 -1
  8. package/dist/client/fetcher.js +2 -2
  9. package/dist/client/serialize-query.d.ts +13 -0
  10. package/dist/client/serialize-query.js +62 -0
  11. package/dist/core/apply-decorator-adapter.d.ts +7 -0
  12. package/dist/core/apply-decorator-adapter.js +50 -0
  13. package/dist/core/controllers-to-static-params.d.ts +13 -0
  14. package/dist/core/controllers-to-static-params.js +32 -0
  15. package/dist/core/create-decorator.d.ts +12 -0
  16. package/dist/core/create-decorator.js +52 -0
  17. package/dist/core/decorators.js +4 -4
  18. package/dist/core/get-schema.d.ts +21 -0
  19. package/dist/core/get-schema.js +31 -0
  20. package/dist/core/http-exception.d.ts +16 -0
  21. package/dist/core/http-exception.js +26 -0
  22. package/dist/core/init-segment.d.ts +33 -0
  23. package/dist/core/init-segment.js +62 -0
  24. package/dist/core/json-lines-responder.d.ts +42 -0
  25. package/dist/core/json-lines-responder.js +94 -0
  26. package/dist/core/resolve-generator-config-values.d.ts +19 -0
  27. package/dist/core/resolve-generator-config-values.js +59 -0
  28. package/dist/core/set-handler-schema.d.ts +4 -0
  29. package/dist/core/set-handler-schema.js +12 -0
  30. package/dist/core/to-download-response.d.ts +11 -0
  31. package/dist/core/to-download-response.js +25 -0
  32. package/dist/core/vovk-app.d.ts +36 -0
  33. package/dist/core/vovk-app.js +318 -0
  34. package/dist/index.d.ts +10 -10
  35. package/dist/index.js +10 -10
  36. package/dist/internal.d.ts +10 -10
  37. package/dist/internal.js +9 -9
  38. package/dist/openapi/error.js +1 -1
  39. package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.d.ts +23 -0
  40. package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.js +90 -0
  41. package/dist/openapi/openapi-to-vovk-schema/index.d.ts +5 -0
  42. package/dist/openapi/openapi-to-vovk-schema/index.js +179 -0
  43. package/dist/openapi/openapi-to-vovk-schema/inline-refs.d.ts +9 -0
  44. package/dist/openapi/openapi-to-vovk-schema/inline-refs.js +99 -0
  45. package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.d.ts +7 -0
  46. package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.js +51 -0
  47. package/dist/openapi/operation.js +1 -1
  48. package/dist/openapi/tool.js +1 -1
  49. package/dist/openapi/vovk-schema-to-openapi.d.ts +21 -0
  50. package/dist/openapi/vovk-schema-to-openapi.js +250 -0
  51. package/dist/req/buffer-body.d.ts +1 -0
  52. package/dist/req/buffer-body.js +30 -0
  53. package/dist/req/parse-body.d.ts +4 -0
  54. package/dist/req/parse-body.js +49 -0
  55. package/dist/req/parse-form.d.ts +1 -0
  56. package/dist/req/parse-form.js +24 -0
  57. package/dist/req/parse-query.d.ts +24 -0
  58. package/dist/req/parse-query.js +156 -0
  59. package/dist/req/req-meta.d.ts +2 -0
  60. package/dist/req/req-meta.js +10 -0
  61. package/dist/req/req-query.d.ts +2 -0
  62. package/dist/req/req-query.js +4 -0
  63. package/dist/req/validate-content-type.d.ts +1 -0
  64. package/dist/req/validate-content-type.js +32 -0
  65. package/dist/samples/create-code-samples.d.ts +20 -0
  66. package/dist/samples/create-code-samples.js +293 -0
  67. package/dist/samples/object-to-code.d.ts +8 -0
  68. package/dist/samples/object-to-code.js +38 -0
  69. package/dist/samples/schema-to-code.d.ts +11 -0
  70. package/dist/samples/schema-to-code.js +264 -0
  71. package/dist/samples/schema-to-object.d.ts +2 -0
  72. package/dist/samples/schema-to-object.js +164 -0
  73. package/dist/samples/schema-to-ts-type.d.ts +2 -0
  74. package/dist/samples/schema-to-ts-type.js +114 -0
  75. package/dist/tools/create-tool-factory.d.ts +135 -0
  76. package/dist/tools/create-tool-factory.js +62 -0
  77. package/dist/tools/create-tool.d.ts +126 -0
  78. package/dist/tools/create-tool.js +6 -0
  79. package/dist/tools/derive-tools.d.ts +46 -0
  80. package/dist/tools/derive-tools.js +131 -0
  81. package/dist/tools/to-model-output-default.d.ts +7 -0
  82. package/dist/tools/to-model-output-default.js +7 -0
  83. package/dist/tools/to-model-output-mcp.d.ts +30 -0
  84. package/dist/tools/to-model-output-mcp.js +54 -0
  85. package/dist/tools/to-model-output.d.ts +8 -0
  86. package/dist/tools/to-model-output.js +10 -0
  87. package/dist/types/client.d.ts +3 -3
  88. package/dist/types/core.d.ts +1 -1
  89. package/dist/types/inference.d.ts +1 -1
  90. package/dist/types/validation.d.ts +1 -1
  91. package/dist/utils/camel-case.d.ts +6 -0
  92. package/dist/utils/camel-case.js +34 -0
  93. package/dist/utils/deep-extend.d.ts +54 -0
  94. package/dist/utils/deep-extend.js +127 -0
  95. package/dist/utils/file-name-to-disposition.d.ts +1 -0
  96. package/dist/utils/file-name-to-disposition.js +3 -0
  97. package/dist/utils/to-kebab-case.d.ts +1 -0
  98. package/dist/utils/to-kebab-case.js +5 -0
  99. package/dist/utils/trim-path.d.ts +1 -0
  100. package/dist/utils/trim-path.js +1 -0
  101. package/dist/utils/upper-first.d.ts +1 -0
  102. package/dist/utils/upper-first.js +3 -0
  103. package/dist/validation/create-standard-validation.d.ts +268 -0
  104. package/dist/validation/create-standard-validation.js +45 -0
  105. package/dist/validation/create-validate-on-client.d.ts +14 -0
  106. package/dist/validation/create-validate-on-client.js +23 -0
  107. package/dist/validation/procedure.d.ts +24 -24
  108. package/dist/validation/procedure.js +1 -1
  109. package/dist/validation/validation-schemas-object-to-single-validation-schema.d.ts +17 -0
  110. package/dist/validation/validation-schemas-object-to-single-validation-schema.js +92 -0
  111. package/dist/validation/with-validation-library.d.ts +119 -0
  112. package/dist/validation/with-validation-library.js +184 -0
  113. package/package.json +13 -5
@@ -0,0 +1,179 @@
1
+ import { applyComponentsSchemas } from './apply-components-schemas.js';
2
+ import { inlineRefs } from './inline-refs.js';
3
+ import { pruneComponentsSchemas } from './prune-components-schemas.js';
4
+ import { VovkSchemaIdEnum } from '../../types/enums.js';
5
+ import { schemaToTsType } from '../../samples/schema-to-ts-type.js';
6
+ function getTsTypeString(contentType, schema) {
7
+ const tsTypes = new Set(contentType.flatMap((ct) => {
8
+ switch (ct) {
9
+ case 'application/json':
10
+ return [schemaToTsType(schema)];
11
+ case 'multipart/form-data':
12
+ return ['FormData', schemaToTsType(schema)];
13
+ case 'application/x-www-form-urlencoded':
14
+ return ['FormData', 'URLSearchParams', schemaToTsType(schema)];
15
+ case 'text/plain':
16
+ return ['string'];
17
+ default:
18
+ return ['Blob', 'ArrayBuffer', 'Uint8Array'];
19
+ }
20
+ }));
21
+ return [...tsTypes].join(' | ') || schemaToTsType(schema);
22
+ }
23
+ export function openAPIToVovkSchema({ apiRoot, source: { object: openAPIObject }, getModuleName, getMethodName, filterOperations, pruneComponents, errorMessageKey, segmentName, }) {
24
+ segmentName = segmentName ?? '';
25
+ const forceApiRoot = apiRoot ||
26
+ (openAPIObject.servers?.[0]?.url ??
27
+ ('host' in openAPIObject
28
+ ? `https://${openAPIObject.host}${'basePath' in openAPIObject ? openAPIObject.basePath : ''}`
29
+ : null));
30
+ if (!forceApiRoot) {
31
+ throw new Error('API root URL is required in OpenAPI configuration');
32
+ }
33
+ const { paths, ...noPathsOpenAPIObject } = openAPIObject;
34
+ const schema = {
35
+ $schema: VovkSchemaIdEnum.SCHEMA,
36
+ segments: {
37
+ [segmentName]: {
38
+ $schema: VovkSchemaIdEnum.SEGMENT,
39
+ emitSchema: true,
40
+ segmentName,
41
+ segmentType: 'mixin',
42
+ controllers: {},
43
+ forceApiRoot,
44
+ meta: {
45
+ openAPIObject: noPathsOpenAPIObject,
46
+ },
47
+ },
48
+ },
49
+ };
50
+ const segment = schema.segments[segmentName];
51
+ Object.entries(paths ?? {}).forEach(([path, operations]) => {
52
+ Object.entries(operations ?? {})
53
+ .filter(([, operation]) => operation && typeof operation === 'object')
54
+ .forEach(([method, operation]) => {
55
+ if (filterOperations &&
56
+ !filterOperations({
57
+ method: method.toUpperCase(),
58
+ path,
59
+ openAPIObject,
60
+ operationObject: operation,
61
+ })) {
62
+ return;
63
+ }
64
+ const rpcModuleName = getModuleName({
65
+ method: method.toUpperCase(),
66
+ path,
67
+ openAPIObject,
68
+ operationObject: operation,
69
+ });
70
+ const handlerName = getMethodName({
71
+ method: method.toUpperCase(),
72
+ path,
73
+ openAPIObject,
74
+ operationObject: operation,
75
+ });
76
+ segment.controllers[rpcModuleName] ??= {
77
+ rpcModuleName,
78
+ handlers: {},
79
+ };
80
+ const parameters = inlineRefs(operation.parameters ?? [], openAPIObject);
81
+ const queryProperties = parameters?.filter((p) => p.in === 'query') ?? null;
82
+ const pathProperties = parameters?.filter((p) => p.in === 'path') ?? null;
83
+ const query = queryProperties?.length
84
+ ? {
85
+ type: 'object',
86
+ properties: Object.fromEntries(queryProperties.map((p) => [p.name, p.schema])),
87
+ required: queryProperties.filter((p) => p.required).map((p) => p.name),
88
+ }
89
+ : null;
90
+ const params = pathProperties?.length
91
+ ? {
92
+ type: 'object',
93
+ properties: Object.fromEntries(pathProperties.map((p) => [p.name, p.schema])),
94
+ required: pathProperties.filter((p) => p.required).map((p) => p.name),
95
+ }
96
+ : null;
97
+ const requestBodyContent = inlineRefs(operation.requestBody, openAPIObject)?.content ?? {};
98
+ const contentTypes = [
99
+ 'application/json',
100
+ 'multipart/form-data',
101
+ 'application/x-www-form-urlencoded',
102
+ 'text/plain',
103
+ 'application/octet-stream',
104
+ ];
105
+ const bodySchemas = contentTypes
106
+ .map((contentType) => requestBodyContent[contentType]?.schema
107
+ ? { ...requestBodyContent[contentType].schema, 'x-contentType': [contentType] }
108
+ : null)
109
+ .filter(Boolean);
110
+ const body = !bodySchemas.length
111
+ ? null
112
+ : bodySchemas.length === 1
113
+ ? {
114
+ ...bodySchemas[0],
115
+ 'x-tsType': getTsTypeString(bodySchemas[0]['x-contentType'] ?? [], bodySchemas[0]),
116
+ }
117
+ : {
118
+ anyOf: bodySchemas,
119
+ 'x-tsType': getTsTypeString(bodySchemas.flatMap((s) => s['x-contentType'] ?? []), { anyOf: bodySchemas }),
120
+ };
121
+ const output = operation.responses?.['200']?.content?.['application/json']?.schema ??
122
+ operation.responses?.['201']?.content?.['application/json']?.schema ??
123
+ null;
124
+ const iteration = operation.responses?.['200']?.content?.['application/jsonl']?.schema ??
125
+ operation.responses?.['201']?.content?.['application/jsonl']?.schema ??
126
+ operation.responses?.['200']?.content?.['application/jsonlines']?.schema ??
127
+ operation.responses?.['201']?.content?.['application/jsonlines']?.schema ??
128
+ null;
129
+ if (errorMessageKey) {
130
+ operation['x-errorMessageKey'] = errorMessageKey;
131
+ }
132
+ const componentsSchemas = openAPIObject.components?.schemas ??
133
+ ('definitions' in openAPIObject ? openAPIObject.definitions : {});
134
+ segment.controllers[rpcModuleName].handlers[handlerName] = {
135
+ httpMethod: method.toUpperCase(),
136
+ path,
137
+ operationObject: operation,
138
+ misc: {
139
+ isOpenAPIMixin: true,
140
+ originalPath: path,
141
+ },
142
+ validation: {
143
+ ...(query && {
144
+ query: applyComponentsSchemas(query, componentsSchemas, segmentName),
145
+ }),
146
+ ...(params && {
147
+ params: applyComponentsSchemas(params, componentsSchemas, segmentName),
148
+ }),
149
+ ...(body && {
150
+ body: applyComponentsSchemas(body, componentsSchemas, segmentName),
151
+ }),
152
+ ...(output && {
153
+ // Response slot: not validated + typed via x-tsType → skip $defs (dedup).
154
+ output: applyComponentsSchemas(output, componentsSchemas, segmentName, false),
155
+ }),
156
+ ...(iteration && {
157
+ iteration: applyComponentsSchemas(iteration, componentsSchemas, segmentName, false),
158
+ }),
159
+ },
160
+ };
161
+ });
162
+ });
163
+ if (pruneComponents && noPathsOpenAPIObject.components?.schemas) {
164
+ // Reassign with fresh objects only — `noPathsOpenAPIObject` shares references with the
165
+ // caller's spec, so the original `components.schemas` must stay untouched. Walking the
166
+ // whole controllers tree (validation slots + raw operation objects) keeps every `$ref`
167
+ // a kept handler carries resolvable against the pruned meta.
168
+ segment.meta = {
169
+ openAPIObject: {
170
+ ...noPathsOpenAPIObject,
171
+ components: {
172
+ ...noPathsOpenAPIObject.components,
173
+ schemas: pruneComponentsSchemas(segment.controllers, noPathsOpenAPIObject.components.schemas),
174
+ },
175
+ },
176
+ };
177
+ }
178
+ return schema;
179
+ }
@@ -0,0 +1,9 @@
1
+ import type { OpenAPIObject } from 'openapi3-ts/oas31';
2
+ /**
3
+ * Resolves $ref references at the first level only (except for components/schemas references)
4
+ * For arrays, checks each item at the first level
5
+ * @param obj - The object to process (may contain $ref properties)
6
+ * @param openAPIObject - The complete OpenAPI document containing definitions
7
+ * @returns The object with resolved references (except components/schemas)
8
+ */
9
+ export declare function inlineRefs<T extends object>(obj: unknown, openAPIObject: OpenAPIObject): T | null;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Resolves $ref references at the first level only (except for components/schemas references)
3
+ * For arrays, checks each item at the first level
4
+ * @param obj - The object to process (may contain $ref properties)
5
+ * @param openAPIObject - The complete OpenAPI document containing definitions
6
+ * @returns The object with resolved references (except components/schemas)
7
+ */
8
+ export function inlineRefs(obj, openAPIObject) {
9
+ // Handle null or undefined
10
+ if (obj === null || obj === undefined) {
11
+ return null;
12
+ }
13
+ // Handle arrays - check each item for $ref at first level only
14
+ if (Array.isArray(obj)) {
15
+ return obj.map((item) => {
16
+ // Only resolve if item is an object with $ref
17
+ if (item && typeof item === 'object' && '$ref' in item && typeof item.$ref === 'string') {
18
+ // Skip components/schemas references
19
+ if (item.$ref.startsWith('#/components/schemas/')) {
20
+ return item;
21
+ }
22
+ // Resolve the reference
23
+ const resolved = resolveRef(item.$ref, openAPIObject);
24
+ // If resolution successful, return resolved value (with any additional properties merged)
25
+ if (resolved !== undefined) {
26
+ // If there are additional properties besides $ref, merge them
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ const { $ref: _$ref, ...additionalProps } = item;
29
+ if (Object.keys(additionalProps).length > 0) {
30
+ return { ...resolved, ...additionalProps };
31
+ }
32
+ return resolved;
33
+ }
34
+ }
35
+ // Return item as-is if not a resolvable $ref
36
+ return item;
37
+ });
38
+ }
39
+ // Handle non-objects (primitives)
40
+ if (typeof obj !== 'object') {
41
+ return obj;
42
+ }
43
+ // Check if object has a $ref property at the first level
44
+ if ('$ref' in obj && typeof obj.$ref === 'string') {
45
+ // Check if the reference points to components/schemas
46
+ if (obj.$ref.startsWith('#/components/schemas/')) {
47
+ // Return as-is for schema references
48
+ return obj;
49
+ }
50
+ // Resolve the reference
51
+ const resolved = resolveRef(obj.$ref, openAPIObject);
52
+ // If resolution successful, return resolved value (with any additional properties merged)
53
+ if (resolved !== undefined) {
54
+ // If there are additional properties besides $ref, merge them
55
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
56
+ const { $ref: _$ref, ...additionalProps } = obj;
57
+ if (Object.keys(additionalProps).length > 0) {
58
+ return { ...resolved, ...additionalProps };
59
+ }
60
+ return resolved;
61
+ }
62
+ }
63
+ // For regular objects without $ref, return as-is (no recursion)
64
+ return obj;
65
+ }
66
+ /**
67
+ * Resolves a JSON Reference ($ref) to its target value
68
+ * @param ref - The reference string (e.g., "#/components/parameters/id")
69
+ * @param openAPIObject - The complete OpenAPI document
70
+ * @returns The resolved value or undefined if not found
71
+ */
72
+ function resolveRef(ref, openAPIObject) {
73
+ // Handle only local references (starting with #)
74
+ if (!ref.startsWith('#/')) {
75
+ // eslint-disable-next-line no-console
76
+ console.warn(`External references are not supported: ${ref}`);
77
+ return undefined;
78
+ }
79
+ // Remove the leading # and split the path
80
+ const path = ref
81
+ .substring(1)
82
+ .split('/')
83
+ .filter((p) => p !== '');
84
+ // Navigate through the object following the path
85
+ let current = openAPIObject;
86
+ for (const segment of path) {
87
+ // Decode the segment (handles encoded characters like ~0 for ~ and ~1 for /)
88
+ const decodedSegment = segment.replace(/~1/g, '/').replace(/~0/g, '~');
89
+ if (current && typeof current === 'object' && decodedSegment in current) {
90
+ current = current[decodedSegment];
91
+ }
92
+ else {
93
+ // eslint-disable-next-line no-console
94
+ console.warn(`Could not resolve reference: ${ref}`);
95
+ return undefined;
96
+ }
97
+ }
98
+ return current;
99
+ }
@@ -0,0 +1,7 @@
1
+ import type { ComponentsObject } from 'openapi3-ts/oas31';
2
+ /**
3
+ * Shrinks a `components.schemas` dict to the transitive `$ref` closure of `roots`
4
+ * (BFS with a visited set — component graphs of large specs like Stripe are cyclic).
5
+ * Preserves the original key order for deterministic output.
6
+ */
7
+ export declare function pruneComponentsSchemas(roots: unknown, componentsSchemas: NonNullable<ComponentsObject['schemas']>): NonNullable<ComponentsObject['schemas']>;
@@ -0,0 +1,51 @@
1
+ // Collect the trailing name of every `$ref` in the tree. Covers both pointer styles
2
+ // the transform emits: `#/components/schemas/X` (response slots, raw operation objects)
3
+ // and `#/$defs/X` (request slots embed components under their original names).
4
+ function collectRefNames(node, into) {
5
+ if (!node || typeof node !== 'object')
6
+ return;
7
+ if (Array.isArray(node)) {
8
+ for (const item of node) {
9
+ collectRefNames(item, into);
10
+ }
11
+ return;
12
+ }
13
+ for (const [key, value] of Object.entries(node)) {
14
+ if (key === '$ref' && typeof value === 'string') {
15
+ const name = value.split('/').pop();
16
+ if (name)
17
+ into.add(name);
18
+ }
19
+ else {
20
+ collectRefNames(value, into);
21
+ }
22
+ }
23
+ }
24
+ /**
25
+ * Shrinks a `components.schemas` dict to the transitive `$ref` closure of `roots`
26
+ * (BFS with a visited set — component graphs of large specs like Stripe are cyclic).
27
+ * Preserves the original key order for deterministic output.
28
+ */
29
+ export function pruneComponentsSchemas(roots, componentsSchemas) {
30
+ const required = new Set();
31
+ collectRefNames(roots, required);
32
+ const queue = [...required];
33
+ const visited = new Set();
34
+ while (queue.length) {
35
+ const name = queue.pop();
36
+ if (!name || visited.has(name))
37
+ continue;
38
+ visited.add(name);
39
+ const component = componentsSchemas[name];
40
+ if (!component)
41
+ continue;
42
+ const refs = new Set();
43
+ collectRefNames(component, refs);
44
+ for (const ref of refs) {
45
+ required.add(ref);
46
+ if (!visited.has(ref))
47
+ queue.push(ref);
48
+ }
49
+ }
50
+ return Object.fromEntries(Object.entries(componentsSchemas).filter(([name]) => required.has(name)));
51
+ }
@@ -1,4 +1,4 @@
1
- import { createDecorator } from '../core/createDecorator.js';
1
+ import { createDecorator } from '../core/create-decorator.js';
2
2
  import { error } from './error.js';
3
3
  import { tool } from './tool.js';
4
4
  export const operationDecorator = createDecorator(null, (openAPIOperationObject = {}) => {
@@ -1,4 +1,4 @@
1
- import { createDecorator } from '../core/createDecorator.js';
1
+ import { createDecorator } from '../core/create-decorator.js';
2
2
  export const tool = createDecorator(null, (toolOptions) => {
3
3
  return (handlerSchema) => {
4
4
  return {
@@ -0,0 +1,21 @@
1
+ import type { OpenAPIObject } from 'openapi3-ts/oas31';
2
+ import type { VovkOutputConfig, VovkStrictConfig, VovkReadmeConfig, VovkSamplesConfig, VovkPackageJson } from '../types/config.js';
3
+ import type { VovkSchema } from '../types/core.js';
4
+ export declare function vovkSchemaToOpenAPI({ config, rootEntry, schema: fullSchema, outputConfigs, forceOutputConfigs, isBundle, segmentName: givenSegmentName, projectPackageJson, }: {
5
+ config: VovkStrictConfig | undefined;
6
+ rootEntry?: string;
7
+ schema: VovkSchema;
8
+ outputConfigs: VovkOutputConfig[];
9
+ forceOutputConfigs?: VovkOutputConfig[];
10
+ isBundle: boolean;
11
+ segmentName: string | null;
12
+ projectPackageJson: VovkPackageJson | undefined;
13
+ }): {
14
+ readme: VovkReadmeConfig;
15
+ openAPIObject: OpenAPIObject;
16
+ samples: VovkSamplesConfig;
17
+ origin: string;
18
+ package: VovkPackageJson;
19
+ imports: VovkOutputConfig['imports'];
20
+ reExports: VovkOutputConfig['reExports'];
21
+ };
@@ -0,0 +1,250 @@
1
+ import { createCodeSamples } from '../samples/create-code-samples.js';
2
+ import { schemaToObject } from '../samples/schema-to-object.js';
3
+ import { resolveGeneratorConfigValues } from '../core/resolve-generator-config-values.js';
4
+ import { HttpStatus } from '../types/enums.js';
5
+ function extractComponents(schema) {
6
+ if (!schema)
7
+ return [undefined, {}];
8
+ const components = {};
9
+ // Function to collect components and replace $refs recursively
10
+ const process = (obj, path = []) => {
11
+ if (!obj || typeof obj !== 'object')
12
+ return obj;
13
+ // Handle arrays
14
+ if (Array.isArray(obj)) {
15
+ return obj.map((item) => process(item, path));
16
+ }
17
+ // Create a copy to modify
18
+ const result = {};
19
+ Object.entries({ ...obj.definitions, ...obj.$defs }).forEach(([key, value]) => {
20
+ components[key] = process(value, [...path, key]);
21
+ });
22
+ // Process all properties
23
+ for (const [key, value] of Object.entries(obj ?? {})) {
24
+ // Skip already processed special properties
25
+ if (key === '$defs' || key === 'definitions')
26
+ continue;
27
+ if (key === '$ref' && typeof value === 'string') {
28
+ // Extract the component name from the reference
29
+ const refParts = value.split('/');
30
+ const refName = refParts[refParts.length - 1];
31
+ // Replace with component reference
32
+ result[key] = `#/components/schemas/${refName}`;
33
+ }
34
+ else {
35
+ // Recursively process other properties
36
+ result[key] = process(value, [...path, key]);
37
+ }
38
+ }
39
+ return result;
40
+ };
41
+ const processedSchema = process(schema);
42
+ return [processedSchema, components];
43
+ }
44
+ // returns OpenAPIObject along with resolved configs
45
+ // TODO: Refactor and decompose
46
+ export function vovkSchemaToOpenAPI({ config, rootEntry = 'api', schema: fullSchema, outputConfigs, forceOutputConfigs, isBundle, segmentName: givenSegmentName, projectPackageJson, }) {
47
+ const paths = {};
48
+ const components = {};
49
+ const { openAPIObject, samples: samplesConfig, package: packageJson, readme: readmeConfig, origin, imports, reExports, } = resolveGeneratorConfigValues({
50
+ config,
51
+ outputConfigs,
52
+ forceOutputConfigs,
53
+ isBundle,
54
+ segmentName: givenSegmentName ?? null,
55
+ projectPackageJson,
56
+ });
57
+ for (const [segmentName, segmentSchema] of givenSegmentName
58
+ ? [[givenSegmentName, fullSchema.segments[givenSegmentName]]]
59
+ : Object.entries(fullSchema.segments ?? {})) {
60
+ for (const c of Object.values(segmentSchema.controllers)) {
61
+ for (const [handlerName, h] of Object.entries(c.handlers ?? {})) {
62
+ if (h.operationObject && !h.misc?.isOpenAPIMixin) {
63
+ const [queryValidation, queryComponents] = extractComponents(h?.validation?.query);
64
+ const [bodyValidation, bodyComponents] = extractComponents(h?.validation?.body);
65
+ const [paramsValidation, paramsComponents] = extractComponents(h?.validation?.params);
66
+ const [outputValidation, outputComponents] = extractComponents(h?.validation?.output);
67
+ const [iterationValidation, iterationComponents] = extractComponents(h?.validation?.iteration);
68
+ // TODO: Handle name conflicts?
69
+ Object.assign(components, queryComponents, bodyComponents, paramsComponents, outputComponents, iterationComponents);
70
+ const { ts, rs, py } = createCodeSamples({
71
+ package: packageJson,
72
+ handlerName,
73
+ handlerSchema: h,
74
+ controllerSchema: c,
75
+ config: samplesConfig,
76
+ });
77
+ const queryParameters = queryValidation && 'type' in queryValidation && 'properties' in queryValidation
78
+ ? Object.entries(queryValidation.properties ?? {}).map(([propName, propSchema]) => ({
79
+ name: propName,
80
+ in: 'query',
81
+ required: queryValidation.required ? queryValidation.required.includes(propName) : false,
82
+ schema: propSchema,
83
+ }))
84
+ : null;
85
+ const pathParameters = paramsValidation && 'type' in paramsValidation && 'properties' in paramsValidation
86
+ ? Object.entries(paramsValidation.properties ?? {}).map(([propName, propSchema]) => ({
87
+ name: propName,
88
+ in: 'path',
89
+ required: paramsValidation.required ? paramsValidation.required.includes(propName) : false,
90
+ schema: propSchema,
91
+ }))
92
+ : null;
93
+ const path = h.misc?.originalPath ??
94
+ `/${[rootEntry.replace(/^\/+|\/+$/g, ''), segmentName, c.prefix, h.path].filter(Boolean).join('/')}`;
95
+ paths[path] = paths[path] ?? {};
96
+ const httpMethod = h.httpMethod.toLowerCase();
97
+ paths[path][httpMethod] ??= {};
98
+ paths[path][httpMethod] = {
99
+ ...h.operationObject,
100
+ ...paths[path][httpMethod],
101
+ 'x-codeSamples': [
102
+ ...(paths[path][httpMethod]['x-codeSamples'] ?? []),
103
+ ...(h.operationObject?.['x-codeSamples'] ?? []),
104
+ {
105
+ label: 'TypeScript RPC',
106
+ lang: 'typescript',
107
+ source: ts,
108
+ },
109
+ {
110
+ label: 'Python RPC',
111
+ lang: 'python',
112
+ source: py,
113
+ },
114
+ {
115
+ label: 'Rust RPC',
116
+ lang: 'rust',
117
+ source: rs,
118
+ },
119
+ ],
120
+ ...(queryParameters || pathParameters
121
+ ? {
122
+ parameters: h.operationObject?.parameters ?? [...(queryParameters || []), ...(pathParameters || [])],
123
+ }
124
+ : {}),
125
+ ...(paths[path][httpMethod].parameters
126
+ ? {
127
+ parameters: paths[path][httpMethod].parameters,
128
+ }
129
+ : {}),
130
+ ...(outputValidation && 'type' in outputValidation
131
+ ? {
132
+ responses: {
133
+ 200: {
134
+ description: 'description' in outputValidation ? outputValidation.description : 'Success',
135
+ content: {
136
+ 'application/json': {
137
+ schema: outputValidation,
138
+ },
139
+ },
140
+ },
141
+ ...h.operationObject?.responses,
142
+ },
143
+ }
144
+ : {}),
145
+ ...(iterationValidation && 'type' in iterationValidation
146
+ ? {
147
+ responses: {
148
+ 200: {
149
+ description: 'description' in iterationValidation ? iterationValidation.description : 'JSON Lines response',
150
+ content: {
151
+ 'application/jsonl': {
152
+ schema: {
153
+ ...iterationValidation,
154
+ examples: iterationValidation.examples ?? [
155
+ [
156
+ JSON.stringify(schemaToObject(iterationValidation)),
157
+ JSON.stringify(schemaToObject(iterationValidation)),
158
+ JSON.stringify(schemaToObject(iterationValidation)),
159
+ ].join('\n'),
160
+ ],
161
+ },
162
+ },
163
+ },
164
+ },
165
+ ...h.operationObject?.responses,
166
+ },
167
+ }
168
+ : {}),
169
+ ...(paths[path][httpMethod].responses
170
+ ? {
171
+ responses: paths[path][httpMethod].responses,
172
+ }
173
+ : {}),
174
+ ...(bodyValidation && 'type' in bodyValidation
175
+ ? {
176
+ requestBody: h.operationObject?.requestBody ?? {
177
+ description: 'description' in bodyValidation ? bodyValidation.description : 'Request body',
178
+ required: true,
179
+ content: bodyValidation['x-contentType']?.length
180
+ ? Object.fromEntries(bodyValidation['x-contentType'].map((ct) => [ct, { schema: bodyValidation }]))
181
+ : {
182
+ 'application/json': {
183
+ schema: bodyValidation,
184
+ },
185
+ },
186
+ },
187
+ }
188
+ : {}),
189
+ ...(paths[path][httpMethod].requestBody
190
+ ? {
191
+ requestBody: paths[path][httpMethod].requestBody,
192
+ }
193
+ : {}),
194
+ tags: paths[path][httpMethod].tags ?? h.operationObject?.tags,
195
+ };
196
+ }
197
+ }
198
+ }
199
+ }
200
+ return {
201
+ readme: readmeConfig,
202
+ samples: samplesConfig,
203
+ package: packageJson,
204
+ imports,
205
+ reExports,
206
+ origin,
207
+ openAPIObject: {
208
+ ...openAPIObject,
209
+ components: {
210
+ ...openAPIObject?.components,
211
+ schemas: {
212
+ ...(openAPIObject?.components?.schemas ?? components),
213
+ HttpStatus: {
214
+ type: 'integer',
215
+ description: 'HTTP status code',
216
+ enum: Object.keys(HttpStatus)
217
+ .map((k) => HttpStatus[k])
218
+ .filter(Boolean)
219
+ .filter((v) => typeof v === 'number'),
220
+ },
221
+ VovkErrorResponse: {
222
+ type: 'object',
223
+ description: 'Vovk error response',
224
+ properties: {
225
+ cause: {
226
+ description: 'Error cause of any shape',
227
+ },
228
+ statusCode: {
229
+ $ref: '#/components/schemas/HttpStatus',
230
+ },
231
+ message: {
232
+ type: 'string',
233
+ description: 'Error message',
234
+ },
235
+ isError: {
236
+ type: 'boolean',
237
+ const: true,
238
+ description: 'Indicates that this object represents an error',
239
+ },
240
+ },
241
+ required: ['statusCode', 'message', 'isError'],
242
+ additionalProperties: false,
243
+ },
244
+ ...openAPIObject?.components?.schemas,
245
+ },
246
+ },
247
+ paths,
248
+ },
249
+ };
250
+ }
@@ -0,0 +1 @@
1
+ export declare function bufferBody<T extends Request>(req: T): Promise<T>;
@@ -0,0 +1,30 @@
1
+ export async function bufferBody(req) {
2
+ const buffer = await req.arrayBuffer();
3
+ Object.defineProperty(req, 'bodyUsed', {
4
+ get: () => false,
5
+ configurable: true,
6
+ });
7
+ Object.defineProperty(req, 'body', {
8
+ get: () => new ReadableStream({
9
+ start(controller) {
10
+ controller.enqueue(new Uint8Array(buffer.slice(0)));
11
+ controller.close();
12
+ },
13
+ }),
14
+ configurable: true,
15
+ });
16
+ req.json = async () => JSON.parse(new TextDecoder().decode(buffer));
17
+ req.text = async () => new TextDecoder().decode(buffer);
18
+ req.blob = async () => new Blob([buffer.slice(0)]);
19
+ req.arrayBuffer = async () => buffer.slice(0);
20
+ req.bytes = async () => new Uint8Array(buffer.slice(0));
21
+ req.formData = async () => {
22
+ const r = new Request('http://localhost', {
23
+ method: req.method,
24
+ headers: req.headers,
25
+ body: buffer.slice(0),
26
+ });
27
+ return r.formData();
28
+ };
29
+ return req;
30
+ }