vovk-rust 0.0.1-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,529 @@
1
+ // Helper function for indentation
2
+ export function indent(level, pad = 0) {
3
+ return ' '.repeat(pad + level * 2);
4
+ }
5
+ // Generate documentation comments from title and description
6
+ export function generateDocComment(schema, level, pad = 0) {
7
+ if (!schema?.title && !schema?.description)
8
+ return '';
9
+ let comment = '';
10
+ if (schema.title) {
11
+ comment += `${indent(level, pad)}/// ${schema.title}\n`;
12
+ if (schema.description) {
13
+ comment += `${indent(level, pad)}///\n`;
14
+ }
15
+ }
16
+ if (schema.description) {
17
+ // Split description into lines and add /// to each line
18
+ const lines = schema.description.split('\n');
19
+ for (const line of lines) {
20
+ comment += `${indent(level, pad)}/// ${line}\n`;
21
+ }
22
+ }
23
+ return comment;
24
+ }
25
+ // Track processed $refs to avoid circular references
26
+ const processedRefs = new Set();
27
+ // Resolve $ref paths in the schema
28
+ export function resolveRef(ref, rootSchema) {
29
+ if (processedRefs.has(ref)) {
30
+ // Circular reference detected, return a placeholder
31
+ return { type: 'string' };
32
+ }
33
+ processedRefs.add(ref);
34
+ // Format: #/$defs/TypeName or #/components/schemas/TypeName or #/definitions/TypeName etc.
35
+ const parts = ref.split('/').filter((part) => part && part !== '#');
36
+ // Standard path traversal
37
+ let current = rootSchema;
38
+ for (const part of parts) {
39
+ if (!current || typeof current !== 'object') {
40
+ // If standard traversal fails and the path might be a definition reference
41
+ if (parts.includes('definitions') && rootSchema.definitions) {
42
+ // Try to access the definition directly using the last part as the key
43
+ const definitionKey = parts[parts.length - 1];
44
+ return rootSchema.definitions[definitionKey];
45
+ }
46
+ return undefined;
47
+ }
48
+ current = current[part];
49
+ }
50
+ // If the resolved schema also has a $ref, resolve it recursively
51
+ if (current && current.$ref) {
52
+ return resolveRef(current.$ref, rootSchema);
53
+ }
54
+ processedRefs.delete(ref);
55
+ return current;
56
+ }
57
+ // Generate module path for nested types
58
+ export function getModulePath(path) {
59
+ if (path.length <= 1)
60
+ return path[0];
61
+ let result = '';
62
+ for (let i = 0; i < path.length - 1; i++) {
63
+ result += path[i] + '_::';
64
+ }
65
+ result += path[path.length - 1];
66
+ return result;
67
+ }
68
+ // Convert JSON Schema type to Rust type
69
+ export function toRustType(schema, path, rootSchema = schema) {
70
+ if (!schema)
71
+ return 'String';
72
+ // Handle $ref first
73
+ if (schema.$ref) {
74
+ const resolvedSchema = resolveRef(schema.$ref, rootSchema);
75
+ if (resolvedSchema) {
76
+ // Extract type name from ref path for better type naming
77
+ const refName = schema.$ref.split('/').pop();
78
+ // Use the ref name directly if it's a definition reference
79
+ if (schema.$ref.includes('definitions/') || schema.$ref.includes('$defs/')) {
80
+ return refName || 'String';
81
+ }
82
+ return toRustType(resolvedSchema, refName ? [...path.slice(0, -1), refName] : path, rootSchema);
83
+ }
84
+ return 'serde_json::Value'; // Fallback for unresolved $ref
85
+ }
86
+ // Check for enum without type (assume string)
87
+ if (schema.enum) {
88
+ if (schema.type === 'string' || !schema.type) {
89
+ return `${getModulePath(path)}`;
90
+ }
91
+ }
92
+ if (schema.type === 'string') {
93
+ return 'String';
94
+ }
95
+ else if (schema.type === 'number' || schema.type === 'integer') {
96
+ // Handle numeric types with constraints
97
+ if (schema.type === 'integer') {
98
+ // Determine integer type based on min/max constraints
99
+ const min = typeof schema.minimum === 'number'
100
+ ? schema.minimum
101
+ : typeof schema.exclusiveMinimum === 'number'
102
+ ? schema.exclusiveMinimum + 1
103
+ : undefined;
104
+ const max = typeof schema.maximum === 'number'
105
+ ? schema.maximum
106
+ : typeof schema.exclusiveMaximum === 'number'
107
+ ? schema.exclusiveMaximum - 1
108
+ : undefined;
109
+ // Check if we need unsigned (no negative values)
110
+ if (min !== undefined && min >= 0) {
111
+ // Choose appropriate unsigned int size
112
+ if (max !== undefined) {
113
+ if (max <= 255)
114
+ return 'u8';
115
+ if (max <= 65535)
116
+ return 'u16';
117
+ if (max <= 4294967295)
118
+ return 'u32';
119
+ }
120
+ return 'u64'; // Default unsigned
121
+ }
122
+ else {
123
+ // Choose appropriate signed int size
124
+ if (min !== undefined && max !== undefined) {
125
+ const absMin = Math.abs(min);
126
+ const absMax = Math.abs(max);
127
+ const maxVal = Math.max(absMin - 1, absMax);
128
+ if (maxVal <= 127)
129
+ return 'i8';
130
+ if (maxVal <= 32767)
131
+ return 'i16';
132
+ if (maxVal <= 2147483647)
133
+ return 'i32';
134
+ }
135
+ return 'i64'; // Default signed
136
+ }
137
+ }
138
+ else {
139
+ // Floating point
140
+ const hasLowRange = (schema.minimum !== undefined &&
141
+ schema.maximum !== undefined &&
142
+ Math.abs(schema.maximum - schema.minimum) <= 3.4e38) ||
143
+ (schema.exclusiveMinimum !== undefined &&
144
+ schema.exclusiveMaximum !== undefined &&
145
+ Math.abs(schema.exclusiveMaximum - schema.exclusiveMinimum) <= 3.4e38);
146
+ return hasLowRange ? 'f32' : 'f64';
147
+ }
148
+ }
149
+ else if (schema.type === 'boolean') {
150
+ return 'bool';
151
+ }
152
+ else if (schema.type === 'null') {
153
+ return '()';
154
+ }
155
+ else if (schema.type === 'array') {
156
+ if (schema.items) {
157
+ // Check if array items are objects that need special handling
158
+ if (schema.items.type === 'object' || schema.items.properties || schema.items.$ref) {
159
+ // For array of objects, reference the item type with proper module path
160
+ // Find the parent module name to reference the item
161
+ const parentName = path[path.length - 2] || path[0];
162
+ return `Vec<${parentName}_::${path[path.length - 1]}Item>`;
163
+ }
164
+ const itemType = toRustType(schema.items, [...path, '_item'], rootSchema);
165
+ return `Vec<${itemType}>`;
166
+ }
167
+ return 'Vec<String>';
168
+ }
169
+ else if (schema.type === 'object' || schema.properties) {
170
+ // Handle empty objects
171
+ if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
172
+ return 'serde_json::Value';
173
+ }
174
+ return path[path.length - 1];
175
+ }
176
+ else if (schema.anyOf || schema.oneOf || schema.allOf) {
177
+ return `${getModulePath(path)}`;
178
+ }
179
+ return 'String'; // Default fallback
180
+ }
181
+ // Generate enum for string with enum values
182
+ export function generateEnum(schema, name, level, pad = 0) {
183
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
184
+ let code = '';
185
+ // Add documentation comments for the enum
186
+ code += generateDocComment(schema, level, pad);
187
+ code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
188
+ code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
189
+ code += `${indentFn(level)}pub enum ${name} {\n`;
190
+ schema.enum?.forEach((value) => {
191
+ // Create valid Rust enum variant
192
+ const variant = value?.replace(/[^a-zA-Z0-9_]/g, '_');
193
+ code += `${indentFn(level + 1)}#[serde(rename = "${value}")]\n`;
194
+ code += `${indentFn(level + 1)}${variant},\n`;
195
+ });
196
+ code += `${indentFn(level)}}\n\n`;
197
+ return code;
198
+ }
199
+ // Generate enum for anyOf/oneOf/allOf schemas
200
+ export function generateVariantEnum(schema, name, path, level, rootSchema, pad = 0) {
201
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
202
+ // Handle allOf separately - it should combine schemas rather than create variants
203
+ if (schema.allOf) {
204
+ return generateAllOfType(schema.allOf, name, path, level, rootSchema, pad);
205
+ }
206
+ const variants = schema.anyOf || schema.oneOf || [];
207
+ let code = '';
208
+ let nestedTypes = '';
209
+ // Add documentation comments for the enum
210
+ code += generateDocComment(schema, level, pad);
211
+ code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
212
+ code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
213
+ code += `${indentFn(level)}#[serde(untagged)]\n`;
214
+ code += `${indentFn(level)}pub enum ${name} {\n`;
215
+ variants.forEach((variant, index) => {
216
+ // Resolve $ref if present
217
+ if (variant.$ref) {
218
+ const resolved = resolveRef(variant.$ref, rootSchema);
219
+ if (resolved) {
220
+ variant = resolved;
221
+ }
222
+ }
223
+ const variantName = `Variant${index}`;
224
+ const variantPath = [...path, name, variantName];
225
+ // If it's an object type, we need to create a separate struct
226
+ if (variant.type === 'object' || variant.properties) {
227
+ code += `${indentFn(level + 1)}${variantName}(${name}_::${variantName}),\n`;
228
+ // Create a nested type definition to be added outside the enum
229
+ nestedTypes += processObject(variant, variantPath, level, rootSchema, pad);
230
+ }
231
+ else {
232
+ // For simple types, we can include them directly in the enum
233
+ const variantType = toRustType(variant, variantPath, rootSchema);
234
+ code += `${indentFn(level + 1)}${variantName}(${variantType}),\n`;
235
+ }
236
+ });
237
+ code += `${indentFn(level)}}\n\n`;
238
+ // Add nested type definitions if needed
239
+ if (nestedTypes) {
240
+ code += nestedTypes;
241
+ }
242
+ return code;
243
+ }
244
+ // Handle allOf schema by merging properties
245
+ export function generateAllOfType(schemas, name, path, level, rootSchema, pad = 0) {
246
+ const mergedSchema = {
247
+ type: 'object',
248
+ properties: {},
249
+ required: [],
250
+ };
251
+ // Merge all schemas in allOf
252
+ schemas.forEach((schema) => {
253
+ // Resolve $ref if present
254
+ if (schema.$ref) {
255
+ const resolved = resolveRef(schema.$ref, rootSchema);
256
+ if (resolved) {
257
+ schema = resolved;
258
+ }
259
+ }
260
+ if (schema.properties) {
261
+ mergedSchema.properties = {
262
+ ...mergedSchema.properties,
263
+ ...schema.properties,
264
+ };
265
+ }
266
+ if (schema.required) {
267
+ mergedSchema.required = [...(mergedSchema.required ?? []), ...schema.required];
268
+ }
269
+ });
270
+ // Process the merged schema as a regular object
271
+ return processObject(mergedSchema, [...path, name], level, rootSchema, pad);
272
+ }
273
+ // Process schema objects and generate Rust code
274
+ export function processObject(schema, path, level, rootSchema = schema, pad = 0) {
275
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
276
+ if (!schema) {
277
+ return '';
278
+ }
279
+ // Handle empty objects
280
+ if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
281
+ // Empty object is handled as serde_json::Value at the field level
282
+ return '';
283
+ }
284
+ if (!schema.properties) {
285
+ return '';
286
+ }
287
+ const currentName = path[path.length - 1];
288
+ let code = '';
289
+ // Add documentation comments for the struct
290
+ code += generateDocComment(schema, level, pad);
291
+ if (schema.type === 'object' && schema['x-isForm'] === true) {
292
+ code += `${indentFn(level)}pub use reqwest::multipart::Form as ${currentName};\n`;
293
+ return code;
294
+ }
295
+ // Generate struct
296
+ code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
297
+ code += `${indentFn(level)}#[allow(non_snake_case, non_camel_case_types)]\n`;
298
+ code += `${indentFn(level)}pub struct ${currentName} {\n`;
299
+ // Generate struct fields
300
+ Object.entries(schema.properties).forEach(([propName, propSchema]) => {
301
+ const isRequired = schema.required && schema.required.includes(propName);
302
+ // Handle $ref in property
303
+ if (propSchema.$ref) {
304
+ const resolvedSchema = resolveRef(propSchema.$ref, rootSchema);
305
+ if (resolvedSchema) {
306
+ propSchema = resolvedSchema;
307
+ }
308
+ }
309
+ // Add documentation comments for the field
310
+ code += generateDocComment(propSchema, level + 1, pad);
311
+ // Determine if this property is a nested type that should be accessed via module path
312
+ const isNestedObject = propSchema.type === 'object' || propSchema.properties;
313
+ const isGenericObject = propSchema.type === 'object' && !propSchema.properties;
314
+ // Define nested enum types
315
+ const isNestedEnum = ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) ||
316
+ propSchema.anyOf ||
317
+ propSchema.oneOf ||
318
+ propSchema.allOf;
319
+ const propPath = [...path, propName];
320
+ let propType;
321
+ if (isGenericObject) {
322
+ // For generic objects, we can use serde_json::Value
323
+ propType = 'serde_json::Value';
324
+ }
325
+ else if (isNestedObject || isNestedEnum) {
326
+ // For nested objects and enums, we need to reference them via their module path
327
+ propType = `${currentName}_::${propName}`;
328
+ // Special case for enums which have a different naming convention
329
+ if (isNestedEnum) {
330
+ propType += ''; // 'Enum';
331
+ }
332
+ }
333
+ else {
334
+ // For other types, use the standard type resolution
335
+ propType = toRustType(propSchema, propPath, rootSchema);
336
+ }
337
+ if (!isRequired) {
338
+ propType = `Option<${propType}>`;
339
+ }
340
+ code += `${indentFn(level + 1)}pub ${propName}: ${propType},\n`;
341
+ });
342
+ code += `${indentFn(level)}}\n\n`;
343
+ // Check if any properties require nested types before generating the sub-module
344
+ const hasNestedTypes = Object.entries(schema.properties).some(([, propSchema]) => {
345
+ // Resolve $ref if present
346
+ if (propSchema.$ref) {
347
+ const resolved = resolveRef(propSchema.$ref, rootSchema);
348
+ propSchema = resolved || propSchema;
349
+ }
350
+ return (propSchema.type === 'object' ||
351
+ propSchema.properties ||
352
+ ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) ||
353
+ (propSchema.type === 'array' &&
354
+ propSchema.items &&
355
+ (propSchema.items.type === 'object' || propSchema.items.properties || propSchema.items.$ref)) ||
356
+ propSchema.anyOf ||
357
+ propSchema.oneOf ||
358
+ propSchema.allOf);
359
+ });
360
+ // Only generate sub-modules if there are nested types
361
+ if (hasNestedTypes && Object.keys(schema.properties).length > 0) {
362
+ code += `${indentFn(level)}#[allow(non_snake_case)]\n`;
363
+ code += `${indentFn(level)}pub mod ${currentName}_ {\n`;
364
+ code += `${indentFn(level + 1)}use serde::{Serialize, Deserialize};\n\n`;
365
+ Object.entries(schema.properties).forEach(([propName, propSchema]) => {
366
+ // Resolve $ref if present
367
+ if (propSchema.$ref) {
368
+ const resolved = resolveRef(propSchema.$ref, rootSchema);
369
+ if (resolved) {
370
+ propSchema = resolved;
371
+ }
372
+ }
373
+ // Generate nested object types
374
+ if (propSchema.type === 'object' || propSchema.properties) {
375
+ code += processObject(propSchema, [...path, propName], level + 1, rootSchema, pad);
376
+ }
377
+ // Generate enum types for string enums (also when type is missing but enum exists)
378
+ else if ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) {
379
+ code += generateEnum(propSchema, propName, level + 1, pad);
380
+ }
381
+ // Generate types for array items if they're objects
382
+ else if (propSchema.type === 'array' && propSchema.items) {
383
+ // Check if items has a $ref
384
+ if (propSchema.items.$ref) {
385
+ const resolved = resolveRef(propSchema.items.$ref, rootSchema);
386
+ if (resolved && (resolved.type === 'object' || resolved.properties)) {
387
+ code += processObject(resolved, [...path, propName + 'Item'], level + 1, rootSchema, pad);
388
+ }
389
+ }
390
+ else if (propSchema.items.type === 'object' || propSchema.items.properties) {
391
+ code += processObject(propSchema.items, [...path, propName + 'Item'], level + 1, rootSchema, pad);
392
+ }
393
+ }
394
+ // Handle anyOf/oneOf/allOf schemas
395
+ else if (propSchema.anyOf || propSchema.oneOf || propSchema.allOf) {
396
+ code += generateVariantEnum(propSchema, propName, path, level + 1, rootSchema, pad);
397
+ }
398
+ });
399
+ code += `${indentFn(level)}}\n`;
400
+ }
401
+ return code;
402
+ }
403
+ // Generate code for primitive types
404
+ export function processPrimitive(schema, name, level, pad = 0) {
405
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
406
+ let code = '';
407
+ // Add documentation comments
408
+ code += generateDocComment(schema, level, pad);
409
+ // For primitive types, create a type alias
410
+ code += `${indentFn(level)}pub type ${name} = `;
411
+ if (schema.type === 'string') {
412
+ code += 'String';
413
+ }
414
+ else if (schema.type === 'number') {
415
+ code += 'f64';
416
+ }
417
+ else if (schema.type === 'integer') {
418
+ code += 'i64';
419
+ }
420
+ else if (schema.type === 'boolean') {
421
+ code += 'bool';
422
+ }
423
+ else if (schema.type === 'null') {
424
+ code += '()';
425
+ }
426
+ else if (schema.enum) {
427
+ // If it has enum values, we'll generate an actual enum instead of a type alias
428
+ return generateEnum(schema, name, level, pad);
429
+ }
430
+ else {
431
+ code += 'String'; // Default fallback
432
+ }
433
+ code += ';\n\n';
434
+ return code;
435
+ }
436
+ export function convertJSONSchemasToRustTypes({ schemas, pad = 0, rootName, }) {
437
+ // Check if all schemas are undefined
438
+ const hasDefinedSchemas = Object.values(schemas).some((schema) => schema !== undefined);
439
+ if (!hasDefinedSchemas) {
440
+ return '';
441
+ }
442
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
443
+ // Start code generation
444
+ let result = `${indentFn(0)}pub mod ${rootName}_ {\n`;
445
+ result += `${indentFn(1)}use serde::{Serialize, Deserialize};\n`;
446
+ // Process each schema in the schemas object
447
+ Object.entries(schemas).forEach(([schemaName, schemaObj]) => {
448
+ // Skip undefined schemas
449
+ if (!schemaObj)
450
+ return;
451
+ // Extract and process types from $defs if present
452
+ if (schemaObj.$defs) {
453
+ Object.entries(schemaObj.$defs).forEach(([defName, defSchema]) => {
454
+ // Create a root object for each definition
455
+ if (defSchema) {
456
+ if (defSchema.type === 'object' || defSchema.properties) {
457
+ const rootDefObject = {
458
+ type: 'object',
459
+ properties: defSchema.properties || {},
460
+ required: defSchema.required || [],
461
+ title: defSchema.title,
462
+ description: defSchema.description,
463
+ 'x-isForm': defSchema['x-isForm'],
464
+ };
465
+ result += processObject(rootDefObject, [defName], 1, schemaObj, pad);
466
+ }
467
+ else if (defSchema.type === 'string' && defSchema.enum) {
468
+ result += generateEnum(defSchema, defName, 1, pad);
469
+ }
470
+ else if (defSchema.anyOf || defSchema.oneOf || defSchema.allOf) {
471
+ result += generateVariantEnum(defSchema, defName, [defName], 1, schemaObj, pad);
472
+ }
473
+ else if (defSchema.type && ['string', 'number', 'integer', 'boolean', 'null'].includes(defSchema.type)) {
474
+ // Handle primitive types in $defs
475
+ result += processPrimitive(defSchema, defName, 1, pad);
476
+ }
477
+ }
478
+ });
479
+ }
480
+ // Handle the schema based on its type
481
+ if (schemaObj.type === 'object' || schemaObj.properties) {
482
+ // Create a root object for object schema
483
+ const rootObject = {
484
+ type: 'object',
485
+ properties: schemaObj.properties || {},
486
+ required: schemaObj.required || [],
487
+ title: schemaObj.title,
488
+ description: schemaObj.description,
489
+ 'x-isForm': schemaObj['x-isForm'],
490
+ };
491
+ result += processObject(rootObject, [schemaName], 1, schemaObj, pad);
492
+ }
493
+ else if (['string', 'number', 'integer', 'boolean', 'null'].includes(schemaObj.type)) {
494
+ // Handle primitive schema
495
+ result += processPrimitive(schemaObj, schemaName, 1, pad);
496
+ }
497
+ else if (schemaObj.enum) {
498
+ // Handle enum schema
499
+ result += generateEnum(schemaObj, schemaName, 1, pad);
500
+ }
501
+ else if (schemaObj.anyOf || schemaObj.oneOf || schemaObj.allOf) {
502
+ // Handle variant schema
503
+ result += generateVariantEnum(schemaObj, schemaName, [schemaName], 1, schemaObj, pad);
504
+ }
505
+ else if (schemaObj.type === 'array') {
506
+ // For array as root type, create a type alias to Vec<ItemType>
507
+ let itemType = 'String'; // Default if no items specified
508
+ if (schemaObj.items) {
509
+ if (schemaObj.items.type === 'object' || schemaObj.items.properties) {
510
+ // Create the item type
511
+ const itemSchema = {
512
+ ...schemaObj.items,
513
+ title: schemaObj.items.title || `${schemaName}Item`,
514
+ description: schemaObj.items.description || `Item of ${schemaName} array`,
515
+ };
516
+ result += processObject(itemSchema, [`${schemaName}Item`], 1, schemaObj, pad);
517
+ itemType = `${schemaName}Item`;
518
+ }
519
+ else {
520
+ // For primitive array items
521
+ itemType = toRustType(schemaObj.items, [`${schemaName}Item`], schemaObj);
522
+ }
523
+ }
524
+ result += `${indentFn(1)}pub type ${schemaName} = Vec<${itemType}>;\n\n`;
525
+ }
526
+ });
527
+ result += `${indentFn(0)}}\n`;
528
+ return result;
529
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "vovk-rust",
3
+ "version": "0.0.1-beta.10",
4
+ "description": "Codegen templates for Rust client library for Vovk.ts",
5
+ "files": [
6
+ "client-templates",
7
+ "index.js",
8
+ "index.d.ts"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "npm-publish": "npm publish",
13
+ "tsc": "tsc --noEmit",
14
+ "ncu": "npm-check-updates -u",
15
+ "pre-test": "npm run build",
16
+ "test-only": "npm run pre-test && node --experimental-strip-types --test --test-only test/test/**/*.mts",
17
+ "unit": "npm run pre-test && node --experimental-strip-types --test --test-concurrency=1 test/*.mts",
18
+ "unit:rs": "RUST_BACKTRACE=full cargo test --manifest-path ./test_rust/Cargo.toml -- --show-output",
19
+ "unit:ts": "npm run pre-test && node --experimental-strip-types --test --test-concurrency=1 test_ts/*.mts",
20
+ "generate": "npm run generate --prefix ../../test -- --config=../packages/vovk-rust/vovk.config.test.mjs",
21
+ "test": "npm run unit:ts && PORT=3210 npm run generate && npm run build --prefix ../../test && PORT=3210 concurrently 'sleep 15 && npm run unit:rs' 'npm run start --prefix ../../test' --kill-others --success first"
22
+ },
23
+ "main": "./index.js",
24
+ "type": "module",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/finom/vovk.git"
28
+ },
29
+ "keywords": [
30
+ "vovk",
31
+ "rust",
32
+ "codegen"
33
+ ],
34
+ "author": "Andrey Gubanov",
35
+ "license": "MIT",
36
+ "bugs": {
37
+ "url": "https://github.com/finom/vovk/issues"
38
+ },
39
+ "homepage": "https://vovk.dev/rust"
40
+ }