@vkontakte/api-schema-typescript-generator 0.9.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ import { newLineChar } from '../constants';
2
+ import { getEnumPropertyName, getInterfaceName, joinOneOfValues } from '../helpers';
3
+ import { RefsDictionaryType } from '../types';
4
+ import { quoteJavaScriptValue } from '../utils';
5
+ import { CodeBlocksArray, GeneratorResultInterface } from './BaseCodeBlock';
6
+ import { SchemaObject } from './SchemaObject';
7
+ import { TypeCodeBlock, TypeScriptCodeTypes } from './TypeCodeBlock';
8
+
9
+ export function isNumericEnum(object: SchemaObject): boolean {
10
+ return object.enum.some((value) => !!+value);
11
+ }
12
+
13
+ export function getEnumNamesIdentifier(name: string) {
14
+ if (!name) {
15
+ throw new Error('[getEnumNamesIdentifier] empty name');
16
+ }
17
+
18
+ return `${name} enumNames`.trim();
19
+ }
20
+
21
+ export function generateEnumConstantObject(object: SchemaObject, objectName: string, enumNames: Array<string | number>) {
22
+ const enumInterfaceName = getInterfaceName(objectName);
23
+
24
+ const codeBlock = new TypeCodeBlock({
25
+ type: TypeScriptCodeTypes.ConstantObject,
26
+ refName: objectName,
27
+ interfaceName: enumInterfaceName,
28
+ needExport: true,
29
+ properties: [],
30
+ });
31
+
32
+ enumNames.forEach((name, index) => {
33
+ codeBlock.addProperty({
34
+ name: getEnumPropertyName(name.toString()),
35
+ value: object.enum[index],
36
+ wrapValue: true,
37
+ });
38
+ });
39
+
40
+ return codeBlock;
41
+ }
42
+
43
+ /**
44
+ * Generates enum as union type with constant object if necessary
45
+ */
46
+ export function generateEnumAsUnionType(object: SchemaObject): GeneratorResultInterface {
47
+ const { codeBlocks, value, description } = generateInlineEnum(object, {
48
+ refName: getEnumNamesIdentifier(object.name),
49
+ });
50
+
51
+ const unionType = new TypeCodeBlock({
52
+ type: TypeScriptCodeTypes.Type,
53
+ refName: object.name,
54
+ interfaceName: getInterfaceName(object.name),
55
+ description: [
56
+ object.description,
57
+ description,
58
+ ].join(newLineChar),
59
+ needExport: true,
60
+ properties: [],
61
+ value,
62
+ });
63
+
64
+ codeBlocks.push(unionType);
65
+
66
+ return {
67
+ codeBlocks,
68
+ imports: {},
69
+ value: '',
70
+ };
71
+ }
72
+
73
+ function getEnumNames(object: SchemaObject) {
74
+ let { enumNames } = object;
75
+
76
+ const isNumeric = isNumericEnum(object);
77
+ const needEnumNamesDescription = !!enumNames;
78
+
79
+ if (!enumNames) {
80
+ const canUseEnumNames = !isNumeric;
81
+ if (canUseEnumNames) {
82
+ enumNames = [...object.enum];
83
+ }
84
+ }
85
+
86
+ return {
87
+ isNumericEnum: isNumeric,
88
+ needEnumNamesDescription,
89
+ enumNames: Array.isArray(enumNames) && enumNames.length ? enumNames : undefined,
90
+ };
91
+ }
92
+
93
+ interface GenerateInlineEnumOptions {
94
+ objectParentName?: string;
95
+ needEnumNamesConstant?: boolean;
96
+ refType?: RefsDictionaryType.Generate;
97
+ refName?: string;
98
+ }
99
+
100
+ export function generateInlineEnum(object: SchemaObject, options: GenerateInlineEnumOptions = {}): GeneratorResultInterface {
101
+ const {
102
+ isNumericEnum,
103
+ enumNames,
104
+ needEnumNamesDescription,
105
+ } = getEnumNames(object);
106
+
107
+ options = {
108
+ needEnumNamesConstant: isNumericEnum,
109
+ ...options,
110
+ };
111
+
112
+ const codeBlocks: CodeBlocksArray = [];
113
+ let descriptionLines: string[] = [];
114
+
115
+ if (enumNames) {
116
+ if (needEnumNamesDescription) {
117
+ if (isNumericEnum && options.refName) {
118
+ descriptionLines.push('');
119
+ descriptionLines.push('@note This enum have auto-generated constant with keys and values');
120
+ descriptionLines.push(`@see ${getInterfaceName(options.refName)}`);
121
+ }
122
+
123
+ descriptionLines.push('');
124
+
125
+ enumNames.forEach((name, index) => {
126
+ const value = object.enum[index];
127
+
128
+ if (needEnumNamesDescription) {
129
+ descriptionLines.push(`\`${value}\` — ${name}`);
130
+ }
131
+ });
132
+ }
133
+
134
+ if (isNumericEnum && options.needEnumNamesConstant) {
135
+ const enumName = getEnumNamesIdentifier(`${options.objectParentName || ''} ${object.name}`);
136
+
137
+ const codeBlock = generateEnumConstantObject(object, enumName, enumNames);
138
+ codeBlocks.push(codeBlock);
139
+ }
140
+ }
141
+
142
+ const values = object.enum.map((value) => quoteJavaScriptValue(value));
143
+
144
+ return {
145
+ codeBlocks,
146
+ imports: {},
147
+ value: joinOneOfValues(values, true),
148
+ description: descriptionLines.join(newLineChar),
149
+ };
150
+ }
@@ -0,0 +1,49 @@
1
+ import { baseBoolIntRef, newLineChar } from '../constants';
2
+ import { getInterfaceName, getObjectNameByRef } from '../helpers';
3
+ import { RefsDictionary, RefsDictionaryType } from '../types';
4
+ import * as Schema from '../types/schema';
5
+
6
+ interface NormalizeMethodInfoResult {
7
+ method: Schema.Method;
8
+ parameterRefs: RefsDictionary;
9
+ }
10
+
11
+ /**
12
+ * Patches for method definition
13
+ */
14
+ export function normalizeMethodInfo(method: Schema.Method): NormalizeMethodInfoResult {
15
+ const parameterRefs: RefsDictionary = {};
16
+
17
+ method.parameters?.forEach((parameter) => {
18
+ // For method params "boolean" type means 1 or 0
19
+ // Real "false" boolean value will be detected by API as true
20
+ if (parameter.type === 'boolean') {
21
+ delete parameter.type;
22
+ parameter.$ref = baseBoolIntRef;
23
+ }
24
+
25
+ // For parameters of the "array" type, VK API still accepts only a comma-separated string
26
+ // This may change in the future when the VK API starts accepting a json body
27
+ if (parameter.type === 'array') {
28
+ parameter.type = 'string';
29
+ }
30
+
31
+ if (!parameter.description) {
32
+ parameter.description = '';
33
+ }
34
+
35
+ if (parameter.items && parameter.items.$ref) {
36
+ const ref = parameter.items?.$ref;
37
+ parameterRefs[ref] = RefsDictionaryType.Generate;
38
+
39
+ parameter.description += newLineChar.repeat(2) + [
40
+ `@see ${getInterfaceName(getObjectNameByRef(ref))} (${ref})`,
41
+ ].join(newLineChar);
42
+ }
43
+ });
44
+
45
+ return {
46
+ method,
47
+ parameterRefs,
48
+ };
49
+ }
@@ -1,7 +1,3 @@
1
- import { CodeBlocksArray, GeneratorResultInterface } from './BaseCodeBlock';
2
- import { SchemaObject } from './SchemaObject';
3
- import { Dictionary } from '../types';
4
- import { getInterfaceName, getObjectNameByRef, joinOneOfValues, resolvePrimitiveTypesArray } from '../helpers';
5
1
  import {
6
2
  baseBoolIntRef,
7
3
  baseOkResponseRef,
@@ -10,14 +6,32 @@ import {
10
6
  PropertyType,
11
7
  scalarTypes,
12
8
  } from '../constants';
13
- import { isString } from '../utils';
9
+ import { generateInlineEnum } from './enums';
10
+ import {
11
+ formatArrayDepth,
12
+ getInterfaceName,
13
+ getObjectNameByRef,
14
+ joinOneOfValues,
15
+ resolvePrimitiveTypesArray,
16
+ } from '../helpers';
14
17
  import { consoleLogErrorAndExit } from '../log';
15
- import { generateInlineEnum } from '../generator';
18
+ import { Dictionary, RefsDictionary, RefsDictionaryType } from '../types';
19
+ import { isString } from '../utils';
20
+ import { CodeBlocksArray, GeneratorResultInterface } from './BaseCodeBlock';
21
+ import { SchemaObject } from './SchemaObject';
22
+
23
+ interface GenerateTypeStringOptions {
24
+ objectParentName?: string;
25
+ /**
26
+ * Determines whether enums will be inline to type value or them will be as separate interface block
27
+ */
28
+ needEnumNamesConstant?: boolean;
29
+ }
16
30
 
17
31
  function generateBaseType(object: SchemaObject, options: GenerateTypeStringOptions): GeneratorResultInterface {
18
32
  let codeBlocks: CodeBlocksArray = [];
19
33
  let typeString = 'any /* default type */';
20
- let imports: Record<string, boolean> = {};
34
+ let imports: RefsDictionary = {};
21
35
  let description: string | undefined = '';
22
36
 
23
37
  if (object.enum) {
@@ -29,7 +43,7 @@ function generateBaseType(object: SchemaObject, options: GenerateTypeStringOptio
29
43
  // TODO: Refactor
30
44
  // section_object_name -> property_name -> items => section_object_name_property_name_items enumNames
31
45
  objectParentName: options.objectParentName || object.parentObjectName,
32
- skipEnumNamesConstant: options.skipEnumNamesConstant,
46
+ needEnumNamesConstant: options.needEnumNamesConstant,
33
47
  });
34
48
 
35
49
  typeString = value;
@@ -57,14 +71,6 @@ function generateBaseType(object: SchemaObject, options: GenerateTypeStringOptio
57
71
  };
58
72
  }
59
73
 
60
- interface GenerateTypeStringOptions {
61
- objectParentName?: string;
62
- /**
63
- * Determines whether enums will be inline to type value or them will be as separate interface block
64
- */
65
- skipEnumNamesConstant?: boolean;
66
- }
67
-
68
74
  export function generateTypeString(
69
75
  object: SchemaObject,
70
76
  objects: Dictionary<SchemaObject>,
@@ -72,9 +78,14 @@ export function generateTypeString(
72
78
  ): GeneratorResultInterface {
73
79
  let codeBlocks: CodeBlocksArray = [];
74
80
  let typeString = 'any /* default type */';
75
- let imports: Dictionary<boolean> = {};
81
+ let imports: RefsDictionary = {};
76
82
  let description: string | undefined = '';
77
83
 
84
+ options = {
85
+ needEnumNamesConstant: true,
86
+ ...options,
87
+ };
88
+
78
89
  if (object.oneOf) {
79
90
  const values = object.oneOf.map((oneOfObject) => {
80
91
  const { value, imports: newImports } = generateTypeString(oneOfObject, objects);
@@ -104,8 +115,8 @@ export function generateTypeString(
104
115
  consoleLogErrorAndExit(`Error, object for "${refName}" ref is not found.`);
105
116
  }
106
117
 
107
- imports[refName] = true;
108
- typeString = getInterfaceName(refName) + '[]'.repeat(depth);
118
+ imports[refName] = RefsDictionaryType.GenerateAndImport;
119
+ typeString = formatArrayDepth(getInterfaceName(refName), depth);
109
120
  } else {
110
121
  const {
111
122
  value,
@@ -118,18 +129,11 @@ export function generateTypeString(
118
129
  objectParentName: object.parentObjectName,
119
130
  });
120
131
 
121
- if (value.endsWith('\'') || value.includes('|')) {
122
- typeString = `Array<${value}>` + '[]'.repeat(depth - 1); // Need decrement depth value because of Array<T> has its own depth
123
- } else {
124
- typeString = value + '[]'.repeat(depth);
125
- }
126
-
132
+ typeString = formatArrayDepth(value, depth);
127
133
  description = newDescription;
128
134
  imports = { ...imports, ...newImports };
129
135
  codeBlocks = [...codeBlocks, ...newCodeBlocks];
130
136
  }
131
- } else if (object.type) {
132
- return generateBaseType(object, options);
133
137
  } else if (object.ref) {
134
138
  const refName = getObjectNameByRef(object.ref);
135
139
 
@@ -145,13 +149,12 @@ export function generateTypeString(
145
149
 
146
150
  default: {
147
151
  const refObject = objects[refName];
148
-
149
152
  if (!refObject) {
150
153
  consoleLogErrorAndExit(`Error, object for "${refName}" ref is not found.`);
151
154
  }
152
155
 
153
156
  if (refObject.enum) {
154
- imports[refName] = true;
157
+ imports[refName] = RefsDictionaryType.GenerateAndImport;
155
158
  typeString = getInterfaceName(refName);
156
159
  } else if (refObject.oneOf) {
157
160
  const values = refObject.oneOf.map((oneOfObject) => {
@@ -164,11 +167,13 @@ export function generateTypeString(
164
167
  } else if (isString(refObject.type) && scalarTypes[refObject.type] && !refObject.ref) {
165
168
  typeString = scalarTypes[refObject.type];
166
169
  } else {
167
- imports[refName] = true;
170
+ imports[refName] = RefsDictionaryType.GenerateAndImport;
168
171
  typeString = getInterfaceName(refName);
169
172
  }
170
173
  }
171
174
  }
175
+ } else if (object.type) {
176
+ return generateBaseType(object, options);
172
177
  }
173
178
 
174
179
  return {
package/src/helpers.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs, { promises as fsPromises } from 'fs';
2
2
  import path from 'path';
3
- import { capitalizeFirstLetter } from './utils';
3
+ import { capitalizeFirstLetter, trimArray } from './utils';
4
4
  import { newLineChar, primitiveTypes, spaceChar } from './constants';
5
5
  import { Dictionary } from './types';
6
6
  import { consoleLogErrorAndExit } from './log';
@@ -125,6 +125,39 @@ export function transformPatternPropertyName(name: string): string {
125
125
  return '[key: string] /* default pattern property name */';
126
126
  }
127
127
 
128
+ export function joinCommentLines(indent = 2, ...description: Array<string | string[]>): string[] {
129
+ let descriptionLines: string[] = [];
130
+
131
+ description.forEach((entry) => {
132
+ if (typeof entry === 'string') {
133
+ descriptionLines = [
134
+ ...descriptionLines,
135
+ ...trimArray((entry || '').trim().split(newLineChar)),
136
+ ];
137
+ } else if (Array.isArray(entry)) {
138
+ descriptionLines = [
139
+ ...descriptionLines,
140
+ ...entry,
141
+ ];
142
+ }
143
+ });
144
+
145
+ descriptionLines = trimArray(descriptionLines);
146
+ if (!descriptionLines.length) {
147
+ return [];
148
+ }
149
+
150
+ const indentSpaces = spaceChar.repeat(indent);
151
+
152
+ return [
153
+ `${indentSpaces}/**`,
154
+ ...descriptionLines.map((line) => {
155
+ return indentSpaces + ' ' + `* ${line}`.trim();
156
+ }),
157
+ `${indentSpaces} */`,
158
+ ];
159
+ }
160
+
128
161
  export function joinOneOfValues(values: Array<string | number>, primitive?: boolean) {
129
162
  const joined = values.join(' | ');
130
163
 
@@ -136,6 +169,14 @@ export function joinOneOfValues(values: Array<string | number>, primitive?: bool
136
169
  }
137
170
  }
138
171
 
172
+ export function formatArrayDepth(value: string, depth: number) {
173
+ if (value.endsWith('\'') || value.includes('|')) {
174
+ return `Array<${value}>` + '[]'.repeat(depth - 1); // Need decrement depth value because of Array<T> has its own depth
175
+ } else {
176
+ return value + '[]'.repeat(depth);
177
+ }
178
+ }
179
+
139
180
  export function resolvePrimitiveTypesArray(types: string[]): string | null {
140
181
  const isEveryTypePrimitive = types.every((type) => !!primitiveTypes[type]);
141
182
  if (isEveryTypePrimitive) {
package/src/log.ts CHANGED
@@ -6,7 +6,7 @@ function getInspectArgs(args: any[]) {
6
6
  if (typeof arg === 'object') {
7
7
  return inspect(arg, {
8
8
  showHidden: false,
9
- depth: null,
9
+ depth: 10,
10
10
  colors: true,
11
11
  });
12
12
  } else {
@@ -0,0 +1,152 @@
1
+ export type Format = 'json' | 'int32' | 'int64';
2
+
3
+ /**
4
+ * Enum values text representations
5
+ */
6
+ export type EnumNames = [string, ...string[]];
7
+
8
+ /**
9
+ * This interface was referenced by `undefined`'s JSON-Schema definition
10
+ * via the `patternProperty` "^[a-zA-Z0-9_]+$".
11
+ */
12
+ export type ResponseProperty = {
13
+ [k: string]: unknown;
14
+ };
15
+
16
+ /**
17
+ * Possible custom errors
18
+ */
19
+ export type MethodErrors = Array<{
20
+ $ref?: string;
21
+ }>;
22
+
23
+ /**
24
+ * VK API declaration
25
+ */
26
+ export interface API {
27
+ errors?: {
28
+ [k: string]: Error;
29
+ };
30
+ methods?: Method[];
31
+ definitions?: {
32
+ [k: string]: Response;
33
+ };
34
+ $schema?: string;
35
+ title?: string;
36
+ description?: string;
37
+ termsOfService?: string;
38
+ version?: string;
39
+ }
40
+
41
+ /**
42
+ * This interface was referenced by `undefined`'s JSON-Schema definition
43
+ * via the `patternProperty` "^[a-z][a-z0-9_]+$".
44
+ */
45
+ export interface Error {
46
+ /**
47
+ * Error code
48
+ */
49
+ code: number;
50
+ /**
51
+ * Error description
52
+ */
53
+ description: string;
54
+ /**
55
+ * Array of error subcodes
56
+ */
57
+ subcodes?: ErrorSubcode[];
58
+ global?: boolean;
59
+ disabled?: boolean;
60
+ }
61
+
62
+ export interface ErrorSubcode {
63
+ subcode?: number;
64
+ description?: string;
65
+ $comment?: string;
66
+ $ref?: string;
67
+ }
68
+
69
+ export interface Method {
70
+ /**
71
+ * Method name
72
+ */
73
+ name: string;
74
+ /**
75
+ * Method description
76
+ */
77
+ description?: string;
78
+ timeout?: number;
79
+ /**
80
+ * Input parameters for method
81
+ */
82
+ access_token_type: Array<'open' | 'user' | 'group' | 'service'>;
83
+ /**
84
+ * Input parameters for method
85
+ */
86
+ parameters?: Parameter[];
87
+ /**
88
+ * References to response objects
89
+ */
90
+ responses: {
91
+ [k: string]: Response;
92
+ };
93
+ emptyResponse?: boolean;
94
+ errors?: MethodErrors;
95
+ }
96
+
97
+ export interface Parameter {
98
+ /**
99
+ * Parameter name
100
+ */
101
+ name: string;
102
+ format?: Format;
103
+ /**
104
+ * Parameter type
105
+ */
106
+ type: 'array' | 'boolean' | 'integer' | 'number' | 'string';
107
+ items?: {
108
+ $ref: string;
109
+ };
110
+ maxItems?: number;
111
+ minItems?: number;
112
+ maximum?: number;
113
+ minimum?: number;
114
+ $ref?: string;
115
+ enum?: [unknown, ...unknown[]];
116
+ enumNames?: EnumNames;
117
+ /**
118
+ * Default property value
119
+ */
120
+ default?: {
121
+ [k: string]: unknown;
122
+ };
123
+ required?: boolean;
124
+ maxLength?: number;
125
+ minLength?: number;
126
+ /**
127
+ * Parameter description
128
+ */
129
+ description?: string;
130
+ }
131
+
132
+ /**
133
+ * This interface was referenced by `undefined`'s JSON-Schema definition
134
+ * via the `patternProperty` "^([a-zA-Z0-9_]+)?[rR]esponse$".
135
+ *
136
+ * This interface was referenced by `undefined`'s JSON-Schema definition
137
+ * via the `patternProperty` "^([a-zA-Z0-9_]+)?[rR]esponse$".
138
+ */
139
+ export interface Response {
140
+ type?: string;
141
+ description?: string;
142
+ allOf?: Response[];
143
+ items?: any[];
144
+ required?: unknown[];
145
+ title?: string;
146
+ oneOf?: unknown[];
147
+ $ref?: string;
148
+ properties?: {
149
+ [k: string]: ResponseProperty;
150
+ };
151
+ additionalProperties?: boolean;
152
+ }
package/src/types.ts CHANGED
@@ -2,49 +2,17 @@ export interface Dictionary<T> {
2
2
  [key: string]: T;
3
3
  }
4
4
 
5
- export type RefsDictionary = Dictionary<true>;
5
+ export enum RefsDictionaryType {
6
+ GenerateAndImport,
7
+ Generate,
8
+ }
9
+
10
+ export type RefsDictionary = Record<string, RefsDictionaryType>;
11
+
12
+ export type EnumLikeArray = Array<string | number>;
6
13
 
7
14
  export enum ObjectType {
8
15
  Object = 'object',
9
16
  Response = 'response',
10
17
  Params = 'params',
11
18
  }
12
-
13
- export interface JSONSchemaPropertyInterface {
14
- type?: 'integer' | 'string' | 'boolean' | 'array';
15
- items?: JSONSchemaPropertyInterface;
16
- $ref?: string;
17
- minimum?: number;
18
- format?: 'uri';
19
- description?: string;
20
- }
21
-
22
- export interface JSONSchemaObjectInterface {
23
- type: 'object';
24
- properties?: JSONSchemaPropertyInterface[];
25
- required?: string[];
26
- }
27
-
28
- export interface JSONSchemaMethodParameter extends JSONSchemaPropertyInterface {
29
- name: string;
30
- required?: boolean;
31
- }
32
-
33
- export interface JSONSchemaMethodsDefinitionsInterface {
34
- $schema: string;
35
- version: string;
36
- title: string;
37
- description: string;
38
- termsOfService: string;
39
- methods: JSONSchemaMethodInfoInterface[];
40
- }
41
-
42
- export interface JSONSchemaMethodInfoInterface {
43
- name: string;
44
- description?: string;
45
- access_token_type?: string[];
46
- parameters?: JSONSchemaMethodParameter[];
47
- responses: Dictionary<Dictionary<string>>;
48
- errors?: Array<Dictionary<any>>;
49
- emptyResponse?: boolean;
50
- }