@opra/cli 1.0.0-alpha.9 → 1.0.0-beta.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.
Files changed (41) hide show
  1. package/cjs/file-writer.js +2 -2
  2. package/cjs/oprimp-cli.js +11 -9
  3. package/cjs/ts-generator/{processors → generators}/clean-directory.js +4 -5
  4. package/cjs/ts-generator/generators/generate-data-type.js +258 -0
  5. package/cjs/ts-generator/generators/generate-document.js +64 -0
  6. package/cjs/ts-generator/{processors/process-http-api.js → generators/generate-http-api.js} +3 -4
  7. package/cjs/ts-generator/generators/generate-http-controller.js +296 -0
  8. package/cjs/ts-generator/ts-file.js +18 -12
  9. package/cjs/ts-generator/ts-generator.js +20 -20
  10. package/cjs/ts-generator/utils/locate-named-type.js +1 -2
  11. package/cjs/ts-generator/utils/string-utils.js +4 -5
  12. package/esm/file-writer.js +1 -1
  13. package/esm/oprimp-cli.js +9 -7
  14. package/esm/package.json +3 -0
  15. package/esm/ts-generator/{processors → generators}/clean-directory.js +3 -3
  16. package/esm/ts-generator/generators/generate-data-type.js +248 -0
  17. package/esm/ts-generator/{processors/process-document.js → generators/generate-document.js} +17 -12
  18. package/esm/ts-generator/{processors/process-http-api.js → generators/generate-http-api.js} +2 -2
  19. package/esm/ts-generator/generators/generate-http-controller.js +292 -0
  20. package/esm/ts-generator/ts-file.js +15 -9
  21. package/esm/ts-generator/ts-generator.js +20 -20
  22. package/package.json +28 -33
  23. package/types/file-writer.d.ts +1 -1
  24. package/types/index.d.cts +1 -0
  25. package/types/interfaces/service-generation-context.interface.d.ts +2 -2
  26. package/types/ts-generator/generators/generate-data-type.d.ts +41 -0
  27. package/types/ts-generator/{processors/process-document.d.ts → generators/generate-document.d.ts} +1 -1
  28. package/types/ts-generator/generators/generate-http-api.d.ts +3 -0
  29. package/types/ts-generator/generators/generate-http-controller.d.ts +3 -0
  30. package/types/ts-generator/ts-file.d.ts +6 -2
  31. package/types/ts-generator/ts-generator.d.ts +22 -20
  32. package/bin/bin/oprimp.mjs +0 -3
  33. package/cjs/ts-generator/processors/process-data-types.js +0 -262
  34. package/cjs/ts-generator/processors/process-document.js +0 -60
  35. package/cjs/ts-generator/processors/process-http-controller.js +0 -189
  36. package/esm/ts-generator/processors/process-data-types.js +0 -251
  37. package/esm/ts-generator/processors/process-http-controller.js +0 -184
  38. package/types/ts-generator/processors/process-data-types.d.ts +0 -30
  39. package/types/ts-generator/processors/process-http-api.d.ts +0 -3
  40. package/types/ts-generator/processors/process-http-controller.d.ts +0 -3
  41. /package/types/ts-generator/{processors → generators}/clean-directory.d.ts +0 -0
@@ -0,0 +1,248 @@
1
+ import path from 'node:path';
2
+ import { ComplexType, EnumType, MappedType, MixinType, SimpleType } from '@opra/common';
3
+ import { CodeBlock } from '../../code-block.js';
4
+ import { wrapJSDocString } from '../utils/string-utils.js';
5
+ const internalTypeNames = ['any', 'boolean', 'bigint', 'number', 'null', 'string', 'object'];
6
+ export async function generateDataType(dataType, intent, currentFile) {
7
+ const doc = dataType.node.getDocument();
8
+ if (doc.id !== this._document?.id) {
9
+ const { generator } = await this.generateDocument(doc);
10
+ return await generator.generateDataType(dataType, intent, currentFile);
11
+ }
12
+ const typeName = dataType.name;
13
+ if (typeName) {
14
+ if (internalTypeNames.includes(typeName))
15
+ return { kind: 'internal', typeName: dataType.name };
16
+ let file = this._filesMap.get(dataType);
17
+ if (file) {
18
+ if (currentFile)
19
+ currentFile.addImport(file.filename, [typeName]);
20
+ return { kind: 'named', file, typeName: dataType.name };
21
+ }
22
+ if (dataType instanceof SimpleType)
23
+ file = this.addFile(path.join(this._documentRoot, '/simple-types.ts'), true);
24
+ else if (dataType instanceof EnumType) {
25
+ file = this.addFile(path.join(this._typesRoot, 'enums', typeName + '.ts'), true);
26
+ }
27
+ else
28
+ file = this.addFile(path.join(this._typesRoot, 'types', typeName + '.ts'), true);
29
+ this._filesMap.set(dataType, file);
30
+ if (file.exportTypes.includes(typeName)) {
31
+ if (currentFile)
32
+ currentFile.addImport(file.filename, [typeName]);
33
+ return { kind: 'named', file, typeName: dataType.name };
34
+ }
35
+ file.exportTypes.push(typeName);
36
+ const typesIndexTs = this.addFile(path.join(this._typesRoot, 'index.ts'), true);
37
+ const indexTs = this.addFile('/index.ts', true);
38
+ indexTs.addExport(typesIndexTs.filename, undefined, this._typesNamespace);
39
+ const codeBlock = (file.code['type_' + typeName] = new CodeBlock());
40
+ codeBlock.head = `/**\n * ${wrapJSDocString(dataType.description || '')}\n *`;
41
+ codeBlock.head += `
42
+ * @url ${path.posix.join(doc.url || this.serviceUrl, '$schema', '#types/' + typeName)}
43
+ */
44
+ export `;
45
+ codeBlock.typeDef = (await this._generateTypeCode(file, dataType, 'root')) + '\n\n';
46
+ typesIndexTs.addExport(file.filename);
47
+ if (currentFile)
48
+ currentFile.addImport(file.filename, [typeName]);
49
+ return { kind: 'named', file, typeName };
50
+ }
51
+ if (!currentFile)
52
+ throw new TypeError(`You must provide currentFile to generate data type`);
53
+ const code = await this._generateTypeCode(currentFile, dataType, intent);
54
+ return { kind: 'embedded', code };
55
+ }
56
+ /**
57
+ *
58
+ */
59
+ export async function _generateTypeCode(currentFile, dataType, intent) {
60
+ if (intent === 'root' && !dataType.name) {
61
+ throw new TypeError(`Name required to generate data type code to root intent`);
62
+ }
63
+ if (dataType instanceof EnumType) {
64
+ return await this._generateEnumTypeCode(currentFile, dataType, intent);
65
+ }
66
+ if (dataType instanceof ComplexType) {
67
+ return await this._generateComplexTypeCode(currentFile, dataType, intent);
68
+ }
69
+ if (dataType instanceof SimpleType) {
70
+ return await this._generateSimpleTypeCode(currentFile, dataType, intent);
71
+ }
72
+ if (dataType instanceof MappedType) {
73
+ return await this._generateMappedTypeCode(currentFile, dataType, intent);
74
+ }
75
+ if (dataType instanceof MixinType) {
76
+ return await this._generateMixinTypeCode(currentFile, dataType, intent);
77
+ }
78
+ /* istanbul ignore next */
79
+ throw new TypeError(`${dataType.kind} data types can not be directly exported`);
80
+ }
81
+ /**
82
+ *
83
+ */
84
+ export async function _generateEnumTypeCode(currentFile, dataType, intent) {
85
+ if (intent === 'root') {
86
+ let out = `enum ${dataType.name} {\n\t`;
87
+ for (const [value, info] of Object.entries(dataType.attributes)) {
88
+ // Print JSDoc
89
+ let jsDoc = '';
90
+ if (dataType.attributes[value].description)
91
+ jsDoc += ` * ${dataType.attributes[value].description}\n`;
92
+ if (jsDoc)
93
+ out += `/**\n${jsDoc} */\n`;
94
+ out +=
95
+ `${info.alias || value} = ` +
96
+ (typeof value === 'number' ? value : "'" + String(value).replace("'", "\\'") + "'");
97
+ out += ',\n\n';
98
+ }
99
+ return out + '\b}';
100
+ }
101
+ return ('(' +
102
+ Object.keys(dataType.attributes)
103
+ .map(t => `'${t}'`)
104
+ .join(' | ') +
105
+ ')');
106
+ }
107
+ /**
108
+ *
109
+ */
110
+ export async function _generateComplexTypeCode(currentFile, dataType, intent) {
111
+ let out = intent === 'root' ? `interface ${dataType.name} ` : '';
112
+ const ownFields = [...dataType.fields.values()].filter(f => f.origin === dataType);
113
+ if (dataType.base) {
114
+ const base = await this.generateDataType(dataType.base, 'extends', currentFile);
115
+ let baseDef = base.kind === 'embedded' ? base.code : base.typeName;
116
+ const omitBaseFields = ownFields.filter(f => dataType.base.fields.has(f.name));
117
+ if (omitBaseFields.length)
118
+ baseDef = `Omit<${baseDef}, ${omitBaseFields.map(x => "'" + x.name + "'").join(' | ')}>`;
119
+ if (intent === 'root')
120
+ out += `extends ${baseDef} `;
121
+ else {
122
+ out += baseDef;
123
+ if (!ownFields.length)
124
+ return out;
125
+ out += ' & ';
126
+ }
127
+ }
128
+ out += '{\n\t';
129
+ let i = 0;
130
+ for (const field of ownFields) {
131
+ if (i++)
132
+ out += '\n';
133
+ // Print JSDoc
134
+ out += `/**\n * ${field.description || ''}\n`;
135
+ if (field.default)
136
+ out += ` * @default ` + field.default + '\n';
137
+ // if (field.format)
138
+ // jsDoc += ` * @format ` + field.format + '\n';
139
+ if (field.exclusive)
140
+ out += ` * @exclusive\n`;
141
+ if (field.readonly)
142
+ out += ` * @readonly\n`;
143
+ if (field.writeonly)
144
+ out += ` * @writeonly\n`;
145
+ if (field.deprecated) {
146
+ out += ` * @deprecated ` + (typeof field.deprecated === 'string' ? field.deprecated : '') + '\n';
147
+ }
148
+ out += ' */\n';
149
+ // Print field name
150
+ if (field.readonly)
151
+ out += 'readonly ';
152
+ out += `${field.name}${field.required ? '' : '?'}: `;
153
+ if (field.fixed) {
154
+ const t = typeof field.fixed;
155
+ out += `${t === 'number' || t === 'boolean' || t === 'bigint' ? field.fixed : "'" + field.fixed + "'"}\n`;
156
+ }
157
+ else {
158
+ const x = await this.generateDataType(field.type, 'typeDef', currentFile);
159
+ out += (x.kind === 'embedded' ? x.code : x.typeName) + `${field.isArray ? '[]' : ''};\n`;
160
+ }
161
+ }
162
+ if (dataType.additionalFields)
163
+ out += '[key: string]: any;\n';
164
+ return out + '\b}';
165
+ }
166
+ /**
167
+ *
168
+ */
169
+ export async function _generateSimpleTypeCode(currentFile, dataType, intent) {
170
+ let out = intent === 'root' ? `type ${dataType.name} = ` : '';
171
+ out += dataType.nameMappings.js || 'any';
172
+ return intent === 'root' ? out + ';' : out;
173
+ }
174
+ /**
175
+ *
176
+ */
177
+ export async function _generateMixinTypeCode(currentFile, dataType, intent) {
178
+ const outArray = [];
179
+ for (const t of dataType.types) {
180
+ const x = await this.generateDataType(t, 'typeDef', currentFile);
181
+ if (x.kind === 'embedded') {
182
+ outArray.push(x.code.includes('|') ? '(' + x.code + ')' : x.code);
183
+ }
184
+ else
185
+ outArray.push(x.typeName);
186
+ }
187
+ if (intent === 'root')
188
+ return `type ${dataType.name} = ${outArray.join(' & ')}`;
189
+ if (intent === 'extends')
190
+ return outArray.join(', ');
191
+ return outArray.join(' & ');
192
+ }
193
+ /**
194
+ *
195
+ */
196
+ export async function _generateMappedTypeCode(currentFile, dataType, intent) {
197
+ let out = intent === 'root' ? `type ${dataType.name} = ` : '';
198
+ const base = await this.generateDataType(dataType.base, 'typeDef', currentFile);
199
+ const typeDef = base.kind === 'embedded' ? base.code : base.typeName;
200
+ const pick = dataType.pick?.length ? dataType.pick : undefined;
201
+ const omit = !pick && dataType.omit?.length ? dataType.omit : undefined;
202
+ const partial = dataType.partial === true || (Array.isArray(dataType.partial) && dataType.partial.length > 0)
203
+ ? dataType.partial
204
+ : undefined;
205
+ const required = dataType.required === true || (Array.isArray(dataType.required) && dataType.required.length > 0)
206
+ ? dataType.required
207
+ : undefined;
208
+ if (!(pick || omit || partial || required))
209
+ return typeDef;
210
+ if (partial === true)
211
+ out += 'Partial<';
212
+ else if (partial) {
213
+ out += 'PartialSome<';
214
+ currentFile.addExport('ts-gems', ['PartialSome']);
215
+ }
216
+ if (required === true)
217
+ out += 'Partial<';
218
+ else if (required) {
219
+ out += 'RequiredSome<';
220
+ currentFile.addExport('ts-gems', ['RequiredSome']);
221
+ }
222
+ if (pick)
223
+ out += 'Pick<';
224
+ else if (omit)
225
+ out += 'Omit<';
226
+ out += typeDef;
227
+ if (omit || pick) {
228
+ out +=
229
+ ', ' +
230
+ (omit || pick)
231
+ .filter(x => !!x)
232
+ .map(x => `'${x}'`)
233
+ .join(' | ') +
234
+ '>';
235
+ }
236
+ if (partial) {
237
+ if (Array.isArray(partial)) {
238
+ out +=
239
+ ', ' +
240
+ partial
241
+ .filter(x => !!x)
242
+ .map(x => `'${x}'`)
243
+ .join(' | ');
244
+ }
245
+ out += '>';
246
+ }
247
+ return out;
248
+ }
@@ -1,16 +1,16 @@
1
+ import path from 'node:path';
1
2
  import { OpraHttpClient } from '@opra/client';
2
- import { HttpApi } from '@opra/common';
3
- import chalk from 'chalk';
4
- import path from 'path';
3
+ import { BUILTIN, HttpApi } from '@opra/common';
4
+ import colors from 'ansi-colors';
5
5
  import { pascalCase } from 'putil-varhelpers';
6
- export async function processDocument(document, options) {
6
+ export async function generateDocument(document, options) {
7
7
  if (!document || typeof document === 'string') {
8
8
  if (document) {
9
9
  const out = this._documentsMap.get(document);
10
10
  if (out)
11
11
  return out;
12
12
  }
13
- this.emit('log', chalk.cyan('Fetching document schema from ') + chalk.blueBright(this.serviceUrl));
13
+ this.emit('log', colors.cyan('Fetching document schema from ') + colors.blueBright(this.serviceUrl));
14
14
  const client = new OpraHttpClient(this.serviceUrl);
15
15
  document = await client.fetchDocument({ documentId: document });
16
16
  }
@@ -23,15 +23,20 @@ export async function processDocument(document, options) {
23
23
  generator: this,
24
24
  };
25
25
  this._documentsMap.set(document.id, out);
26
- this.emit('log', chalk.white('[' + document.id + '] ') + chalk.cyan('Processing document ') + chalk.magenta(document.info.title));
26
+ this.emit('log', colors.white('[' + document.id + '] ') +
27
+ colors.cyan('Processing document ') +
28
+ colors.magenta(document.info.title || ''));
27
29
  if (document.references.size) {
28
- this.emit('log', chalk.white('[' + document.id + '] ') + chalk.cyan(`Processing references`));
30
+ let refIdGenerator = options?.refIdGenerator || 1;
31
+ this.emit('log', colors.white('[' + document.id + '] ') + colors.cyan(`Processing references`));
29
32
  for (const ref of document.references.values()) {
30
33
  const generator = this.extend();
31
34
  generator._document = ref;
32
- generator._documentRoot = '/references/' + (ref.info.title ? pascalCase(ref.info.title) : ref.id);
35
+ const typesNamespace = ref.api?.name || (ref.info.title ? pascalCase(ref.info.title) : `Reference${refIdGenerator++}`);
36
+ generator._documentRoot = '/references/' + typesNamespace;
33
37
  generator._typesRoot = path.join(generator._documentRoot, 'models');
34
- await generator.processDocument(ref, { typesOnly: true });
38
+ generator._typesNamespace = !this.options.referenceNamespaces || ref[BUILTIN] ? '' : typesNamespace;
39
+ await generator.generateDocument(ref, { typesOnly: true, refIdGenerator });
35
40
  }
36
41
  }
37
42
  this._fileHeaderDocInfo = `/*
@@ -41,15 +46,15 @@ export async function processDocument(document, options) {
41
46
  * ${this.serviceUrl}
42
47
  */`;
43
48
  if (document.types.size) {
44
- this.emit('log', chalk.white('[' + document.id + ']'), chalk.cyan(`Processing data types`));
49
+ this.emit('log', colors.white('[' + document.id + ']'), colors.cyan(`Processing data types`));
45
50
  for (const t of document.types.values()) {
46
- await this.processDataType(t);
51
+ await this.generateDataType(t, 'root');
47
52
  }
48
53
  }
49
54
  if (options?.typesOnly)
50
55
  return out;
51
56
  if (document.api instanceof HttpApi) {
52
- await this.processHttpApi(document.api);
57
+ await this.generateHttpApi(document.api);
53
58
  }
54
59
  return out;
55
60
  }
@@ -3,7 +3,7 @@ import { camelCase, pascalCase } from 'putil-varhelpers';
3
3
  import { CodeBlock } from '../../code-block.js';
4
4
  import { httpControllerNodeScript } from '../http-controller-node.js';
5
5
  import { wrapJSDocString } from '../utils/string-utils.js';
6
- export async function processHttpApi(api) {
6
+ export async function generateHttpApi(api) {
7
7
  let file = this._filesMap.get(api);
8
8
  if (file)
9
9
  return file;
@@ -28,7 +28,7 @@ export async function processHttpApi(api) {
28
28
  classConstBlock.tail = `\b\n}\n`;
29
29
  for (const controller of api.controllers.values()) {
30
30
  const generator = this.extend();
31
- const f = await generator.processHttpController(controller);
31
+ const f = await generator.generateHttpController(controller);
32
32
  const childClassName = pascalCase(controller.name) + 'Controller';
33
33
  file.addImport('.' + f.filename, [childClassName]);
34
34
  const property = '$' + controller.name.charAt(0).toLowerCase() + camelCase(controller.name.substring(1));
@@ -0,0 +1,292 @@
1
+ import path from 'node:path';
2
+ import typeIs from '@browsery/type-is';
3
+ import { ComplexType, HttpController, MimeTypes } from '@opra/common';
4
+ import { camelCase, pascalCase } from 'putil-varhelpers';
5
+ import { CodeBlock } from '../../code-block.js';
6
+ import { locateNamedType } from '../utils/locate-named-type.js';
7
+ import { wrapJSDocString } from '../utils/string-utils.js';
8
+ export async function generateHttpController(controller) {
9
+ let file = this._filesMap.get(controller);
10
+ if (file)
11
+ return file;
12
+ const generateParamDoc = async (name, type, options) => {
13
+ let typeDef;
14
+ if (type) {
15
+ const xt = await this.generateDataType(type, 'typeDef', file);
16
+ typeDef = xt.kind === 'embedded' ? 'object' : xt.typeName;
17
+ }
18
+ else
19
+ typeDef = 'any';
20
+ if (options?.isArray)
21
+ typeDef += '[]';
22
+ let out = `\n * @param {${typeDef}} ` + (options?.required ? name : `[${name}]`);
23
+ if (options?.description)
24
+ out += ` - ${wrapJSDocString(options?.description)}`;
25
+ if (type instanceof ComplexType && type.embedded) {
26
+ for (const f of type.fields.values()) {
27
+ out += await generateParamDoc(name + '.' + f.name, f.type, f);
28
+ }
29
+ }
30
+ return out;
31
+ };
32
+ const className = pascalCase(controller.name) + 'Controller';
33
+ file = this.addFile(path.join(this._apiPath, className + '.ts'));
34
+ file.addImport('@opra/client', ['HttpRequestObservable', 'kClient', 'OpraHttpClient']);
35
+ file.addImport(path.relative(file.dirname, '/http-controller-node.ts'), ['HttpControllerNode']);
36
+ const classBlock = (file.code[className] = new CodeBlock());
37
+ classBlock.doc = `/**
38
+ * ${wrapJSDocString(controller.description || '')}
39
+ * @class ${className}
40
+ * @apiUrl ${path.posix.join(this.serviceUrl, controller.getFullUrl())}
41
+ */`;
42
+ classBlock.head = `\nexport class ${className} extends HttpControllerNode {\n\t`;
43
+ classBlock.properties = '';
44
+ const classConstBlock = (classBlock.classConstBlock = new CodeBlock());
45
+ classConstBlock.head = `\n/**
46
+ * @param {OpraHttpClient} client - OpraHttpClient instance to operate
47
+ * @constructor
48
+ */
49
+ constructor(client: OpraHttpClient) {`;
50
+ classConstBlock.body = `\n\tsuper(client);`;
51
+ classConstBlock.tail = `\b\n}\n`;
52
+ if (controller.controllers.size) {
53
+ for (const child of controller.controllers.values()) {
54
+ const generator = this.extend();
55
+ generator._apiPath = path.join(this._apiPath, className);
56
+ const f = await generator.generateHttpController(child);
57
+ const childClassName = pascalCase(child.name) + 'Controller';
58
+ file.addImport(f.filename, [childClassName]);
59
+ const property = '$' + child.name.charAt(0).toLowerCase() + camelCase(child.name.substring(1));
60
+ classBlock.properties += `\nreadonly ${property}: ${childClassName};`;
61
+ classConstBlock.body += `\nthis.${property} = new ${childClassName}(client);`;
62
+ }
63
+ }
64
+ /** Process operations */
65
+ const mergedControllerParams = [...controller.parameters];
66
+ let _base = controller;
67
+ while (_base.owner instanceof HttpController) {
68
+ _base = _base.owner;
69
+ mergedControllerParams.unshift(..._base?.parameters);
70
+ }
71
+ for (const operation of controller.operations.values()) {
72
+ const mergedParams = [...mergedControllerParams, ...operation.parameters];
73
+ const operationBlock = (classBlock['operation_' + operation.name] = new CodeBlock());
74
+ operationBlock.doc = new CodeBlock();
75
+ operationBlock.doc.header = `
76
+ /**
77
+ * ${wrapJSDocString(operation.description || operation.name + ' operation')}`;
78
+ operationBlock.doc.parameters = new CodeBlock();
79
+ if (mergedParams.length) {
80
+ const block = new CodeBlock();
81
+ block.doc = '\n *\n * RegExp parameters:';
82
+ let i = 0;
83
+ for (const prm of operation.parameters) {
84
+ if (!(prm.name instanceof RegExp))
85
+ continue;
86
+ i++;
87
+ block.doc +=
88
+ `\n * > ${String(prm.name)} - ${prm.description || ''}` +
89
+ `\n * - location: ${prm.location}` +
90
+ `\n * - type: ${locateNamedType(prm.type)?.name || 'any'}${prm.isArray ? '[' + prm.arraySeparator + ']' : ''}` +
91
+ (prm.required ? `\n * required: ${prm.required}` : '') +
92
+ (prm.deprecated ? `\n * deprecated: ${prm.deprecated}` : '');
93
+ }
94
+ if (i)
95
+ operationBlock.doc.regExParameters = block;
96
+ }
97
+ operationBlock.doc.tail = `
98
+ * @apiUrl ${path.posix.join(this.serviceUrl, operation.getFullUrl())}
99
+ */\n`;
100
+ operationBlock.head = `${operation.name}(`;
101
+ /** Process operation parameters */
102
+ const pathParams = [];
103
+ const queryParams = [];
104
+ const headerParams = [];
105
+ if (mergedParams.length) {
106
+ const pathParamsMap = {};
107
+ const queryParamsMap = {};
108
+ const headerParamsMap = {};
109
+ for (const prm of mergedParams) {
110
+ if (typeof prm.name !== 'string')
111
+ continue;
112
+ if (prm.location === 'path')
113
+ pathParamsMap[prm.name] = prm;
114
+ if (prm.location === 'query')
115
+ queryParamsMap[prm.name] = prm;
116
+ if (prm.location === 'header')
117
+ headerParamsMap[prm.name] = prm;
118
+ }
119
+ pathParams.push(...Object.values(pathParamsMap));
120
+ queryParams.push(...Object.values(queryParamsMap));
121
+ headerParams.push(...Object.values(headerParamsMap));
122
+ }
123
+ /** Process path parameters and add as function arguments */
124
+ let argIndex = 0;
125
+ for (const prm of pathParams) {
126
+ let typeDef;
127
+ if (prm.type) {
128
+ const xt = await this.generateDataType(prm.type, 'typeDef', file);
129
+ typeDef = xt.kind === 'embedded' ? xt.code : xt.typeName;
130
+ }
131
+ else
132
+ typeDef = `any`;
133
+ if (prm.isArray)
134
+ typeDef += '[]';
135
+ if (argIndex++ > 0)
136
+ operationBlock.head += ', ';
137
+ operationBlock.head += `${prm.name}: ${typeDef}`;
138
+ operationBlock.doc.parameters +=
139
+ `\n * @param {${typeDef}} ` +
140
+ (prm.required ? prm.name : `[${prm.name}]`) +
141
+ (prm.description ? ' - ' + wrapJSDocString(prm.description || '') : '');
142
+ }
143
+ /** Process requestBody and add as function argument ($body) */
144
+ let hasBody = false;
145
+ if (operation.requestBody?.content.length) {
146
+ if (argIndex++ > 0)
147
+ operationBlock.head += ', ';
148
+ let typeArr = [];
149
+ for (const content of operation.requestBody.content) {
150
+ if (content.type) {
151
+ /** Generate JSDoc for parameter */
152
+ operationBlock.doc.parameters += await generateParamDoc('$body', content.type, {
153
+ required: operation.requestBody.required,
154
+ description: content.description || content.type.description,
155
+ });
156
+ /** */
157
+ const xt = await this.generateDataType(content.type, 'typeDef', file);
158
+ let typeDef = xt.kind === 'embedded' ? xt.code : xt.typeName;
159
+ if (typeDef === 'any') {
160
+ typeArr = [];
161
+ break;
162
+ }
163
+ if (xt.kind === 'named') {
164
+ if (operation.requestBody.partial) {
165
+ file.addImport('ts-gems', ['PartialDTO']);
166
+ typeDef = `PartialDTO<${typeDef}>`;
167
+ }
168
+ else {
169
+ file.addImport('ts-gems', ['DTO']);
170
+ typeDef = `DTO<${typeDef}>`;
171
+ }
172
+ }
173
+ if (typeDef && content.isArray)
174
+ typeDef += '[]';
175
+ typeDef = typeDef || 'undefined';
176
+ if (!typeArr.includes(typeDef))
177
+ typeArr.push(typeDef);
178
+ continue;
179
+ }
180
+ else if (content.contentType && typeIs.is(String(content.contentType), ['multipart/*'])) {
181
+ const typeDef = 'FormData';
182
+ if (!typeArr.includes(typeDef))
183
+ typeArr.push(typeDef);
184
+ continue;
185
+ }
186
+ typeArr = [];
187
+ break;
188
+ }
189
+ const typeDef = typeArr.join(' | ') || 'any';
190
+ operationBlock.head += `$body: ${typeDef}`;
191
+ // operationBlock.doc.parameters += `\n * @param {${typeDef}} $body - Http body` + bodyFields;
192
+ hasBody = true;
193
+ }
194
+ /** process query params */
195
+ const isQueryRequired = queryParams.find(p => p.required);
196
+ const isHeadersRequired = queryParams.find(p => p.required);
197
+ if (queryParams.length) {
198
+ if (argIndex++ > 0)
199
+ operationBlock.head += ', ';
200
+ operationBlock.head += '\n\t$params' + (isHeadersRequired || isQueryRequired ? '' : '?') + ': {\n\t';
201
+ operationBlock.doc.parameters += '\n * @param {object} $params - Available parameters for the operation';
202
+ let hasAdditionalFields = false;
203
+ for (const prm of queryParams) {
204
+ if (typeof prm.name !== 'string') {
205
+ hasAdditionalFields = true;
206
+ continue;
207
+ }
208
+ operationBlock.doc.parameters += await generateParamDoc('$params.' + prm.name, prm.type, prm);
209
+ operationBlock.head += `${prm.name}${prm.required ? '' : '?'}: `;
210
+ if (prm.type) {
211
+ const xt = await this.generateDataType(prm.type, 'typeDef', file);
212
+ let typeDef = xt.kind === 'embedded' ? xt.code : xt.typeName;
213
+ if (prm.isArray)
214
+ typeDef += '[]';
215
+ operationBlock.head += `${typeDef};\n`;
216
+ }
217
+ else
218
+ operationBlock.head += `any;\n`;
219
+ }
220
+ if (hasAdditionalFields) {
221
+ operationBlock.head += '[index: string]: any;\n';
222
+ }
223
+ operationBlock.head += '\b}\b';
224
+ }
225
+ /** process header params */
226
+ if (headerParams.length) {
227
+ if (argIndex++ > 0)
228
+ operationBlock.head += ', \n';
229
+ operationBlock.head += '\t$headers' + (isHeadersRequired ? '' : '?') + ': {\n\t';
230
+ for (const prm of headerParams) {
231
+ operationBlock.head += `/**\n * ${prm.description || ''}\n */\n`;
232
+ operationBlock.head += `${prm.name}${prm.required ? '' : '?'}: `;
233
+ if (prm.type) {
234
+ const xt = await this.generateDataType(prm.type, 'typeDef', file);
235
+ let typeDef = xt.kind === 'embedded' ? xt.code : xt.typeName;
236
+ if (prm.isArray)
237
+ typeDef += '[]';
238
+ operationBlock.head += `${typeDef};\n`;
239
+ }
240
+ else
241
+ operationBlock.head += `any;\n`;
242
+ }
243
+ operationBlock.head += '\b}\b';
244
+ }
245
+ /* Determine return type */
246
+ const returnTypes = [];
247
+ let typeDef = '';
248
+ for (const resp of operation.responses) {
249
+ if (!resp.statusCode.find(r => r.intersects(200, 299)))
250
+ continue;
251
+ typeDef = '';
252
+ if (resp.type) {
253
+ const xt = await this.generateDataType(resp.type, 'typeDef', file);
254
+ typeDef = xt.kind === 'embedded' ? xt.code : xt.typeName;
255
+ }
256
+ if (typeDef) {
257
+ if (typeDef !== 'OperationResult') {
258
+ if (resp.partial) {
259
+ file.addImport('ts-gems', ['PartialDTO']);
260
+ typeDef = `PartialDTO<${typeDef}>`;
261
+ }
262
+ else {
263
+ file.addImport('ts-gems', ['DTO']);
264
+ typeDef = `DTO<${typeDef}>`;
265
+ }
266
+ }
267
+ }
268
+ if (typeDef && resp.isArray)
269
+ typeDef += '[]';
270
+ if (resp.contentType && typeIs.is(String(resp.contentType), [MimeTypes.opra_response_json])) {
271
+ file.addImport('@opra/common', ['OperationResult']);
272
+ typeDef = typeDef ? `OperationResult<${typeDef}>` : 'OperationResult';
273
+ }
274
+ typeDef = typeDef || 'undefined';
275
+ if (!returnTypes.includes(typeDef))
276
+ returnTypes.push(typeDef);
277
+ }
278
+ operationBlock.head += `\n): HttpRequestObservable<${returnTypes.join(' | ') || 'any'}>{`;
279
+ operationBlock.body = `\n\t`;
280
+ operationBlock.body +=
281
+ `const url = this._prepareUrl('${operation.getFullUrl()}', {` + pathParams.map(p => p.name).join(', ') + '});';
282
+ operationBlock.body +=
283
+ `\nreturn this[kClient].request(url, { method: '${operation.method}'` +
284
+ (hasBody ? ', body: $body' : '') +
285
+ (queryParams.length ? ', params: $params as any' : '') +
286
+ (headerParams.length ? ', headers: $headers as any' : '') +
287
+ '});';
288
+ operationBlock.tail = `\b\n};\n`;
289
+ }
290
+ classBlock.tail = `\b}`;
291
+ return file;
292
+ }