vovk 3.0.0-draft.99 → 3.0.2

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 (168) hide show
  1. package/README.md +23 -12
  2. package/bin/index.mjs +10 -0
  3. package/dist/client/createRPC.d.ts +13 -3
  4. package/dist/client/createRPC.js +112 -50
  5. package/dist/client/defaultHandler.d.ts +5 -1
  6. package/dist/client/defaultHandler.js +12 -9
  7. package/dist/client/defaultStreamHandler.d.ts +16 -4
  8. package/dist/client/defaultStreamHandler.js +262 -62
  9. package/dist/client/fetcher.d.ts +41 -3
  10. package/dist/client/fetcher.js +125 -60
  11. package/dist/client/progressive.d.ts +15 -0
  12. package/dist/client/progressive.js +56 -0
  13. package/dist/{utils → client}/serializeQuery.d.ts +2 -2
  14. package/dist/{utils → client}/serializeQuery.js +2 -5
  15. package/dist/core/HttpException.d.ts +16 -0
  16. package/dist/core/HttpException.js +26 -0
  17. package/dist/core/JSONLinesResponder.d.ts +42 -0
  18. package/dist/core/JSONLinesResponder.js +94 -0
  19. package/dist/core/controllersToStaticParams.d.ts +13 -0
  20. package/dist/core/controllersToStaticParams.js +32 -0
  21. package/dist/core/createDecorator.d.ts +18 -0
  22. package/dist/core/createDecorator.js +60 -0
  23. package/dist/core/decorators.d.ts +66 -0
  24. package/dist/core/decorators.js +155 -0
  25. package/dist/core/getSchema.d.ts +21 -0
  26. package/dist/core/getSchema.js +31 -0
  27. package/dist/core/initSegment.d.ts +33 -0
  28. package/dist/core/initSegment.js +35 -0
  29. package/dist/core/multitenant.d.ts +33 -0
  30. package/dist/core/multitenant.js +132 -0
  31. package/dist/core/resolveGeneratorConfigValues.d.ts +19 -0
  32. package/dist/core/resolveGeneratorConfigValues.js +59 -0
  33. package/dist/{utils → core}/setHandlerSchema.d.ts +2 -2
  34. package/dist/{utils → core}/setHandlerSchema.js +1 -4
  35. package/dist/core/toDownloadResponse.d.ts +11 -0
  36. package/dist/core/toDownloadResponse.js +25 -0
  37. package/dist/core/vovkApp.d.ts +36 -0
  38. package/dist/core/vovkApp.js +318 -0
  39. package/dist/index.d.ts +25 -59
  40. package/dist/index.js +23 -23
  41. package/dist/internal.d.ts +17 -0
  42. package/dist/internal.js +10 -0
  43. package/dist/openapi/error.d.ts +6 -0
  44. package/dist/openapi/error.js +97 -0
  45. package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.d.ts +3 -0
  46. package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.js +65 -0
  47. package/dist/openapi/openAPIToVovkSchema/index.d.ts +5 -0
  48. package/dist/openapi/openAPIToVovkSchema/index.js +153 -0
  49. package/dist/openapi/openAPIToVovkSchema/inlineRefs.d.ts +9 -0
  50. package/dist/openapi/openAPIToVovkSchema/inlineRefs.js +99 -0
  51. package/dist/openapi/operation.d.ts +26 -0
  52. package/dist/openapi/operation.js +19 -0
  53. package/dist/openapi/tool.d.ts +6 -0
  54. package/dist/openapi/tool.js +12 -0
  55. package/dist/openapi/vovkSchemaToOpenAPI.d.ts +21 -0
  56. package/dist/openapi/vovkSchemaToOpenAPI.js +250 -0
  57. package/dist/req/bufferBody.d.ts +1 -0
  58. package/dist/req/bufferBody.js +30 -0
  59. package/dist/req/parseBody.d.ts +4 -0
  60. package/dist/req/parseBody.js +49 -0
  61. package/dist/req/parseForm.d.ts +1 -0
  62. package/dist/req/parseForm.js +24 -0
  63. package/dist/{utils → req}/parseQuery.d.ts +1 -2
  64. package/dist/{utils → req}/parseQuery.js +12 -12
  65. package/dist/req/reqMeta.d.ts +2 -0
  66. package/dist/{utils → req}/reqMeta.js +1 -4
  67. package/dist/req/reqQuery.d.ts +2 -0
  68. package/dist/req/reqQuery.js +4 -0
  69. package/dist/req/validateContentType.d.ts +1 -0
  70. package/dist/req/validateContentType.js +32 -0
  71. package/dist/samples/createCodeSamples.d.ts +20 -0
  72. package/dist/samples/createCodeSamples.js +293 -0
  73. package/dist/samples/objectToCode.d.ts +8 -0
  74. package/dist/samples/objectToCode.js +38 -0
  75. package/dist/samples/schemaToCode.d.ts +11 -0
  76. package/dist/samples/schemaToCode.js +264 -0
  77. package/dist/samples/schemaToObject.d.ts +2 -0
  78. package/dist/samples/schemaToObject.js +164 -0
  79. package/dist/samples/schemaToTsType.d.ts +2 -0
  80. package/dist/samples/schemaToTsType.js +114 -0
  81. package/dist/tools/ToModelOutput.d.ts +8 -0
  82. package/dist/tools/ToModelOutput.js +10 -0
  83. package/dist/tools/createTool.d.ts +126 -0
  84. package/dist/tools/createTool.js +6 -0
  85. package/dist/tools/createToolFactory.d.ts +135 -0
  86. package/dist/tools/createToolFactory.js +62 -0
  87. package/dist/tools/deriveTools.d.ts +46 -0
  88. package/dist/tools/deriveTools.js +132 -0
  89. package/dist/tools/toModelOutputDefault.d.ts +7 -0
  90. package/dist/tools/toModelOutputDefault.js +7 -0
  91. package/dist/tools/toModelOutputMCP.d.ts +30 -0
  92. package/dist/tools/toModelOutputMCP.js +54 -0
  93. package/dist/tsconfig.tsbuildinfo +1 -0
  94. package/dist/types/client.d.ts +139 -0
  95. package/dist/types/client.js +1 -0
  96. package/dist/types/config.d.ts +151 -0
  97. package/dist/types/config.js +1 -0
  98. package/dist/types/core.d.ts +115 -0
  99. package/dist/types/core.js +1 -0
  100. package/dist/types/enums.d.ts +75 -0
  101. package/dist/{types.js → types/enums.js} +21 -9
  102. package/dist/types/inference.d.ts +117 -0
  103. package/dist/types/inference.js +1 -0
  104. package/dist/types/json-schema.d.ts +51 -0
  105. package/dist/types/json-schema.js +1 -0
  106. package/dist/types/operation.d.ts +5 -0
  107. package/dist/types/operation.js +1 -0
  108. package/dist/types/package.d.ts +544 -0
  109. package/dist/types/package.js +5 -0
  110. package/dist/types/request.d.ts +48 -0
  111. package/dist/types/request.js +1 -0
  112. package/dist/types/standard-schema.d.ts +117 -0
  113. package/dist/types/standard-schema.js +6 -0
  114. package/dist/types/tools.d.ts +43 -0
  115. package/dist/types/tools.js +1 -0
  116. package/dist/types/utils.d.ts +9 -0
  117. package/dist/types/utils.js +1 -0
  118. package/dist/types/validation.d.ts +48 -0
  119. package/dist/types/validation.js +1 -0
  120. package/dist/utils/camelCase.d.ts +6 -0
  121. package/dist/utils/camelCase.js +34 -0
  122. package/dist/utils/deepExtend.d.ts +54 -0
  123. package/dist/utils/deepExtend.js +127 -0
  124. package/dist/utils/fileNameToDisposition.d.ts +1 -0
  125. package/dist/utils/fileNameToDisposition.js +3 -0
  126. package/dist/utils/shim.d.ts +1 -0
  127. package/dist/utils/shim.js +1 -1
  128. package/dist/utils/toKebabCase.d.ts +1 -0
  129. package/dist/utils/toKebabCase.js +5 -0
  130. package/dist/utils/trimPath.d.ts +1 -0
  131. package/dist/utils/trimPath.js +1 -0
  132. package/dist/utils/upperFirst.d.ts +1 -0
  133. package/dist/utils/upperFirst.js +3 -0
  134. package/dist/validation/createStandardValidation.d.ts +268 -0
  135. package/dist/validation/createStandardValidation.js +45 -0
  136. package/dist/validation/createValidateOnClient.d.ts +14 -0
  137. package/dist/validation/createValidateOnClient.js +23 -0
  138. package/dist/validation/procedure.d.ts +261 -0
  139. package/dist/validation/procedure.js +8 -0
  140. package/dist/validation/withValidationLibrary.d.ts +119 -0
  141. package/dist/validation/withValidationLibrary.js +174 -0
  142. package/package.json +45 -11
  143. package/dist/HttpException.d.ts +0 -7
  144. package/dist/HttpException.js +0 -15
  145. package/dist/StreamJSONResponse.d.ts +0 -14
  146. package/dist/StreamJSONResponse.js +0 -57
  147. package/dist/VovkApp.d.ts +0 -29
  148. package/dist/VovkApp.js +0 -188
  149. package/dist/client/index.d.ts +0 -3
  150. package/dist/client/index.js +0 -7
  151. package/dist/client/types.d.ts +0 -104
  152. package/dist/client/types.js +0 -2
  153. package/dist/createDecorator.d.ts +0 -6
  154. package/dist/createDecorator.js +0 -43
  155. package/dist/createVovkApp.d.ts +0 -62
  156. package/dist/createVovkApp.js +0 -118
  157. package/dist/types.d.ts +0 -220
  158. package/dist/utils/generateStaticAPI.d.ts +0 -4
  159. package/dist/utils/generateStaticAPI.js +0 -18
  160. package/dist/utils/getSchema.d.ts +0 -20
  161. package/dist/utils/getSchema.js +0 -33
  162. package/dist/utils/reqForm.d.ts +0 -2
  163. package/dist/utils/reqForm.js +0 -13
  164. package/dist/utils/reqMeta.d.ts +0 -2
  165. package/dist/utils/reqQuery.d.ts +0 -2
  166. package/dist/utils/reqQuery.js +0 -10
  167. package/dist/utils/withValidation.d.ts +0 -20
  168. package/dist/utils/withValidation.js +0 -72
@@ -0,0 +1,293 @@
1
+ import { schemaToCode, getSampleValue } from './schemaToCode.js';
2
+ import { objectToCode } from './objectToCode.js';
3
+ const toSnakeCase = (str) => str
4
+ .replace(/-/g, '_') // Replace hyphens with underscores
5
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // Add underscore between lowercase/digit and uppercase
6
+ .replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1_$2') // Add underscore between uppercase letters if the second one is followed by a lowercase
7
+ .toLowerCase()
8
+ .replace(/^_/, ''); // Remove leading underscore
9
+ const getIndentSpaces = (level) => ' '.repeat(level);
10
+ function isTextFormat(mimeType) {
11
+ if (!mimeType)
12
+ return false;
13
+ return (mimeType.startsWith('text/') ||
14
+ [
15
+ 'application/json',
16
+ 'application/ld+json',
17
+ 'application/xml',
18
+ 'application/xhtml+xml',
19
+ 'application/javascript',
20
+ 'application/typescript',
21
+ 'application/yaml',
22
+ 'application/x-yaml',
23
+ 'application/toml',
24
+ 'application/sql',
25
+ 'application/graphql',
26
+ 'application/x-www-form-urlencoded',
27
+ ].includes(mimeType) ||
28
+ mimeType.endsWith('+json') ||
29
+ mimeType.endsWith('+xml'));
30
+ }
31
+ const isForm = (schema) => {
32
+ const contentTypes = schema['x-contentType'] ?? [];
33
+ return contentTypes.some((ct) => ct === 'multipart/form-data' || ct === 'application/x-www-form-urlencoded') ?? false;
34
+ };
35
+ function generateTypeScriptCode({ handlerName, rpcName, packageName, queryValidation, bodyValidation, paramsValidation, outputValidation, iterationValidation, hasArg, config, }) {
36
+ const getTsSample = (schema, indent) => schemaToCode(schema, { stripQuotes: true, indent: indent ?? 4 });
37
+ const getTsFormSample = (schema) => {
38
+ let formSample = '\nconst formData = new FormData();';
39
+ for (const [key, prop] of Object.entries(schema.properties || {})) {
40
+ const target = prop.oneOf?.[0] || prop.anyOf?.[0] || prop.allOf?.[0] || prop;
41
+ const desc = target.description ?? prop.description ?? undefined;
42
+ if (target.type === 'array' && target.items && typeof target.items !== 'boolean') {
43
+ formSample += getTsFormAppend(target.items, key, desc);
44
+ formSample += getTsFormAppend(target.items, key, desc);
45
+ }
46
+ else {
47
+ formSample += getTsFormAppend(target, key, desc);
48
+ }
49
+ }
50
+ return formSample;
51
+ };
52
+ const getTsFormAppend = (schema, key, description) => {
53
+ let sampleValue;
54
+ if (schema.type === 'string' && schema.format === 'binary') {
55
+ sampleValue = `new Blob(${isTextFormat(schema.contentMediaType) ? '["text_content"]' : '[binary_data]'}${schema.contentMediaType ? `, { type: "${schema.contentMediaType}" }` : ''})`;
56
+ }
57
+ else if (schema.type === 'object') {
58
+ sampleValue = '"object_unknown"';
59
+ }
60
+ else {
61
+ sampleValue = `"${getSampleValue(schema)}"`;
62
+ }
63
+ const desc = schema.description ?? description;
64
+ return `\n${desc ? `// ${desc}\n` : ''}formData.append("${key}", ${sampleValue});`;
65
+ };
66
+ const tsArgs = hasArg
67
+ ? `{
68
+ ${[
69
+ bodyValidation ? ` body: ${isForm(bodyValidation) ? 'formData' : getTsSample(bodyValidation)},` : null,
70
+ queryValidation ? ` query: ${getTsSample(queryValidation)},` : null,
71
+ paramsValidation ? ` params: ${getTsSample(paramsValidation)},` : null,
72
+ config?.apiRoot ? ` apiRoot: '${config.apiRoot}',` : null,
73
+ config?.headers
74
+ ? ` init: {
75
+ headers: ${objectToCode(config.headers, { stripQuotes: true, indent: 6, nestingIndent: 4 })}
76
+ },`
77
+ : null,
78
+ ]
79
+ .filter(Boolean)
80
+ .join('\n')}
81
+ }`
82
+ : '';
83
+ const TS_CODE = `import { ${rpcName} } from '${packageName}';
84
+ ${bodyValidation && isForm(bodyValidation) ? `${getTsFormSample(bodyValidation)}\n` : ''}
85
+ ${iterationValidation ? 'using' : 'const'} response = await ${rpcName}.${handlerName}(${tsArgs});
86
+ ${outputValidation
87
+ ? `
88
+ console.log(response);
89
+ /*
90
+ ${getTsSample(outputValidation, 0)}
91
+ */`
92
+ : ''}${iterationValidation
93
+ ? `
94
+ for await (const item of response) {
95
+ console.log(item);
96
+ /*
97
+ ${getTsSample(iterationValidation)}
98
+ */
99
+ }`
100
+ : ''}`;
101
+ return TS_CODE.trim();
102
+ }
103
+ function generatePythonCode({ handlerName, rpcName, packageName, queryValidation, bodyValidation, paramsValidation, outputValidation, iterationValidation, hasArg, config, }) {
104
+ const getPySample = (schema, indent) => schemaToCode(schema, {
105
+ stripQuotes: false,
106
+ indent: indent ?? 4,
107
+ comment: '#',
108
+ ignoreBinary: true,
109
+ nestingIndent: 4,
110
+ });
111
+ const handlerNameSnake = toSnakeCase(handlerName);
112
+ const getFileTouple = (schema) => {
113
+ return `('name.ext', BytesIO(${isTextFormat(schema.contentMediaType) ? '"text_content".encode("utf-8")' : 'binary_data'})${schema.contentMediaType ? `, "${schema.contentMediaType}"` : ''})`;
114
+ };
115
+ const getPyFiles = (schema) => {
116
+ return Object.entries(schema.properties ?? {}).reduce((acc, [key, prop]) => {
117
+ const target = prop.oneOf?.[0] || prop.anyOf?.[0] || prop.allOf?.[0] || prop;
118
+ const desc = target.description ?? prop.description ?? undefined;
119
+ if (target.type === 'string' && target.format === 'binary') {
120
+ acc.push(`${desc ? `${getIndentSpaces(8)}# ${desc}\n` : ''}${getIndentSpaces(8)}('${key}', ${getFileTouple(target)})`);
121
+ }
122
+ else if (target.type === 'array' &&
123
+ target.items &&
124
+ typeof target.items !== 'boolean' &&
125
+ target.items.format === 'binary') {
126
+ const val = `${desc ? `${getIndentSpaces(8)}# ${desc}\n` : ''}${getIndentSpaces(8)}('${key}', ${getFileTouple(target.items)})`;
127
+ acc.push(val, val);
128
+ }
129
+ return acc;
130
+ }, []);
131
+ };
132
+ const pyFiles = bodyValidation ? getPyFiles(bodyValidation) : null;
133
+ const pyFilesArg = pyFiles?.length
134
+ ? `${getIndentSpaces(4)}files=[\n${pyFiles.join(',\n')}\n${getIndentSpaces(4)}],`
135
+ : null;
136
+ const PY_CODE = `from ${packageName} import ${rpcName}
137
+ ${bodyValidation && isForm(bodyValidation) ? 'from io import BytesIO\n' : ''}
138
+ response = ${rpcName}.${handlerNameSnake}(${hasArg
139
+ ? '\n' +
140
+ [
141
+ bodyValidation ? ` body=${getPySample(bodyValidation)},` : null,
142
+ pyFilesArg,
143
+ queryValidation ? ` query=${getPySample(queryValidation)},` : null,
144
+ paramsValidation ? ` params=${getPySample(paramsValidation)},` : null,
145
+ config?.apiRoot ? ` api_root="${config.apiRoot}",` : null,
146
+ config?.headers
147
+ ? ` headers=${objectToCode(config.headers, { stripQuotes: false, indent: 4, nestingIndent: 4 })},`
148
+ : null,
149
+ ]
150
+ .filter(Boolean)
151
+ .join('\n') +
152
+ '\n'
153
+ : ''})
154
+
155
+ ${outputValidation ? `print(response)\n${getPySample(outputValidation, 0)}` : ''}${iterationValidation
156
+ ? `for i, item in enumerate(response):
157
+ print(f"iteration #{i}:\\n {item}")
158
+ # iteration #0:
159
+ ${getPySample(iterationValidation)}`
160
+ : ''}`;
161
+ return PY_CODE.trim();
162
+ }
163
+ function generateRustCode({ handlerName, rpcName, packageName, queryValidation, bodyValidation, paramsValidation, outputValidation, iterationValidation, config, }) {
164
+ const getRsJSONSample = (schema, indent) => schemaToCode(schema, { stripQuotes: false, indent: indent ?? 4 });
165
+ const getRsOutputSample = (schema, indent) => schemaToCode(schema, { stripQuotes: true, indent: indent ?? 4 });
166
+ const getRsFormSample = (schema) => {
167
+ let formSample = 'let form = reqwest::multipart::Form::new()';
168
+ for (const [key, prop] of Object.entries(schema.properties || {})) {
169
+ const target = prop.oneOf?.[0] || prop.anyOf?.[0] || prop.allOf?.[0] || prop;
170
+ const desc = target.description ?? prop.description ?? undefined;
171
+ if (target.type === 'array' && target.items && typeof target.items !== 'boolean') {
172
+ formSample += getRsFormPart(target.items, key, desc);
173
+ formSample += getRsFormPart(target.items, key, desc);
174
+ }
175
+ else {
176
+ formSample += getRsFormPart(target, key, desc);
177
+ }
178
+ }
179
+ return formSample;
180
+ };
181
+ const getRsFormPart = (schema, key, description) => {
182
+ let sampleValue;
183
+ if (schema.type === 'string' && schema.format === 'binary') {
184
+ sampleValue = isTextFormat(schema.contentMediaType)
185
+ ? 'reqwest::multipart::Part::text("text_content")'
186
+ : 'reqwest::multipart::Part::bytes(binary_data)';
187
+ if (schema.contentMediaType) {
188
+ sampleValue += `.mime_str("${schema.contentMediaType}").unwrap()`;
189
+ }
190
+ }
191
+ else if (schema.type === 'object') {
192
+ sampleValue = '"object_unknown"';
193
+ }
194
+ else {
195
+ sampleValue = `"${getSampleValue(schema)}"`;
196
+ }
197
+ const desc = schema.description ?? description;
198
+ return `\n${getIndentSpaces(4)}${desc ? `// ${desc}\n` : ''}${getIndentSpaces(4)}.part("${key}", ${sampleValue});`;
199
+ };
200
+ const getHashMapSample = (map, indent = 4) => {
201
+ const entries = Object.entries(map)
202
+ .map(([key, value]) => {
203
+ return `${getIndentSpaces(indent + 2)}("${key}".to_string(), "${value}".to_string())`;
204
+ })
205
+ .join(',\n');
206
+ return `Some(&HashMap::from([\n${entries}\n${getIndentSpaces(4)}]))`;
207
+ };
208
+ const getBody = (schema) => {
209
+ if (isForm(schema)) {
210
+ return 'form';
211
+ }
212
+ return serdeUnwrap(getRsJSONSample(schema));
213
+ };
214
+ const handlerNameSnake = toSnakeCase(handlerName);
215
+ const rpcNameSnake = toSnakeCase(rpcName);
216
+ const serdeUnwrap = (fake) => `from_value(json!(${fake})).unwrap()`;
217
+ const RS_CODE = `use ${packageName}::${rpcNameSnake};
218
+ use serde_json::{
219
+ from_value,
220
+ json
221
+ };
222
+ ${iterationValidation ? 'use futures_util::StreamExt;\n' : ''}${bodyValidation && isForm(bodyValidation) ? `use reqwest::multipart;\n` : ''}#[tokio::main]
223
+ async fn main() {${bodyValidation && isForm(bodyValidation) ? `\n ${getRsFormSample(bodyValidation)}\n` : ''}
224
+ let response = ${rpcNameSnake}::${handlerNameSnake}(
225
+ ${bodyValidation ? getBody(bodyValidation) : '()'}, /* body */
226
+ ${queryValidation ? serdeUnwrap(getRsJSONSample(queryValidation)) : '()'}, /* query */
227
+ ${paramsValidation ? serdeUnwrap(getRsJSONSample(paramsValidation)) : '()'}, /* params */
228
+ ${config?.headers ? `${getHashMapSample(config.headers)}, /* headers */` : 'None, /* headers (HashMap) */ '}
229
+ ${config?.apiRoot ? `Some("${config.apiRoot}"), /* api_root */` : 'None, /* api_root */'}
230
+ false, /* disable_client_validation */
231
+ ).await;${outputValidation
232
+ ? `\n\nmatch response {
233
+ Ok(output) => println!("{:?}", output),
234
+ /*
235
+ output ${getRsOutputSample(outputValidation)}
236
+ */
237
+ Err(e) => println!("error: {:?}", e),
238
+ }`
239
+ : ''}${iterationValidation
240
+ ? `\n\nmatch response {
241
+ Ok(mut stream) => {
242
+ let mut i = 0;
243
+ while let Some(item) = stream.next().await {
244
+ match item {
245
+ Ok(value) => {
246
+ println!("#{}: {:?}", i, value);
247
+ /*
248
+ #0: iteration ${getRsOutputSample(iterationValidation, 8)}
249
+ */
250
+ i += 1;
251
+ }
252
+ Err(e) => {
253
+ eprintln!("stream error: {:?}", e);
254
+ break;
255
+ }
256
+ }
257
+ }
258
+ },
259
+ Err(e) => println!("Error initiating stream: {:?}", e),
260
+ }`
261
+ : ''}
262
+ }`;
263
+ return RS_CODE.trim();
264
+ }
265
+ export function createCodeSamples({ handlerName, handlerSchema, controllerSchema, package: packageJson, config, }) {
266
+ const queryValidation = handlerSchema?.validation?.query;
267
+ const bodyValidation = handlerSchema?.validation?.body;
268
+ const paramsValidation = handlerSchema?.validation?.params;
269
+ const outputValidation = handlerSchema?.validation?.output;
270
+ const iterationValidation = handlerSchema?.validation?.iteration;
271
+ const hasArg = !!queryValidation || !!bodyValidation || !!paramsValidation || !!config?.apiRoot || !!config?.headers;
272
+ const rpcName = controllerSchema.rpcModuleName;
273
+ const packageName = packageJson?.name || 'vovk-client';
274
+ const packageNameSnake = toSnakeCase(packageName);
275
+ const pyPackageName = packageJson?.py_name ?? packageNameSnake;
276
+ const rsPackageName = packageJson?.rs_name ?? packageNameSnake;
277
+ const commonParams = {
278
+ handlerName,
279
+ rpcName,
280
+ packageName,
281
+ queryValidation,
282
+ bodyValidation,
283
+ paramsValidation,
284
+ outputValidation,
285
+ iterationValidation,
286
+ hasArg,
287
+ config,
288
+ };
289
+ const ts = generateTypeScriptCode(commonParams);
290
+ const py = generatePythonCode({ ...commonParams, packageName: pyPackageName });
291
+ const rs = generateRustCode({ ...commonParams, packageName: rsPackageName });
292
+ return { ts, py, rs };
293
+ }
@@ -0,0 +1,8 @@
1
+ interface SamplerOptions {
2
+ stripQuotes?: boolean;
3
+ indent?: number;
4
+ nestingIndent?: number;
5
+ quote?: '"' | "'";
6
+ }
7
+ export declare function objectToCode(obj: unknown, options?: SamplerOptions): string;
8
+ export {};
@@ -0,0 +1,38 @@
1
+ export function objectToCode(obj, options) {
2
+ const { stripQuotes = false, indent = 0, nestingIndent = 2, quote = '"' } = options || {};
3
+ // Use JSON.stringify with the nesting indent
4
+ let result = JSON.stringify(obj, null, nestingIndent);
5
+ // Replace double quotes with single quotes for string values if requested
6
+ if (quote === "'") {
7
+ // First, escape any existing single quotes in string values
8
+ result = result.replace(/"([^"]*)"/g, (match, content) => {
9
+ // Check if this is a key (followed by colon) or a value
10
+ const matchIndex = result.indexOf(match);
11
+ const afterMatch = result.substring(matchIndex + match.length);
12
+ if (afterMatch.startsWith(':')) {
13
+ // This is a key, keep it for now
14
+ return match;
15
+ }
16
+ // This is a value, replace quotes and escape single quotes
17
+ const escaped = content.replace(/'/g, "\\'");
18
+ return `'${escaped}'`;
19
+ });
20
+ }
21
+ // Strip quotes from keys if requested
22
+ if (stripQuotes) {
23
+ // Remove quotes from keys that are valid JavaScript identifiers
24
+ // Keep quotes for keys with special characters (like 'x-foo', spaces, etc.)
25
+ const keyQuote = quote === "'" ? "'" : '"';
26
+ const pattern = new RegExp(`${keyQuote}([a-zA-Z_$][a-zA-Z0-9_$]*)${keyQuote}:`, 'g');
27
+ result = result.replace(pattern, '$1:');
28
+ }
29
+ // Apply base indentation if specified
30
+ if (indent > 0) {
31
+ const indentStr = ' '.repeat(indent);
32
+ result = result
33
+ .split('\n')
34
+ .map((line, i) => (i === 0 ? line : indentStr + line))
35
+ .join('\n');
36
+ }
37
+ return result;
38
+ }
@@ -0,0 +1,11 @@
1
+ import type { VovkJSONSchemaBase } from '../types/json-schema.js';
2
+ interface SamplerOptions {
3
+ comment?: '//' | '#';
4
+ stripQuotes?: boolean;
5
+ indent?: number;
6
+ nestingIndent?: number;
7
+ ignoreBinary?: boolean;
8
+ }
9
+ export declare function schemaToCode(schema: VovkJSONSchemaBase, options: SamplerOptions, rootSchema?: VovkJSONSchemaBase): string;
10
+ export declare function getSampleValue(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase, ignoreBinary?: boolean): unknown;
11
+ export {};
@@ -0,0 +1,264 @@
1
+ export function schemaToCode(schema, options, rootSchema) {
2
+ const { comment = '//', stripQuotes = false, indent = 0, nestingIndent = 4, ignoreBinary = false } = options;
3
+ if (!schema || typeof schema !== 'object')
4
+ return 'null';
5
+ // Use the input schema as the root if not provided
6
+ rootSchema = rootSchema || schema;
7
+ // Get the sample value
8
+ const sampleValue = getSampleValue(schema, rootSchema, ignoreBinary);
9
+ // Format the output with descriptions
10
+ return formatWithDescriptions(sampleValue, schema, rootSchema, comment, stripQuotes, indent, nestingIndent, ignoreBinary, true // isTopLevel
11
+ );
12
+ }
13
+ export function getSampleValue(schema, rootSchema, ignoreBinary) {
14
+ if (!schema || typeof schema !== 'object')
15
+ return null;
16
+ rootSchema = rootSchema || schema;
17
+ // Check if this is a binary string schema and should be ignored
18
+ if (ignoreBinary && schema.type === 'string' && schema.format === 'binary') {
19
+ return undefined;
20
+ }
21
+ // If there's an example, use it
22
+ if (schema.example !== undefined) {
23
+ return schema.example;
24
+ }
25
+ // If there are examples, use one of them
26
+ if (schema.examples && schema.examples.length > 0) {
27
+ return schema.examples[0];
28
+ }
29
+ // Handle const if present
30
+ if (schema.const !== undefined) {
31
+ return schema.const;
32
+ }
33
+ // Handle $ref if present
34
+ if (schema.$ref) {
35
+ return handleRef(schema.$ref, rootSchema, ignoreBinary);
36
+ }
37
+ // Handle enum if present
38
+ if (schema.enum && schema.enum.length > 0) {
39
+ return schema.enum[0];
40
+ }
41
+ // Handle oneOf, anyOf, allOf
42
+ if (schema.oneOf && schema.oneOf.length > 0) {
43
+ return getSampleValue(schema.oneOf[0], rootSchema, ignoreBinary);
44
+ }
45
+ if (schema.anyOf && schema.anyOf.length > 0) {
46
+ return getSampleValue(schema.anyOf[0], rootSchema, ignoreBinary);
47
+ }
48
+ if (schema.allOf && schema.allOf.length > 0) {
49
+ // Merge all schemas in allOf
50
+ const mergedSchema = schema.allOf.reduce((acc, s) => Object.assign(acc, s), {});
51
+ return getSampleValue(mergedSchema, rootSchema, ignoreBinary);
52
+ }
53
+ // Handle different types
54
+ if (schema.type) {
55
+ switch (schema.type) {
56
+ case 'string':
57
+ return handleString(schema);
58
+ case 'number':
59
+ case 'integer':
60
+ return handleNumber(schema);
61
+ case 'boolean':
62
+ return handleBoolean();
63
+ case 'object':
64
+ return handleObject(schema, rootSchema, ignoreBinary);
65
+ case 'array':
66
+ return handleArray(schema, rootSchema, ignoreBinary);
67
+ case 'null':
68
+ return null;
69
+ default:
70
+ return null;
71
+ }
72
+ }
73
+ // If type is not specified but properties are, treat it as an object
74
+ if (schema.properties) {
75
+ return handleObject(schema, rootSchema, ignoreBinary);
76
+ }
77
+ // Default fallback
78
+ return null;
79
+ }
80
+ function formatWithDescriptions(value, schema, rootSchema, comment, stripQuotes, indent, nestingIndent, ignoreBinary, isTopLevel) {
81
+ const indentStr = ' '.repeat(indent);
82
+ const nestIndentStr = ' '.repeat(nestingIndent); // Create nesting indent string
83
+ // Handle undefined (for ignored binary fields)
84
+ if (value === undefined) {
85
+ return '';
86
+ }
87
+ // Handle null
88
+ if (value === null) {
89
+ return 'null';
90
+ }
91
+ // Handle primitives
92
+ if (typeof value !== 'object' || value instanceof Date) {
93
+ return JSON.stringify(value);
94
+ }
95
+ // Handle arrays
96
+ if (Array.isArray(value)) {
97
+ if (value.length === 0)
98
+ return '[]';
99
+ const items = value.map((item) => {
100
+ const itemSchema = schema.items && typeof schema.items === 'object' ? schema.items : {};
101
+ const formattedItem = formatWithDescriptions(item, itemSchema, rootSchema, comment, stripQuotes, indent + nestingIndent, // Use nestingIndent instead of hardcoded 4
102
+ nestingIndent, ignoreBinary, false);
103
+ return `${indentStr}${nestIndentStr}${formattedItem}`; // Use nestIndentStr for item indentation
104
+ });
105
+ return `[\n${items.join(',\n')}\n${indentStr}]`;
106
+ }
107
+ // Handle objects
108
+ if (typeof value === 'object') {
109
+ const entries = Object.entries(value);
110
+ if (entries.length === 0)
111
+ return '{}';
112
+ const formattedEntries = [];
113
+ // Add top-level description for objects
114
+ if (isTopLevel && schema.type === 'object' && schema.description) {
115
+ const descLines = schema.description.split('\n');
116
+ formattedEntries.push(`${indentStr}${nestIndentStr}${comment} -----`);
117
+ descLines.forEach((line) => {
118
+ formattedEntries.push(`${indentStr}${nestIndentStr}${comment} ${line.trim()}`);
119
+ });
120
+ formattedEntries.push(`${indentStr}${nestIndentStr}${comment} -----`);
121
+ }
122
+ entries.forEach(([key, val], index) => {
123
+ const propSchema = schema.properties?.[key] ?? {};
124
+ // Handle $ref in property schema
125
+ let resolvedPropSchema = propSchema;
126
+ if (propSchema.$ref) {
127
+ resolvedPropSchema = resolveRef(propSchema.$ref, rootSchema);
128
+ }
129
+ // Add property description if it exists
130
+ if (resolvedPropSchema.description) {
131
+ const descLines = resolvedPropSchema.description.split('\n');
132
+ descLines.forEach((line) => {
133
+ formattedEntries.push(`${indentStr}${nestIndentStr}${comment} ${line.trim()}`);
134
+ });
135
+ }
136
+ // Format the key
137
+ const formattedKey = stripQuotes && /^[A-Za-z_$][0-9A-Za-z_$]*$/.test(key) ? key : JSON.stringify(key);
138
+ // Format the value
139
+ const formattedValue = formatWithDescriptions(val, resolvedPropSchema, rootSchema, comment, stripQuotes, indent + nestingIndent, nestingIndent, ignoreBinary, false);
140
+ formattedEntries.push(`${indentStr}${nestIndentStr}${formattedKey}: ${formattedValue}${index < entries.length - 1 ? ',' : ''}`);
141
+ });
142
+ return `{\n${formattedEntries.join('\n')}\n${indentStr}}`;
143
+ }
144
+ return JSON.stringify(value);
145
+ }
146
+ function resolveRef(ref, rootSchema) {
147
+ const path = ref.split('/').slice(1); // Remove the initial '#'
148
+ let current = rootSchema;
149
+ for (const segment of path) {
150
+ current = current[segment];
151
+ if (current === undefined) {
152
+ return {};
153
+ }
154
+ }
155
+ return current;
156
+ }
157
+ function handleRef(ref, rootSchema, ignoreBinary) {
158
+ const resolved = resolveRef(ref, rootSchema);
159
+ return getSampleValue(resolved, rootSchema, ignoreBinary);
160
+ }
161
+ function handleString(schema) {
162
+ if (schema.format) {
163
+ switch (schema.format) {
164
+ case 'email':
165
+ case 'idn-email':
166
+ return 'user@example.com';
167
+ case 'uri':
168
+ case 'url':
169
+ case 'iri':
170
+ return 'https://example.com';
171
+ case 'date':
172
+ return '2023-01-01';
173
+ case 'date-time':
174
+ return '2023-01-01T00:00:00Z';
175
+ case 'time':
176
+ return '12:00:00Z';
177
+ case 'duration':
178
+ return 'PT1H';
179
+ case 'uuid':
180
+ return '00000000-0000-0000-0000-000000000000';
181
+ case 'regex':
182
+ return '^[a-zA-Z0-9]+$';
183
+ case 'relative-json-pointer':
184
+ return '/some/relative/path';
185
+ case 'color':
186
+ return '#000000';
187
+ case 'hostname':
188
+ return 'example.com';
189
+ case 'zipcode':
190
+ return '12345';
191
+ case 'phone':
192
+ return '+123-456-7890';
193
+ case 'password':
194
+ return '******';
195
+ case 'binary':
196
+ return 'binary-data';
197
+ default:
198
+ return 'string';
199
+ }
200
+ }
201
+ if (schema.pattern) {
202
+ return 'pattern-string';
203
+ }
204
+ return 'string';
205
+ }
206
+ function handleNumber(schema) {
207
+ if (schema.minimum !== undefined && schema.maximum !== undefined) {
208
+ return schema.minimum;
209
+ }
210
+ else if (schema.minimum !== undefined) {
211
+ return schema.minimum;
212
+ }
213
+ else if (schema.maximum !== undefined) {
214
+ return schema.maximum;
215
+ }
216
+ return 0;
217
+ }
218
+ function handleBoolean() {
219
+ return true;
220
+ }
221
+ function handleObject(schema, rootSchema, ignoreBinary) {
222
+ const result = {};
223
+ if (schema.properties) {
224
+ const required = schema.required || [];
225
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
226
+ if (required.includes(key) || required.length === 0) {
227
+ const value = getSampleValue(propSchema, rootSchema, ignoreBinary);
228
+ // Only add the property if it's not undefined (which happens when ignoreBinary is true and it's a binary field)
229
+ if (value !== undefined) {
230
+ result[key] = value;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
236
+ const value = getSampleValue(schema.additionalProperties, rootSchema, ignoreBinary);
237
+ if (value !== undefined) {
238
+ result.additionalProp = value;
239
+ }
240
+ }
241
+ return result;
242
+ }
243
+ function handleArray(schema, rootSchema, ignoreBinary) {
244
+ if (schema.items) {
245
+ // If items is a boolean, return empty array (true means any items allowed, false means no items)
246
+ if (typeof schema.items === 'boolean') {
247
+ return schema.items ? [null] : [];
248
+ }
249
+ const itemSchema = schema.items;
250
+ // Check if the items are binary strings that should be ignored
251
+ if (ignoreBinary && itemSchema.type === 'string' && itemSchema.format === 'binary') {
252
+ return undefined;
253
+ }
254
+ const minItems = schema.minItems || 1;
255
+ const numItems = Math.min(minItems, 3);
256
+ const items = Array.from({ length: numItems }, () => getSampleValue(itemSchema, rootSchema, ignoreBinary)).filter((item) => item !== undefined); // Filter out undefined values from ignored binary items
257
+ // If all items were filtered out (e.g., all were binary), return undefined instead of empty array
258
+ if (items.length === 0 && numItems > 0) {
259
+ return undefined;
260
+ }
261
+ return items;
262
+ }
263
+ return [];
264
+ }
@@ -0,0 +1,2 @@
1
+ import type { VovkJSONSchemaBase } from '../types/json-schema.js';
2
+ export declare function schemaToObject(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase): unknown;