@vkontakte/api-schema-typescript-generator 0.16.0 → 0.17.1

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.
@@ -1 +1 @@
1
- .github/dependabot.yml @VKCOM/vk-sec
1
+ .github/ @VKCOM/vk-sec
@@ -10,14 +10,17 @@ on:
10
10
  jobs:
11
11
  publish:
12
12
  runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read # Recommended for actions/setup-node
15
+ id-token: write # Required for OIDC
13
16
  steps:
14
- - uses: actions/checkout@v3
17
+ - uses: actions/checkout@v5
15
18
  with:
16
19
  token: ${{ secrets.DEVTOOLS_GITHUB_TOKEN }}
17
20
 
18
- - uses: actions/setup-node@v3
21
+ - uses: actions/setup-node@v4
19
22
  with:
20
- node-version: 18
23
+ node-version-file: '.nvmrc'
21
24
  cache: 'yarn'
22
25
  always-auth: true
23
26
  registry-url: 'https://registry.npmjs.org'
@@ -43,5 +46,3 @@ jobs:
43
46
 
44
47
  - name: Publushing release
45
48
  run: yarn publish --non-interactive
46
- env:
47
- NODE_AUTH_TOKEN: ${{ secrets.NPMJS_PUBLISH_TOKEN }}
@@ -6,9 +6,9 @@ jobs:
6
6
  test:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v3
9
+ - uses: actions/checkout@v5
10
10
 
11
- - uses: actions/setup-node@v3
11
+ - uses: actions/setup-node@v4
12
12
  with:
13
13
  node-version: 18
14
14
  cache: 'yarn'
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v24.11.1
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 V Kontakte, LLC.
3
+ Copyright (c) 2020 VK LLC.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.parseArguments = void 0;
6
+ exports.parseArguments = parseArguments;
7
7
  const arg_1 = __importDefault(require("arg"));
8
8
  const utils_1 = require("./utils");
9
9
  function parseArguments() {
@@ -26,4 +26,3 @@ function parseArguments() {
26
26
  methods: (0, utils_1.trimArray)(args['--methods'] || []),
27
27
  };
28
28
  }
29
- exports.parseArguments = parseArguments;
package/dist/generator.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateImportsBlock = void 0;
3
+ exports.generateImportsBlock = generateImportsBlock;
4
4
  const constants_1 = require("./constants");
5
5
  const helpers_1 = require("./helpers");
6
6
  const types_1 = require("./types");
@@ -38,4 +38,3 @@ function generateImportsBlock(refs, section, type) {
38
38
  });
39
39
  return importLines.join(constants_1.newLineChar);
40
40
  }
41
- exports.generateImportsBlock = generateImportsBlock;
@@ -280,12 +280,12 @@ class APITypingsGenerator {
280
280
  }
281
281
  generateMethodParams(methodInfo) {
282
282
  const section = (0, helpers_1.getMethodSection)(methodInfo.name);
283
- const interfaceName = `${methodInfo.name} params`;
283
+ const interfaceName = (0, helpers_1.getInterfaceName)(`${methodInfo.name} params`);
284
284
  let imports = {};
285
285
  let codeBlocks = [];
286
286
  const codeBlock = new TypeCodeBlock_1.TypeCodeBlock({
287
287
  type: TypeCodeBlock_1.TypeScriptCodeTypes.Interface,
288
- interfaceName: (0, helpers_1.getInterfaceName)(interfaceName),
288
+ interfaceName: interfaceName,
289
289
  needExport: true,
290
290
  allowEmptyInterface: true,
291
291
  properties: [],
@@ -305,6 +305,7 @@ class APITypingsGenerator {
305
305
  });
306
306
  this.appendToFileMap(section, imports, [...codeBlocks, codeBlock]);
307
307
  this.generateObjectsFromImports(imports);
308
+ return interfaceName;
308
309
  }
309
310
  getResponseObjectRef(ref) {
310
311
  const objectName = (0, helpers_1.getObjectNameByRef)(ref);
@@ -404,7 +405,7 @@ class APITypingsGenerator {
404
405
  generateResponse(section, response) {
405
406
  const result = this.getResponseCodeBlock(response);
406
407
  if (!result) {
407
- return;
408
+ throw new Error('Unable to generate response');
408
409
  }
409
410
  const { codeBlocks, imports } = result;
410
411
  this.appendToFileMap(section, imports, codeBlocks);
@@ -430,14 +431,53 @@ class APITypingsGenerator {
430
431
  const { method: normalizedMethod, parameterRefs } = (0, methods_1.normalizeMethodInfo)(method);
431
432
  method = normalizedMethod;
432
433
  this.generateObjectsFromRefs(parameterRefs);
433
- this.generateMethodParams(new SchemaObject_1.SchemaObject(method.name, method));
434
- Object.entries(method.responses).forEach(([responseName, responseObject]) => {
434
+ const methodParamsInterfaceName = this.generateMethodParams(new SchemaObject_1.SchemaObject(method.name, method));
435
+ const methodResponsesInterfaceName = Object.entries(method.responses)
436
+ .map(([responseName, responseObject]) => {
435
437
  if (this.ignoredResponses[methodName] && this.ignoredResponses[methodName][responseName]) {
436
438
  return;
437
439
  }
438
440
  const name = `${methodName}_${responseName}`;
439
- this.generateResponse(section, new SchemaObject_1.SchemaObject(name, responseObject));
441
+ const interfaceName = (0, helpers_1.getInterfaceName)(name);
442
+ try {
443
+ this.generateResponse(section, new SchemaObject_1.SchemaObject(name, responseObject));
444
+ return interfaceName;
445
+ }
446
+ catch (e) {
447
+ return;
448
+ }
449
+ })
450
+ .filter(Boolean);
451
+ this.generateMethodType(methodName, methodParamsInterfaceName, methodResponsesInterfaceName);
452
+ }
453
+ generateMethodType(methodName, methodParamsInterfaceName, methodResponsesInterfaceName) {
454
+ const codeBlock = new TypeCodeBlock_1.TypeCodeBlock({
455
+ type: TypeCodeBlock_1.TypeScriptCodeTypes.Interface,
456
+ interfaceName: (0, helpers_1.getInterfaceName)(`${methodName} method`),
457
+ needExport: true,
458
+ allowEmptyInterface: true,
459
+ properties: [],
460
+ });
461
+ codeBlock.addProperty({
462
+ name: 'method',
463
+ value: methodName,
464
+ isRequired: true,
465
+ wrapValue: true,
466
+ });
467
+ codeBlock.addProperty({
468
+ name: 'request',
469
+ value: methodParamsInterfaceName,
470
+ isRequired: true,
440
471
  });
472
+ codeBlock.addProperty({
473
+ name: 'response',
474
+ value: methodResponsesInterfaceName.length > 0
475
+ ? methodResponsesInterfaceName.join(' | ')
476
+ : 'unknown',
477
+ isRequired: true,
478
+ });
479
+ const section = (0, helpers_1.getMethodSection)(methodName);
480
+ this.appendToFileMap(section, {}, [codeBlock]);
441
481
  }
442
482
  generateMethods() {
443
483
  (0, log_1.consoleLogInfo)('creating method params and responses...');
@@ -448,11 +488,33 @@ class APITypingsGenerator {
448
488
  });
449
489
  Object.keys(this.methodFilesMap).forEach((section) => {
450
490
  const { imports, codeBlocks } = this.methodFilesMap[section];
491
+ const methodNames = [];
451
492
  codeBlocks.forEach((codeBlock) => {
452
493
  if (codeBlock instanceof TypeCodeBlock_1.TypeCodeBlock && codeBlock.needExport && codeBlock.interfaceName) {
494
+ /**
495
+ * for interfaceName structure
496
+ * @see this.generateMethodType
497
+ */
498
+ if (codeBlock.interfaceName.endsWith('Method')) {
499
+ methodNames.push(codeBlock.interfaceName);
500
+ // Skip export as it will be exported in union type
501
+ return;
502
+ }
453
503
  this.registerExport(`./methods/${section}`, codeBlock.interfaceName);
454
504
  }
455
505
  });
506
+ if (methodNames.length > 0) {
507
+ const sectionMethodsCodeBlock = new TypeCodeBlock_1.TypeCodeBlock({
508
+ type: TypeCodeBlock_1.TypeScriptCodeTypes.Type,
509
+ interfaceName: (0, helpers_1.getInterfaceName)(`${section} methods union`),
510
+ needExport: true,
511
+ allowEmptyInterface: true,
512
+ value: methodNames.join(' | '),
513
+ properties: [],
514
+ });
515
+ codeBlocks.push(sectionMethodsCodeBlock);
516
+ this.registerExport(`./methods/${section}`, sectionMethodsCodeBlock.interfaceName);
517
+ }
456
518
  const code = [(0, generator_1.generateImportsBlock)(imports, null), ...codeBlocks];
457
519
  this.registerResultFile(path_1.default.join('methods', `${section}.ts`), code.join(constants_1.newLineChar.repeat(2)));
458
520
  });
@@ -513,8 +575,22 @@ class APITypingsGenerator {
513
575
  },
514
576
  ],
515
577
  }).toString());
578
+ code.push(`
579
+ type MethodOf<M> = M extends { method: string } ? M['method'] : never;
580
+ type RequestOf<M> = M extends { request: unknown } ? M['request'] : never;
581
+ type ResponseOf<M> = M extends { response: unknown } ? M['response'] : never;
582
+
583
+ export type CreateMethodMap<Type extends { method: string }> = {
584
+ [Property in Type as Property['method']]: {
585
+ method: MethodOf<Property>;
586
+ request: RequestOf<Property>;
587
+ response: ResponseOf<Property>;
588
+ };
589
+ };
590
+ `);
516
591
  this.registerExport('./common/common', 'API_VERSION');
517
592
  this.registerExport('./common/common', (0, helpers_1.getInterfaceName)(constants_1.baseAPIParamsInterfaceName));
593
+ this.registerExport('./common/common', 'CreateMethodMap');
518
594
  this.registerResultFile(path_1.default.join('common', 'common.ts'), code.join(constants_1.newLineChar.repeat(2)));
519
595
  }
520
596
  /**
@@ -524,6 +600,49 @@ class APITypingsGenerator {
524
600
  (0, log_1.consoleLogInfo)('creating index.ts exports...');
525
601
  const blocks = [];
526
602
  let exportedObjects = {};
603
+ let methodNames = new Set();
604
+ (0, utils_1.sortArrayAlphabetically)(Object.keys(this.exports)).forEach((path) => {
605
+ if (!path.startsWith('./methods/')) {
606
+ return;
607
+ }
608
+ const objects = Object.keys(this.exports[path]);
609
+ if (!objects.length) {
610
+ return;
611
+ }
612
+ const blockLines = [];
613
+ (0, utils_1.sortArrayAlphabetically)(objects).forEach((object) => {
614
+ /**
615
+ * for interfaceName structure
616
+ * @see this.generateMethods
617
+ */
618
+ if (!methodNames.has(object) && /^[A-Z][a-zA-Z0-9]*MethodsUnion$/.test(object)) {
619
+ methodNames.add(object);
620
+ exportedObjects[object] = true; // To skip in export block
621
+ blockLines.push(` ${object},`);
622
+ }
623
+ });
624
+ if (blockLines.length > 0) {
625
+ blockLines.unshift('import type {');
626
+ blockLines.push(`} from '${path.replace('.ts', '')}';`);
627
+ blocks.push(blockLines.join(constants_1.newLineChar));
628
+ }
629
+ });
630
+ blocks.push("import type { CreateMethodMap } from './common/common';");
631
+ const methodsCodeBlock = new TypeCodeBlock_1.TypeCodeBlock({
632
+ type: TypeCodeBlock_1.TypeScriptCodeTypes.Type,
633
+ interfaceName: 'ApiMethodsUnion',
634
+ needExport: false,
635
+ properties: [],
636
+ value: Array.from(methodNames).join(' | '),
637
+ });
638
+ blocks.push(methodsCodeBlock.toString());
639
+ blocks.push(new TypeCodeBlock_1.TypeCodeBlock({
640
+ type: TypeCodeBlock_1.TypeScriptCodeTypes.Type,
641
+ interfaceName: 'ApiMethodsMap',
642
+ needExport: true,
643
+ properties: [],
644
+ value: `CreateMethodMap<${methodsCodeBlock.interfaceName}>`,
645
+ }).toString());
527
646
  (0, utils_1.sortArrayAlphabetically)(Object.keys(this.exports)).forEach((path) => {
528
647
  const objects = Object.keys(this.exports[path]);
529
648
  if (!objects.length) {
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateInlineEnum = exports.generateEnumAsUnionType = exports.generateEnumConstantObject = exports.getEnumNamesIdentifier = exports.isNumericEnum = void 0;
3
+ exports.isNumericEnum = isNumericEnum;
4
+ exports.getEnumNamesIdentifier = getEnumNamesIdentifier;
5
+ exports.generateEnumConstantObject = generateEnumConstantObject;
6
+ exports.generateEnumAsUnionType = generateEnumAsUnionType;
7
+ exports.generateInlineEnum = generateInlineEnum;
4
8
  const constants_1 = require("../constants");
5
9
  const helpers_1 = require("../helpers");
6
10
  const utils_1 = require("../utils");
@@ -8,14 +12,12 @@ const TypeCodeBlock_1 = require("./TypeCodeBlock");
8
12
  function isNumericEnum(object) {
9
13
  return object.enum.some((value) => !!+value);
10
14
  }
11
- exports.isNumericEnum = isNumericEnum;
12
15
  function getEnumNamesIdentifier(name) {
13
16
  if (!name) {
14
17
  throw new Error('[getEnumNamesIdentifier] empty name');
15
18
  }
16
19
  return `${name} enumNames`.trim();
17
20
  }
18
- exports.getEnumNamesIdentifier = getEnumNamesIdentifier;
19
21
  function generateEnumConstantObject(object, objectName, enumNames) {
20
22
  const enumInterfaceName = (0, helpers_1.getInterfaceName)(objectName);
21
23
  const codeBlock = new TypeCodeBlock_1.TypeCodeBlock({
@@ -34,7 +36,6 @@ function generateEnumConstantObject(object, objectName, enumNames) {
34
36
  });
35
37
  return codeBlock;
36
38
  }
37
- exports.generateEnumConstantObject = generateEnumConstantObject;
38
39
  /**
39
40
  * Generates enum as union type with constant object if necessary
40
41
  */
@@ -58,7 +59,6 @@ function generateEnumAsUnionType(object) {
58
59
  value: '',
59
60
  };
60
61
  }
61
- exports.generateEnumAsUnionType = generateEnumAsUnionType;
62
62
  function getEnumNames(object) {
63
63
  let { enumNames } = object;
64
64
  const isNumeric = isNumericEnum(object);
@@ -112,4 +112,3 @@ function generateInlineEnum(object, options = {}) {
112
112
  description: descriptionLines.join(constants_1.newLineChar),
113
113
  };
114
114
  }
115
- exports.generateInlineEnum = generateInlineEnum;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeMethodInfo = void 0;
3
+ exports.normalizeMethodInfo = normalizeMethodInfo;
4
4
  const constants_1 = require("../constants");
5
5
  const helpers_1 = require("../helpers");
6
6
  const types_1 = require("../types");
@@ -38,4 +38,3 @@ function normalizeMethodInfo(method) {
38
38
  parameterRefs,
39
39
  };
40
40
  }
41
- exports.normalizeMethodInfo = normalizeMethodInfo;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateTypeString = void 0;
3
+ exports.generateTypeString = generateTypeString;
4
4
  const constants_1 = require("../constants");
5
5
  const enums_1 = require("./enums");
6
6
  const helpers_1 = require("../helpers");
@@ -146,4 +146,3 @@ function generateTypeString(object, objects, options = {}) {
146
146
  description,
147
147
  };
148
148
  }
149
- exports.generateTypeString = generateTypeString;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mergeImports = void 0;
3
+ exports.mergeImports = mergeImports;
4
4
  const types_1 = require("../../types");
5
5
  function mergeImports(oldImports, newImports) {
6
6
  const result = { ...oldImports };
@@ -13,4 +13,3 @@ function mergeImports(oldImports, newImports) {
13
13
  });
14
14
  return result;
15
15
  }
16
- exports.mergeImports = mergeImports;
package/dist/helpers.js CHANGED
@@ -15,18 +15,44 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
28
38
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.resolvePrimitiveTypesArray = exports.formatArrayDepth = exports.joinOneOfValues = exports.joinCommentLines = exports.transformPatternPropertyName = exports.areQuotesNeededForProperty = exports.isPatternProperty = exports.getSectionFromObjectName = exports.getObjectNameByRef = exports.getEnumPropertyName = exports.getInterfaceName = exports.getMethodSection = exports.isMethodNeeded = exports.prepareMethodsPattern = exports.writeFile = exports.prepareBuildDirectory = exports.readJSONFile = void 0;
39
+ exports.readJSONFile = readJSONFile;
40
+ exports.prepareBuildDirectory = prepareBuildDirectory;
41
+ exports.writeFile = writeFile;
42
+ exports.prepareMethodsPattern = prepareMethodsPattern;
43
+ exports.isMethodNeeded = isMethodNeeded;
44
+ exports.getMethodSection = getMethodSection;
45
+ exports.getInterfaceName = getInterfaceName;
46
+ exports.getEnumPropertyName = getEnumPropertyName;
47
+ exports.getObjectNameByRef = getObjectNameByRef;
48
+ exports.getSectionFromObjectName = getSectionFromObjectName;
49
+ exports.isPatternProperty = isPatternProperty;
50
+ exports.areQuotesNeededForProperty = areQuotesNeededForProperty;
51
+ exports.transformPatternPropertyName = transformPatternPropertyName;
52
+ exports.joinCommentLines = joinCommentLines;
53
+ exports.joinOneOfValues = joinOneOfValues;
54
+ exports.formatArrayDepth = formatArrayDepth;
55
+ exports.resolvePrimitiveTypesArray = resolvePrimitiveTypesArray;
30
56
  const fs_1 = __importStar(require("fs"));
31
57
  const path_1 = __importDefault(require("path"));
32
58
  const utils_1 = require("./utils");
@@ -37,7 +63,6 @@ async function readJSONFile(path) {
37
63
  const content = await fs_1.promises.readFile(path, 'utf-8');
38
64
  return JSON.parse(content);
39
65
  }
40
- exports.readJSONFile = readJSONFile;
41
66
  function deleteDirectoryRecursive(directoryPath) {
42
67
  if (fs_1.default.existsSync(directoryPath)) {
43
68
  fs_1.default.readdirSync(directoryPath).forEach((file) => {
@@ -56,7 +81,6 @@ function prepareBuildDirectory(directoryPath) {
56
81
  deleteDirectoryRecursive(directoryPath);
57
82
  fs_1.default.mkdirSync(directoryPath, { recursive: true });
58
83
  }
59
- exports.prepareBuildDirectory = prepareBuildDirectory;
60
84
  function writeFile(filePath, code, insertAutoGeneratedNote = true) {
61
85
  if (insertAutoGeneratedNote) {
62
86
  code =
@@ -82,7 +106,6 @@ function writeFile(filePath, code, insertAutoGeneratedNote = true) {
82
106
  });
83
107
  fs_1.default.writeFileSync(filePath, code.trim() + constants_1.newLineChar);
84
108
  }
85
- exports.writeFile = writeFile;
86
109
  function prepareMethodsPattern(methodsPattern) {
87
110
  if (!methodsPattern) {
88
111
  (0, log_1.consoleLogErrorAndExit)('methodsPattern is empty. Pass "*" to generate all methods');
@@ -95,7 +118,6 @@ function prepareMethodsPattern(methodsPattern) {
95
118
  return acc;
96
119
  }, {});
97
120
  }
98
- exports.prepareMethodsPattern = prepareMethodsPattern;
99
121
  function isMethodNeeded(methodsPattern, method) {
100
122
  const [methodSection, methodName] = method.split('.');
101
123
  return Object.keys(methodsPattern).some((pattern) => {
@@ -109,11 +131,9 @@ function isMethodNeeded(methodsPattern, method) {
109
131
  return false;
110
132
  });
111
133
  }
112
- exports.isMethodNeeded = isMethodNeeded;
113
134
  function getMethodSection(methodName) {
114
135
  return methodName.split('.')[0];
115
136
  }
116
- exports.getMethodSection = getMethodSection;
117
137
  function getInterfaceName(name) {
118
138
  name = name
119
139
  .replace(/\.|(\s+)|_/g, ' ')
@@ -122,24 +142,19 @@ function getInterfaceName(name) {
122
142
  .join('');
123
143
  return (0, utils_1.capitalizeFirstLetter)(name);
124
144
  }
125
- exports.getInterfaceName = getInterfaceName;
126
145
  function getEnumPropertyName(name) {
127
146
  return name.toUpperCase().replace(/\s+/g, '_').replace(/-/g, '_').replace(/\./g, '_');
128
147
  }
129
- exports.getEnumPropertyName = getEnumPropertyName;
130
148
  function getObjectNameByRef(ref) {
131
149
  const parts = ref.split('/');
132
150
  return parts[parts.length - 1];
133
151
  }
134
- exports.getObjectNameByRef = getObjectNameByRef;
135
152
  function getSectionFromObjectName(name) {
136
153
  return name.split('_')[0];
137
154
  }
138
- exports.getSectionFromObjectName = getSectionFromObjectName;
139
155
  function isPatternProperty(name) {
140
156
  return name.startsWith('[key: ');
141
157
  }
142
- exports.isPatternProperty = isPatternProperty;
143
158
  function areQuotesNeededForProperty(name) {
144
159
  name = String(name);
145
160
  if (isPatternProperty(name)) {
@@ -150,14 +165,12 @@ function areQuotesNeededForProperty(name) {
150
165
  }
151
166
  return !(/^[a-z_]([a-z0-9_])+$/i.test(name) || /^[a-z_]/i.test(name));
152
167
  }
153
- exports.areQuotesNeededForProperty = areQuotesNeededForProperty;
154
168
  function transformPatternPropertyName(name) {
155
169
  if (name === '^[0-9]+$') {
156
170
  return '[key: number]';
157
171
  }
158
172
  return '[key: string] /* default pattern property name */';
159
173
  }
160
- exports.transformPatternPropertyName = transformPatternPropertyName;
161
174
  function joinCommentLines(indent = 2, ...description) {
162
175
  let descriptionLines = [];
163
176
  description.forEach((entry) => {
@@ -184,7 +197,6 @@ function joinCommentLines(indent = 2, ...description) {
184
197
  `${indentSpaces} */`,
185
198
  ];
186
199
  }
187
- exports.joinCommentLines = joinCommentLines;
188
200
  function joinOneOfValues(values, primitive) {
189
201
  const joined = values.join(' | ');
190
202
  if (joined.length > 120) {
@@ -195,7 +207,6 @@ function joinOneOfValues(values, primitive) {
195
207
  return joined;
196
208
  }
197
209
  }
198
- exports.joinOneOfValues = joinOneOfValues;
199
210
  function formatArrayDepth(value, depth) {
200
211
  if (value.endsWith("'") || value.includes('|')) {
201
212
  return `Array<${value}>` + '[]'.repeat(depth - 1); // Need decrement depth value because of Array<T> has its own depth
@@ -204,7 +215,6 @@ function formatArrayDepth(value, depth) {
204
215
  return value + '[]'.repeat(depth);
205
216
  }
206
217
  }
207
- exports.formatArrayDepth = formatArrayDepth;
208
218
  function resolvePrimitiveTypesArray(types) {
209
219
  const isEveryTypePrimitive = types.every((type) => !!constants_1.primitiveTypes[type]);
210
220
  if (isEveryTypePrimitive) {
@@ -212,4 +222,3 @@ function resolvePrimitiveTypesArray(types) {
212
222
  }
213
223
  return null;
214
224
  }
215
- exports.resolvePrimitiveTypesArray = resolvePrimitiveTypesArray;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.main = void 0;
6
+ exports.main = main;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const perf_hooks_1 = require("perf_hooks");
@@ -84,4 +84,3 @@ async function main() {
84
84
  const endTime = perf_hooks_1.performance.now();
85
85
  (0, log_1.consoleLog)(`✨ Done in ${((endTime - startTime) / 1000).toFixed(2)}s.`);
86
86
  }
87
- exports.main = main;
package/dist/log.js CHANGED
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.consoleLogErrorAndExit = exports.consoleLogError = exports.consoleLogInfo = exports.consoleLog = void 0;
6
+ exports.consoleLog = consoleLog;
7
+ exports.consoleLogInfo = consoleLogInfo;
8
+ exports.consoleLogError = consoleLogError;
9
+ exports.consoleLogErrorAndExit = consoleLogErrorAndExit;
7
10
  const chalk_1 = __importDefault(require("chalk"));
8
11
  const util_1 = require("util");
9
12
  function getInspectArgs(args) {
@@ -23,17 +26,13 @@ function getInspectArgs(args) {
23
26
  function consoleLog(...args) {
24
27
  console.log(...getInspectArgs(args));
25
28
  }
26
- exports.consoleLog = consoleLog;
27
29
  function consoleLogInfo(...args) {
28
30
  console.log(`${chalk_1.default.cyanBright.bold('info')}`, ...getInspectArgs(args));
29
31
  }
30
- exports.consoleLogInfo = consoleLogInfo;
31
32
  function consoleLogError(...args) {
32
33
  console.log(`${chalk_1.default.redBright.bold('error')}`, ...getInspectArgs(args));
33
34
  }
34
- exports.consoleLogError = consoleLogError;
35
35
  function consoleLogErrorAndExit(...args) {
36
36
  consoleLogError(...args);
37
37
  process.exit(1);
38
38
  }
39
- exports.consoleLogErrorAndExit = consoleLogErrorAndExit;
package/dist/utils.js CHANGED
@@ -1,6 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.trimArray = exports.quoteJavaScriptValue = exports.trimStringDoubleSpaces = exports.arrayToMap = exports.sortArrayAlphabetically = exports.uniqueArray = exports.capitalizeFirstLetter = exports.isObject = exports.isString = exports.flatten = void 0;
3
+ exports.flatten = flatten;
4
+ exports.isString = isString;
5
+ exports.isObject = isObject;
6
+ exports.capitalizeFirstLetter = capitalizeFirstLetter;
7
+ exports.uniqueArray = uniqueArray;
8
+ exports.sortArrayAlphabetically = sortArrayAlphabetically;
9
+ exports.arrayToMap = arrayToMap;
10
+ exports.trimStringDoubleSpaces = trimStringDoubleSpaces;
11
+ exports.quoteJavaScriptValue = quoteJavaScriptValue;
12
+ exports.trimArray = trimArray;
4
13
  function flatten(input) {
5
14
  const stack = [...input];
6
15
  const result = [];
@@ -17,27 +26,21 @@ function flatten(input) {
17
26
  }
18
27
  return result.reverse();
19
28
  }
20
- exports.flatten = flatten;
21
29
  function isString(object) {
22
30
  return typeof object === 'string';
23
31
  }
24
- exports.isString = isString;
25
32
  function isObject(object) {
26
33
  return Object.prototype.toString.call(object) === '[object Object]';
27
34
  }
28
- exports.isObject = isObject;
29
35
  function capitalizeFirstLetter(string) {
30
36
  return string.charAt(0).toUpperCase() + string.slice(1);
31
37
  }
32
- exports.capitalizeFirstLetter = capitalizeFirstLetter;
33
38
  function uniqueArray(array) {
34
39
  return array.filter((v, i, a) => a.indexOf(v) === i);
35
40
  }
36
- exports.uniqueArray = uniqueArray;
37
41
  function sortArrayAlphabetically(array) {
38
42
  return array.sort((a, b) => a.localeCompare(b));
39
43
  }
40
- exports.sortArrayAlphabetically = sortArrayAlphabetically;
41
44
  function arrayToMap(array) {
42
45
  if (!array) {
43
46
  return {};
@@ -47,15 +50,12 @@ function arrayToMap(array) {
47
50
  return acc;
48
51
  }, {});
49
52
  }
50
- exports.arrayToMap = arrayToMap;
51
53
  function trimStringDoubleSpaces(string) {
52
54
  return string.trim().replace(/\s\s+/g, ' ');
53
55
  }
54
- exports.trimStringDoubleSpaces = trimStringDoubleSpaces;
55
56
  function quoteJavaScriptValue(value) {
56
57
  return isString(value) ? `'${value}'` : value;
57
58
  }
58
- exports.quoteJavaScriptValue = quoteJavaScriptValue;
59
59
  /**
60
60
  * Removes empty string array elements from start and end of array, trim array elements and returns the new array
61
61
  *
@@ -71,4 +71,3 @@ function trimArray(array) {
71
71
  }
72
72
  return trimmedArray;
73
73
  }
74
- exports.trimArray = trimArray;
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@vkontakte/api-schema-typescript-generator",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "license": "MIT",
5
5
  "description": "VK API TypeScript generator",
6
6
  "author": {
7
7
  "name": "VK",
8
8
  "url": "https://vk.com"
9
9
  },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
10
13
  "keywords": [
11
14
  "VK",
12
15
  "VK API",
@@ -46,13 +49,13 @@
46
49
  },
47
50
  "devDependencies": {
48
51
  "@types/jest": "^28.1.5",
49
- "@types/node": "^18.17.3",
50
- "@typescript-eslint/eslint-plugin": "5.30.6",
52
+ "@types/node": "^22.0.0",
53
+ "@typescript-eslint/eslint-plugin": "5.62.0",
51
54
  "@typescript-eslint/parser": "5.62.0",
52
55
  "@vkontakte/eslint-config": "3.1.0",
53
- "eslint": "8.19.0",
54
- "eslint-plugin-react": "7.33.1",
55
- "eslint-plugin-react-hooks": "4.6.0",
56
+ "eslint": "8.57.1",
57
+ "eslint-plugin-react": "7.37.5",
58
+ "eslint-plugin-react-hooks": "5.0.0",
56
59
  "jest": "28.1.3",
57
60
  "pre-commit": "1.2.2",
58
61
  "rimraf": "^3.0.2",
@@ -382,14 +382,14 @@ export class APITypingsGenerator {
382
382
 
383
383
  private generateMethodParams(methodInfo: SchemaObject) {
384
384
  const section = getMethodSection(methodInfo.name);
385
- const interfaceName = `${methodInfo.name} params`;
385
+ const interfaceName = getInterfaceName(`${methodInfo.name} params`);
386
386
 
387
387
  let imports: RefsDictionary = {};
388
388
  let codeBlocks: CodeBlocksArray = [];
389
389
 
390
390
  const codeBlock = new TypeCodeBlock({
391
391
  type: TypeScriptCodeTypes.Interface,
392
- interfaceName: getInterfaceName(interfaceName),
392
+ interfaceName: interfaceName,
393
393
  needExport: true,
394
394
  allowEmptyInterface: true,
395
395
  properties: [],
@@ -417,6 +417,8 @@ export class APITypingsGenerator {
417
417
 
418
418
  this.appendToFileMap(section, imports, [...codeBlocks, codeBlock]);
419
419
  this.generateObjectsFromImports(imports);
420
+
421
+ return interfaceName;
420
422
  }
421
423
 
422
424
  private getResponseObjectRef(ref: string): SchemaObject | undefined {
@@ -543,7 +545,7 @@ export class APITypingsGenerator {
543
545
  public generateResponse(section: string, response: SchemaObject) {
544
546
  const result = this.getResponseCodeBlock(response);
545
547
  if (!result) {
546
- return;
548
+ throw new Error('Unable to generate response');
547
549
  }
548
550
 
549
551
  const { codeBlocks, imports } = result;
@@ -578,16 +580,67 @@ export class APITypingsGenerator {
578
580
  method = normalizedMethod;
579
581
  this.generateObjectsFromRefs(parameterRefs);
580
582
 
581
- this.generateMethodParams(new SchemaObject(method.name, method));
583
+ const methodParamsInterfaceName = this.generateMethodParams(
584
+ new SchemaObject(method.name, method),
585
+ );
582
586
 
583
- Object.entries(method.responses).forEach(([responseName, responseObject]) => {
584
- if (this.ignoredResponses[methodName] && this.ignoredResponses[methodName][responseName]) {
585
- return;
586
- }
587
+ const methodResponsesInterfaceName = Object.entries(method.responses)
588
+ .map(([responseName, responseObject]) => {
589
+ if (this.ignoredResponses[methodName] && this.ignoredResponses[methodName][responseName]) {
590
+ return;
591
+ }
592
+
593
+ const name = `${methodName}_${responseName}`;
594
+ const interfaceName = getInterfaceName(name);
595
+ try {
596
+ this.generateResponse(section, new SchemaObject(name, responseObject));
597
+ return interfaceName;
598
+ } catch (e) {
599
+ return;
600
+ }
601
+ })
602
+ .filter(Boolean) as string[];
603
+
604
+ this.generateMethodType(methodName, methodParamsInterfaceName, methodResponsesInterfaceName);
605
+ }
606
+
607
+ private generateMethodType(
608
+ methodName: string,
609
+ methodParamsInterfaceName: string,
610
+ methodResponsesInterfaceName: string[],
611
+ ) {
612
+ const codeBlock = new TypeCodeBlock({
613
+ type: TypeScriptCodeTypes.Interface,
614
+ interfaceName: getInterfaceName(`${methodName} method`),
615
+ needExport: true,
616
+ allowEmptyInterface: true,
617
+ properties: [],
618
+ });
619
+
620
+ codeBlock.addProperty({
621
+ name: 'method',
622
+ value: methodName,
623
+ isRequired: true,
624
+ wrapValue: true,
625
+ });
587
626
 
588
- const name = `${methodName}_${responseName}`;
589
- this.generateResponse(section, new SchemaObject(name, responseObject));
627
+ codeBlock.addProperty({
628
+ name: 'request',
629
+ value: methodParamsInterfaceName,
630
+ isRequired: true,
590
631
  });
632
+
633
+ codeBlock.addProperty({
634
+ name: 'response',
635
+ value:
636
+ methodResponsesInterfaceName.length > 0
637
+ ? methodResponsesInterfaceName.join(' | ')
638
+ : 'unknown',
639
+ isRequired: true,
640
+ });
641
+
642
+ const section = getMethodSection(methodName);
643
+ this.appendToFileMap(section, {}, [codeBlock]);
591
644
  }
592
645
 
593
646
  private generateMethods() {
@@ -601,11 +654,38 @@ export class APITypingsGenerator {
601
654
 
602
655
  Object.keys(this.methodFilesMap).forEach((section) => {
603
656
  const { imports, codeBlocks } = this.methodFilesMap[section];
657
+ const methodNames: string[] = [];
604
658
  codeBlocks.forEach((codeBlock) => {
605
659
  if (codeBlock instanceof TypeCodeBlock && codeBlock.needExport && codeBlock.interfaceName) {
660
+ /**
661
+ * for interfaceName structure
662
+ * @see this.generateMethodType
663
+ */
664
+ if (codeBlock.interfaceName.endsWith('Method')) {
665
+ methodNames.push(codeBlock.interfaceName);
666
+ // Skip export as it will be exported in union type
667
+ return;
668
+ }
669
+
606
670
  this.registerExport(`./methods/${section}`, codeBlock.interfaceName);
607
671
  }
608
672
  });
673
+
674
+ if (methodNames.length > 0) {
675
+ const sectionMethodsCodeBlock = new TypeCodeBlock({
676
+ type: TypeScriptCodeTypes.Type,
677
+ interfaceName: getInterfaceName(`${section} methods union`),
678
+ needExport: true,
679
+ allowEmptyInterface: true,
680
+ value: methodNames.join(' | '),
681
+ properties: [],
682
+ });
683
+
684
+ codeBlocks.push(sectionMethodsCodeBlock);
685
+
686
+ this.registerExport(`./methods/${section}`, sectionMethodsCodeBlock.interfaceName);
687
+ }
688
+
609
689
  const code = [generateImportsBlock(imports, null), ...codeBlocks];
610
690
 
611
691
  this.registerResultFile(
@@ -685,8 +765,23 @@ export class APITypingsGenerator {
685
765
  }).toString(),
686
766
  );
687
767
 
768
+ code.push(`
769
+ type MethodOf<M> = M extends { method: string } ? M['method'] : never;
770
+ type RequestOf<M> = M extends { request: unknown } ? M['request'] : never;
771
+ type ResponseOf<M> = M extends { response: unknown } ? M['response'] : never;
772
+
773
+ export type CreateMethodMap<Type extends { method: string }> = {
774
+ [Property in Type as Property['method']]: {
775
+ method: MethodOf<Property>;
776
+ request: RequestOf<Property>;
777
+ response: ResponseOf<Property>;
778
+ };
779
+ };
780
+ `);
781
+
688
782
  this.registerExport('./common/common', 'API_VERSION');
689
783
  this.registerExport('./common/common', getInterfaceName(baseAPIParamsInterfaceName));
784
+ this.registerExport('./common/common', 'CreateMethodMap');
690
785
  this.registerResultFile(path.join('common', 'common.ts'), code.join(newLineChar.repeat(2)));
691
786
  }
692
787
 
@@ -698,6 +793,60 @@ export class APITypingsGenerator {
698
793
 
699
794
  const blocks: string[] = [];
700
795
  let exportedObjects: Dictionary<boolean> = {};
796
+ let methodNames = new Set<string>();
797
+
798
+ sortArrayAlphabetically(Object.keys(this.exports)).forEach((path) => {
799
+ if (!path.startsWith('./methods/')) {
800
+ return;
801
+ }
802
+
803
+ const objects = Object.keys(this.exports[path]);
804
+ if (!objects.length) {
805
+ return;
806
+ }
807
+
808
+ const blockLines: string[] = [];
809
+
810
+ sortArrayAlphabetically(objects).forEach((object) => {
811
+ /**
812
+ * for interfaceName structure
813
+ * @see this.generateMethods
814
+ */
815
+ if (!methodNames.has(object) && /^[A-Z][a-zA-Z0-9]*MethodsUnion$/.test(object)) {
816
+ methodNames.add(object);
817
+ exportedObjects[object] = true; // To skip in export block
818
+ blockLines.push(` ${object},`);
819
+ }
820
+ });
821
+
822
+ if (blockLines.length > 0) {
823
+ blockLines.unshift('import type {');
824
+ blockLines.push(`} from '${path.replace('.ts', '')}';`);
825
+
826
+ blocks.push(blockLines.join(newLineChar));
827
+ }
828
+ });
829
+
830
+ blocks.push("import type { CreateMethodMap } from './common/common';");
831
+
832
+ const methodsCodeBlock = new TypeCodeBlock({
833
+ type: TypeScriptCodeTypes.Type,
834
+ interfaceName: 'ApiMethodsUnion',
835
+ needExport: false,
836
+ properties: [],
837
+ value: Array.from(methodNames).join(' | '),
838
+ });
839
+ blocks.push(methodsCodeBlock.toString());
840
+
841
+ blocks.push(
842
+ new TypeCodeBlock({
843
+ type: TypeScriptCodeTypes.Type,
844
+ interfaceName: 'ApiMethodsMap',
845
+ needExport: true,
846
+ properties: [],
847
+ value: `CreateMethodMap<${methodsCodeBlock.interfaceName}>`,
848
+ }).toString(),
849
+ );
701
850
 
702
851
  sortArrayAlphabetically(Object.keys(this.exports)).forEach((path) => {
703
852
  const objects = Object.keys(this.exports[path]);