@intrig/plugin-nest 0.0.2-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.
- package/dist/index.cjs +719 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +694 -0
- package/package.json +28 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var pluginSdk = require('@intrig/plugin-sdk');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var fsx = require('fs-extra');
|
|
8
|
+
var chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
function _interopNamespaceDefault(e) {
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
28
|
+
var fsx__namespace = /*#__PURE__*/_interopNamespaceDefault(fsx);
|
|
29
|
+
|
|
30
|
+
class InternalGeneratorContext {
|
|
31
|
+
constructor(){
|
|
32
|
+
this.codeGenerationBreakdown = {};
|
|
33
|
+
}
|
|
34
|
+
getCounter(sourceId) {
|
|
35
|
+
this.codeGenerationBreakdown[sourceId] = this.codeGenerationBreakdown[sourceId] || new pluginSdk.StatsCounter(sourceId);
|
|
36
|
+
return this.codeGenerationBreakdown[sourceId];
|
|
37
|
+
}
|
|
38
|
+
getCounters() {
|
|
39
|
+
return [
|
|
40
|
+
...Object.values(this.codeGenerationBreakdown)
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function packageJsonTemplate(ctx) {
|
|
46
|
+
const projectDir = ctx.rootDir ?? process.cwd();
|
|
47
|
+
const packageJson = fsx__namespace.readJsonSync(path__namespace.resolve(projectDir, 'package.json'));
|
|
48
|
+
const json = pluginSdk.jsonLiteral(path__namespace.resolve('package.json'));
|
|
49
|
+
return json`
|
|
50
|
+
{
|
|
51
|
+
"name": "@intrig/generated",
|
|
52
|
+
"version": "d${Date.now()}",
|
|
53
|
+
"private": true,
|
|
54
|
+
"main": "dist/index.js",
|
|
55
|
+
"types": "dist/index.d.ts",
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "swc src -d dist --copy-files --strip-leading-paths && tsc --emitDeclarationOnly"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@nestjs/common": "^10.0.0",
|
|
61
|
+
"@nestjs/axios": "^3.0.0",
|
|
62
|
+
"axios": "^1.7.7",
|
|
63
|
+
"rxjs": "^7.8.0",
|
|
64
|
+
"zod": "^3.23.8"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@swc/cli": "^0.7.7",
|
|
68
|
+
"@swc/core": "^1.12.6",
|
|
69
|
+
"@types/node": "^24.0.4",
|
|
70
|
+
"typescript": "${packageJson.devDependencies?.typescript ?? packageJson.dependencies?.typescript ?? '^5.0.0'}"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"@nestjs/common": "^10.0.0",
|
|
74
|
+
"@nestjs/axios": "^3.0.0",
|
|
75
|
+
"rxjs": "^7.0.0"
|
|
76
|
+
},
|
|
77
|
+
"_moduleAliases": {
|
|
78
|
+
"@intrig/nest": "./src"
|
|
79
|
+
},
|
|
80
|
+
"type": "module",
|
|
81
|
+
"exports": {
|
|
82
|
+
".": {
|
|
83
|
+
"import": "./src/index.js",
|
|
84
|
+
"require": "./src/index.js",
|
|
85
|
+
"types": "./src/index.d.ts"
|
|
86
|
+
},
|
|
87
|
+
"./*": {
|
|
88
|
+
"import": "./src/*.js",
|
|
89
|
+
"require": "./src/*.js",
|
|
90
|
+
"types": "./src/*.d.ts"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"typesVersions": {
|
|
94
|
+
"*": {
|
|
95
|
+
"*": ["src/*"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function indexTemplate(serviceNames) {
|
|
103
|
+
const ts = pluginSdk.typescript(path__namespace.resolve("src", "index.ts"));
|
|
104
|
+
return ts`
|
|
105
|
+
export * from './intrig.module';
|
|
106
|
+
${serviceNames.map((name)=>`export * from './${name}/${name}.service';`).join('\n')}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function nestTsConfigTemplate() {
|
|
111
|
+
const json = pluginSdk.jsonLiteral(path__namespace.resolve('tsconfig.json'));
|
|
112
|
+
return json`
|
|
113
|
+
{
|
|
114
|
+
"compilerOptions": {
|
|
115
|
+
"target": "ES2021",
|
|
116
|
+
"module": "ESNext",
|
|
117
|
+
"declaration": true,
|
|
118
|
+
"outDir": "./dist",
|
|
119
|
+
"strict": true,
|
|
120
|
+
"esModuleInterop": true,
|
|
121
|
+
"noImplicitAny": false,
|
|
122
|
+
"moduleResolution": "node",
|
|
123
|
+
"baseUrl": "./",
|
|
124
|
+
"paths": {
|
|
125
|
+
"@intrig/nest": [
|
|
126
|
+
"./src"
|
|
127
|
+
],
|
|
128
|
+
"@intrig/nest/*": [
|
|
129
|
+
"./src/*"
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"skipLibCheck": true,
|
|
133
|
+
"experimentalDecorators": true,
|
|
134
|
+
"emitDecoratorMetadata": true
|
|
135
|
+
},
|
|
136
|
+
"exclude": [
|
|
137
|
+
"node_modules",
|
|
138
|
+
"../../node_modules",
|
|
139
|
+
"**/__tests__/*"
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function moduleTemplate(serviceNames, sources) {
|
|
146
|
+
const ts = pluginSdk.typescript(path__namespace.resolve("src", "intrig.module.ts"));
|
|
147
|
+
return ts`
|
|
148
|
+
import { Module } from '@nestjs/common';
|
|
149
|
+
import { HttpModule } from '@nestjs/axios';
|
|
150
|
+
${serviceNames.map((name)=>`import { ${pluginSdk.pascalCase(name)}Service } from './${name}/${name}.service';`).join('\n')}
|
|
151
|
+
|
|
152
|
+
@Module({
|
|
153
|
+
imports: [
|
|
154
|
+
HttpModule.register({
|
|
155
|
+
timeout: 60000,
|
|
156
|
+
maxRedirects: 5,
|
|
157
|
+
}),
|
|
158
|
+
],
|
|
159
|
+
providers: [${serviceNames.map((name)=>`${pluginSdk.pascalCase(name)}Service`).join(', ')}],
|
|
160
|
+
exports: [${serviceNames.map((name)=>`${pluginSdk.pascalCase(name)}Service`).join(', ')}],
|
|
161
|
+
})
|
|
162
|
+
export class IntrigModule {}
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function extractMethodSignature(descriptor, imports, source) {
|
|
167
|
+
const { data } = descriptor;
|
|
168
|
+
const params = [];
|
|
169
|
+
// Path and query parameters
|
|
170
|
+
if (data.variables && data.variables.length > 0) {
|
|
171
|
+
const pathParams = data.variables.filter((v)=>v.in === 'path');
|
|
172
|
+
const queryParams = data.variables.filter((v)=>v.in === 'query');
|
|
173
|
+
pathParams.forEach((param)=>{
|
|
174
|
+
const typeName = param.ref.split('/').pop() || 'any';
|
|
175
|
+
imports.add(`import { ${typeName} } from '../components/schemas/${typeName}';`);
|
|
176
|
+
params.push(`${pluginSdk.camelCase(param.name)}: ${typeName}`);
|
|
177
|
+
});
|
|
178
|
+
queryParams.forEach((param)=>{
|
|
179
|
+
const typeName = param.ref.split('/').pop() || 'any';
|
|
180
|
+
imports.add(`import { ${typeName} } from '../components/schemas/${typeName}';`);
|
|
181
|
+
params.push(`${pluginSdk.camelCase(param.name)}?: ${typeName}`);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// Request body
|
|
185
|
+
let bodyParam;
|
|
186
|
+
if (data.requestBody) {
|
|
187
|
+
const bodyType = data.requestBody;
|
|
188
|
+
imports.add(`import { ${bodyType} } from '../components/schemas/${bodyType}';`);
|
|
189
|
+
bodyParam = `data: ${bodyType}`;
|
|
190
|
+
params.push(bodyParam);
|
|
191
|
+
}
|
|
192
|
+
// Return type
|
|
193
|
+
let returnType = 'void';
|
|
194
|
+
if (data.response) {
|
|
195
|
+
returnType = data.response;
|
|
196
|
+
imports.add(`import { ${returnType} } from '../components/schemas/${returnType}';`);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
params,
|
|
200
|
+
returnType,
|
|
201
|
+
bodyParam
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function buildUrlExpression(descriptor) {
|
|
205
|
+
const { data } = descriptor;
|
|
206
|
+
const urlPath = data.paths[0] || '';
|
|
207
|
+
if (!data.variables || data.variables.length === 0) {
|
|
208
|
+
return `'${urlPath}'`;
|
|
209
|
+
}
|
|
210
|
+
const pathParams = data.variables.filter((v)=>v.in === 'path');
|
|
211
|
+
if (pathParams.length === 0) {
|
|
212
|
+
return `'${urlPath}'`;
|
|
213
|
+
}
|
|
214
|
+
// Replace {param} with ${param}
|
|
215
|
+
let urlTemplate = urlPath;
|
|
216
|
+
pathParams.forEach((param)=>{
|
|
217
|
+
urlTemplate = urlTemplate.replace(`{${param.name}}`, `\${${pluginSdk.camelCase(param.name)}}`);
|
|
218
|
+
});
|
|
219
|
+
return `\`${urlTemplate}\``;
|
|
220
|
+
}
|
|
221
|
+
function buildRequestConfig(descriptor) {
|
|
222
|
+
const { data } = descriptor;
|
|
223
|
+
const configParts = [];
|
|
224
|
+
// Query parameters
|
|
225
|
+
if (data.variables && data.variables.some((v)=>v.in === 'query')) {
|
|
226
|
+
const queryParams = data.variables.filter((v)=>v.in === 'query');
|
|
227
|
+
const paramsObj = queryParams.map((p)=>`${p.name}: ${pluginSdk.camelCase(p.name)}`).join(', ');
|
|
228
|
+
configParts.push(`params: { ${paramsObj} }`);
|
|
229
|
+
}
|
|
230
|
+
// Headers
|
|
231
|
+
if (data.contentType) {
|
|
232
|
+
configParts.push(`headers: { 'Content-Type': '${data.contentType}' }`);
|
|
233
|
+
}
|
|
234
|
+
if (configParts.length === 0) {
|
|
235
|
+
return '';
|
|
236
|
+
}
|
|
237
|
+
return `, { ${configParts.join(', ')} }`;
|
|
238
|
+
}
|
|
239
|
+
function generateMethodTemplate(descriptor, imports, source) {
|
|
240
|
+
const { data } = descriptor;
|
|
241
|
+
const methodName = pluginSdk.camelCase(data.operationId);
|
|
242
|
+
const signature = extractMethodSignature(descriptor, imports);
|
|
243
|
+
const urlExpression = buildUrlExpression(descriptor);
|
|
244
|
+
const configExpression = buildRequestConfig(descriptor);
|
|
245
|
+
const httpMethod = data.method.toLowerCase();
|
|
246
|
+
const hasBody = data.requestBody !== undefined;
|
|
247
|
+
const hasResponse = data.response !== undefined;
|
|
248
|
+
let methodBody;
|
|
249
|
+
if (hasResponse) {
|
|
250
|
+
if (hasBody) {
|
|
251
|
+
// POST/PUT/PATCH with body and response
|
|
252
|
+
methodBody = `
|
|
253
|
+
async ${methodName}(${signature.params.join(', ')}): Promise<${signature.returnType}> {
|
|
254
|
+
const response = await firstValueFrom(
|
|
255
|
+
this.httpService.${httpMethod}<${signature.returnType}>(${urlExpression}, data${configExpression})
|
|
256
|
+
);
|
|
257
|
+
return response.data;
|
|
258
|
+
}`;
|
|
259
|
+
} else {
|
|
260
|
+
// GET/DELETE with response
|
|
261
|
+
methodBody = `
|
|
262
|
+
async ${methodName}(${signature.params.join(', ')}): Promise<${signature.returnType}> {
|
|
263
|
+
const response = await firstValueFrom(
|
|
264
|
+
this.httpService.${httpMethod}<${signature.returnType}>(${urlExpression}${configExpression})
|
|
265
|
+
);
|
|
266
|
+
return response.data;
|
|
267
|
+
}`;
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
if (hasBody) {
|
|
271
|
+
// POST/PUT/PATCH with body but no response
|
|
272
|
+
methodBody = `
|
|
273
|
+
async ${methodName}(${signature.params.join(', ')}): Promise<void> {
|
|
274
|
+
await firstValueFrom(
|
|
275
|
+
this.httpService.${httpMethod}(${urlExpression}, data${configExpression})
|
|
276
|
+
);
|
|
277
|
+
}`;
|
|
278
|
+
} else {
|
|
279
|
+
// GET/DELETE with no response
|
|
280
|
+
methodBody = `
|
|
281
|
+
async ${methodName}(${signature.params.join(', ')}): Promise<void> {
|
|
282
|
+
await firstValueFrom(
|
|
283
|
+
this.httpService.${httpMethod}(${urlExpression}${configExpression})
|
|
284
|
+
);
|
|
285
|
+
}`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return methodBody;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function serviceTemplate(serviceName, descriptors, source) {
|
|
292
|
+
const ts = pluginSdk.typescript(path__namespace.resolve('src', serviceName, `${serviceName}.service.ts`));
|
|
293
|
+
const imports = new Set();
|
|
294
|
+
const methods = [];
|
|
295
|
+
for (const descriptor of descriptors){
|
|
296
|
+
const methodCode = generateMethodTemplate(descriptor, imports);
|
|
297
|
+
methods.push(methodCode);
|
|
298
|
+
}
|
|
299
|
+
return ts`
|
|
300
|
+
import { Injectable } from '@nestjs/common';
|
|
301
|
+
import { HttpService } from '@nestjs/axios';
|
|
302
|
+
import { firstValueFrom } from 'rxjs';
|
|
303
|
+
${[
|
|
304
|
+
...imports
|
|
305
|
+
].join('\n')}
|
|
306
|
+
|
|
307
|
+
@Injectable()
|
|
308
|
+
export class ${pluginSdk.pascalCase(serviceName)}Service {
|
|
309
|
+
constructor(private readonly httpService: HttpService) {}
|
|
310
|
+
|
|
311
|
+
${methods.join('\n\n')}
|
|
312
|
+
}
|
|
313
|
+
`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function typeTemplate(descriptor) {
|
|
317
|
+
const { data: { schema, name: typeName }, source } = descriptor;
|
|
318
|
+
const { imports, tsType } = openApiSchemaToTypeScript(schema);
|
|
319
|
+
const ts = pluginSdk.typescript(path__namespace.resolve('src', 'components', 'schemas', `${typeName}.ts`));
|
|
320
|
+
const simpleType = (await pluginSdk.jsonLiteral('')`${JSON.stringify(schema)}`).content;
|
|
321
|
+
return ts`
|
|
322
|
+
${[
|
|
323
|
+
...imports
|
|
324
|
+
].join('\n')}
|
|
325
|
+
|
|
326
|
+
//--- TypeScript Type ---//
|
|
327
|
+
|
|
328
|
+
export type ${typeName} = ${tsType}
|
|
329
|
+
|
|
330
|
+
//--- JSON Schema ---//
|
|
331
|
+
|
|
332
|
+
export const ${typeName}_jsonschema = ${JSON.stringify(schema) ?? "{}"}
|
|
333
|
+
|
|
334
|
+
//--- Simple Type ---//
|
|
335
|
+
/*[${simpleType}]*/
|
|
336
|
+
`;
|
|
337
|
+
}
|
|
338
|
+
function isRef(schema) {
|
|
339
|
+
return '$ref' in (schema ?? {});
|
|
340
|
+
}
|
|
341
|
+
// Helper function to convert OpenAPI schema types to TypeScript types
|
|
342
|
+
function openApiSchemaToTypeScript(schema, imports = new Set()) {
|
|
343
|
+
if (!schema) {
|
|
344
|
+
return {
|
|
345
|
+
tsType: 'any',
|
|
346
|
+
imports: new Set()
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (isRef(schema)) {
|
|
350
|
+
return handleRefSchema(schema.$ref, imports);
|
|
351
|
+
}
|
|
352
|
+
if (!schema.type) {
|
|
353
|
+
if ('properties' in schema) {
|
|
354
|
+
schema.type = 'object';
|
|
355
|
+
} else if ('items' in schema) {
|
|
356
|
+
schema.type = 'array';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
switch(schema.type){
|
|
360
|
+
case 'string':
|
|
361
|
+
return handleStringSchema(schema);
|
|
362
|
+
case 'number':
|
|
363
|
+
return handleNumberSchema();
|
|
364
|
+
case 'integer':
|
|
365
|
+
return handleIntegerSchema();
|
|
366
|
+
case 'boolean':
|
|
367
|
+
return handleBooleanSchema();
|
|
368
|
+
case 'array':
|
|
369
|
+
return handleArraySchema(schema, imports);
|
|
370
|
+
case 'object':
|
|
371
|
+
return handleObjectSchema(schema, imports);
|
|
372
|
+
default:
|
|
373
|
+
return handleComplexSchema(schema, imports);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function handleRefSchema(ref, imports) {
|
|
377
|
+
const refParts = ref.split('/');
|
|
378
|
+
const refName = refParts[refParts.length - 1];
|
|
379
|
+
imports.add(`import { ${refName} } from './${refName}';`);
|
|
380
|
+
return {
|
|
381
|
+
tsType: refName,
|
|
382
|
+
imports
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function handleStringSchema(schema) {
|
|
386
|
+
if (schema.enum) {
|
|
387
|
+
const enumValues = schema.enum.map((value)=>`'${value}'`).join(' | ');
|
|
388
|
+
return {
|
|
389
|
+
tsType: enumValues,
|
|
390
|
+
imports: new Set()
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
let tsType = 'string';
|
|
394
|
+
if (schema.format === 'date' || schema.format === 'date-time') {
|
|
395
|
+
tsType = 'Date';
|
|
396
|
+
} else if (schema.format === 'binary') {
|
|
397
|
+
tsType = 'Buffer';
|
|
398
|
+
} else if (schema.format === 'byte') {
|
|
399
|
+
tsType = 'string'; // base64 encoded string
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
tsType,
|
|
403
|
+
imports: new Set()
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function handleNumberSchema() {
|
|
407
|
+
return {
|
|
408
|
+
tsType: 'number',
|
|
409
|
+
imports: new Set()
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function handleIntegerSchema() {
|
|
413
|
+
return {
|
|
414
|
+
tsType: 'number',
|
|
415
|
+
imports: new Set()
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function handleBooleanSchema() {
|
|
419
|
+
return {
|
|
420
|
+
tsType: 'boolean',
|
|
421
|
+
imports: new Set()
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function handleArraySchema(schema, imports) {
|
|
425
|
+
if (!schema.items) {
|
|
426
|
+
throw new Error('Array schema must have an items property');
|
|
427
|
+
}
|
|
428
|
+
const { tsType, imports: itemImports } = openApiSchemaToTypeScript(schema.items, imports);
|
|
429
|
+
return {
|
|
430
|
+
tsType: `(${tsType})[]`,
|
|
431
|
+
imports: new Set([
|
|
432
|
+
...imports,
|
|
433
|
+
...itemImports
|
|
434
|
+
])
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function handleObjectSchema(schema, imports) {
|
|
438
|
+
const updatedRequiredFields = schema.required || [];
|
|
439
|
+
if (schema.properties) {
|
|
440
|
+
const propertiesTs = Object.entries(schema.properties).map(([key, value])=>{
|
|
441
|
+
const { tsType, optional } = openApiSchemaToTypeScript(value);
|
|
442
|
+
const isRequired = !optional && updatedRequiredFields.includes(key);
|
|
443
|
+
return `${key}${isRequired ? '' : '?'}: ${tsType} ${isRequired ? '' : ' | null'};`;
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
tsType: `{ ${propertiesTs.join(' ')} }`,
|
|
447
|
+
imports
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
tsType: 'any',
|
|
452
|
+
imports: new Set(),
|
|
453
|
+
optional: true
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function handleComplexSchema(schema, imports) {
|
|
457
|
+
if (schema.oneOf) {
|
|
458
|
+
const options = schema.oneOf.map((subSchema)=>openApiSchemaToTypeScript(subSchema));
|
|
459
|
+
const tsTypes = options.map((option)=>option.tsType);
|
|
460
|
+
return {
|
|
461
|
+
tsType: tsTypes.join(' | '),
|
|
462
|
+
imports: new Set([
|
|
463
|
+
...imports,
|
|
464
|
+
...options.flatMap((option)=>Array.from(option.imports))
|
|
465
|
+
])
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (schema.anyOf) {
|
|
469
|
+
const options = schema.anyOf.map((subSchema)=>openApiSchemaToTypeScript(subSchema));
|
|
470
|
+
const tsTypes = options.map((option)=>option.tsType);
|
|
471
|
+
return {
|
|
472
|
+
tsType: tsTypes.join(' | '),
|
|
473
|
+
imports: new Set([
|
|
474
|
+
...imports,
|
|
475
|
+
...options.flatMap((option)=>Array.from(option.imports))
|
|
476
|
+
])
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (schema.allOf) {
|
|
480
|
+
const options = schema.allOf.map((subSchema)=>openApiSchemaToTypeScript(subSchema));
|
|
481
|
+
const tsTypes = options.map((option)=>option.tsType);
|
|
482
|
+
return {
|
|
483
|
+
tsType: tsTypes.join(' & '),
|
|
484
|
+
imports: new Set([
|
|
485
|
+
...imports,
|
|
486
|
+
...options.flatMap((option)=>Array.from(option.imports))
|
|
487
|
+
])
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
tsType: 'any',
|
|
492
|
+
imports
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Extract service name from operationId or path
|
|
498
|
+
* Examples:
|
|
499
|
+
* - getUser -> users
|
|
500
|
+
* - createPost -> posts
|
|
501
|
+
* - listProducts -> products
|
|
502
|
+
* - /api/users/{id} -> users
|
|
503
|
+
*/ function extractServiceName(descriptor) {
|
|
504
|
+
const operationId = descriptor.data.operationId;
|
|
505
|
+
// Try to extract from operationId first
|
|
506
|
+
// Common patterns: getUser, createUser, listUsers, getUserById
|
|
507
|
+
const operationPrefixes = [
|
|
508
|
+
'get',
|
|
509
|
+
'post',
|
|
510
|
+
'put',
|
|
511
|
+
'patch',
|
|
512
|
+
'delete',
|
|
513
|
+
'create',
|
|
514
|
+
'update',
|
|
515
|
+
'list',
|
|
516
|
+
'fetch',
|
|
517
|
+
'remove'
|
|
518
|
+
];
|
|
519
|
+
for (const prefix of operationPrefixes){
|
|
520
|
+
if (operationId.toLowerCase().startsWith(prefix)) {
|
|
521
|
+
const rest = operationId.substring(prefix.length);
|
|
522
|
+
if (rest.length > 0) {
|
|
523
|
+
// Extract the resource name (e.g., "User" from "getUser")
|
|
524
|
+
const resourceName = rest.replace(/By.*$/, ''); // Remove "ById", "ByName", etc.
|
|
525
|
+
return pluginSdk.camelCase(resourceName.endsWith('s') ? resourceName : resourceName + 's');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Fallback: extract from path
|
|
530
|
+
const path = descriptor.data.paths[0] || '';
|
|
531
|
+
const pathSegments = path.split('/').filter((s)=>s && !s.startsWith('{'));
|
|
532
|
+
if (pathSegments.length > 0) {
|
|
533
|
+
// Take the first meaningful segment
|
|
534
|
+
const segment = pathSegments[0];
|
|
535
|
+
return pluginSdk.camelCase(segment);
|
|
536
|
+
}
|
|
537
|
+
// Ultimate fallback: use source name
|
|
538
|
+
return pluginSdk.camelCase(descriptor.source);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Group REST descriptors by service name
|
|
542
|
+
*/ function groupDescriptorsByService(descriptors) {
|
|
543
|
+
const grouped = {};
|
|
544
|
+
for (const descriptor of descriptors){
|
|
545
|
+
const serviceName = extractServiceName(descriptor);
|
|
546
|
+
if (!grouped[serviceName]) {
|
|
547
|
+
grouped[serviceName] = [];
|
|
548
|
+
}
|
|
549
|
+
grouped[serviceName].push(descriptor);
|
|
550
|
+
}
|
|
551
|
+
return grouped;
|
|
552
|
+
}
|
|
553
|
+
async function generateCode(ctx) {
|
|
554
|
+
// Root/project files
|
|
555
|
+
await ctx.dump(packageJsonTemplate(ctx));
|
|
556
|
+
await ctx.dump(nestTsConfigTemplate());
|
|
557
|
+
const internalGeneratorContext = new InternalGeneratorContext();
|
|
558
|
+
// Group REST descriptors by service
|
|
559
|
+
const groupedDescriptors = groupDescriptorsByService(ctx.restDescriptors);
|
|
560
|
+
const serviceNames = Object.keys(groupedDescriptors).sort();
|
|
561
|
+
// Generate module and index files
|
|
562
|
+
await ctx.dump(moduleTemplate(serviceNames, ctx.sources));
|
|
563
|
+
await ctx.dump(indexTemplate(serviceNames));
|
|
564
|
+
// Generate service files
|
|
565
|
+
for (const [serviceName, descriptors] of Object.entries(groupedDescriptors)){
|
|
566
|
+
const source = descriptors[0]?.source || 'default';
|
|
567
|
+
await ctx.dump(serviceTemplate(serviceName, descriptors));
|
|
568
|
+
// Track statistics
|
|
569
|
+
const counter = internalGeneratorContext.getCounter(source);
|
|
570
|
+
counter.inc('services');
|
|
571
|
+
descriptors.forEach(()=>counter.inc('methods'));
|
|
572
|
+
}
|
|
573
|
+
// Generate type files
|
|
574
|
+
for (const schemaDescriptor of ctx.schemaDescriptors){
|
|
575
|
+
await ctx.dump(typeTemplate(schemaDescriptor));
|
|
576
|
+
// Track statistics
|
|
577
|
+
const counter = internalGeneratorContext.getCounter(schemaDescriptor.source);
|
|
578
|
+
counter.inc('types');
|
|
579
|
+
}
|
|
580
|
+
return internalGeneratorContext.getCounters();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async function schemaTypescriptDoc(result) {
|
|
584
|
+
const { data: { name, schema }, source } = result;
|
|
585
|
+
const { tsType } = openApiSchemaToTypeScript(result.data.schema);
|
|
586
|
+
const ts = pluginSdk.typescript(path__namespace.resolve('src', source, 'temp', name, `${name}.ts`));
|
|
587
|
+
const importContent = await ts`
|
|
588
|
+
import { ${name} } from '@intrig/nest/components/schemas/${name}';
|
|
589
|
+
`;
|
|
590
|
+
const md = pluginSdk.markdown('');
|
|
591
|
+
return md`
|
|
592
|
+
\`\`\`typescript
|
|
593
|
+
${importContent.content}
|
|
594
|
+
|
|
595
|
+
type Example = ${tsType}
|
|
596
|
+
\`\`\`
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
async function schemaJsonSchemaDoc(result) {
|
|
600
|
+
const { data: { name, schema }, source } = result;
|
|
601
|
+
const ts = pluginSdk.typescript(path__namespace.resolve('src', source, 'temp', name, `${name}.ts`));
|
|
602
|
+
const importContent = await ts`
|
|
603
|
+
import { ${name}_jsonschema } from '@intrig/nest/components/schemas/${name}';
|
|
604
|
+
`;
|
|
605
|
+
const md = pluginSdk.markdown('');
|
|
606
|
+
return md`
|
|
607
|
+
\`\`\`typescript
|
|
608
|
+
${importContent.content}
|
|
609
|
+
|
|
610
|
+
console.log(${name}_jsonschema)
|
|
611
|
+
\`\`\`
|
|
612
|
+
|
|
613
|
+
\`\`\`json
|
|
614
|
+
${JSON.stringify(schema, null, 2)}
|
|
615
|
+
\`\`\`
|
|
616
|
+
`;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function getSchemaDocumentation(result) {
|
|
620
|
+
const tabs = [];
|
|
621
|
+
tabs.push({
|
|
622
|
+
name: 'TypeScript Type',
|
|
623
|
+
content: (await schemaTypescriptDoc(result)).content
|
|
624
|
+
});
|
|
625
|
+
tabs.push({
|
|
626
|
+
name: 'JSON Schema',
|
|
627
|
+
content: (await schemaJsonSchemaDoc(result)).content
|
|
628
|
+
});
|
|
629
|
+
return tabs;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async function nestServiceMethodDocs(result) {
|
|
633
|
+
const { data: { operationId, method, paths, requestBody, response, variables }, source } = result;
|
|
634
|
+
const methodName = pluginSdk.camelCase(operationId);
|
|
635
|
+
const ts = pluginSdk.typescript(path__namespace.resolve('src', source, 'temp', operationId, `${operationId}.ts`));
|
|
636
|
+
// Build example usage
|
|
637
|
+
let exampleParams = [];
|
|
638
|
+
let exampleCall = '';
|
|
639
|
+
if (variables && variables.length > 0) {
|
|
640
|
+
variables.forEach((v)=>{
|
|
641
|
+
exampleParams.push(`// ${v.in} parameter\nconst ${pluginSdk.camelCase(v.name)} = ...;`);
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (requestBody) {
|
|
645
|
+
exampleParams.push(`// request body\nconst data = ...;`);
|
|
646
|
+
}
|
|
647
|
+
const callParams = [
|
|
648
|
+
...variables?.map((v)=>pluginSdk.camelCase(v.name)) || [],
|
|
649
|
+
requestBody ? 'data' : ''
|
|
650
|
+
].filter(Boolean);
|
|
651
|
+
exampleCall = `const result = await service.${methodName}(${callParams.join(', ')});`;
|
|
652
|
+
const importContent = await ts`
|
|
653
|
+
import { SomeService } from '@intrig/nest';
|
|
654
|
+
|
|
655
|
+
// Inject the service in your controller or another service
|
|
656
|
+
constructor(private readonly service: SomeService) {}
|
|
657
|
+
|
|
658
|
+
// Usage example
|
|
659
|
+
${exampleParams.join('\n')}
|
|
660
|
+
${exampleCall}
|
|
661
|
+
`;
|
|
662
|
+
const md = pluginSdk.markdown('');
|
|
663
|
+
return md`
|
|
664
|
+
\`\`\`typescript
|
|
665
|
+
${importContent.content}
|
|
666
|
+
\`\`\`
|
|
667
|
+
|
|
668
|
+
**Endpoint Details:**
|
|
669
|
+
- Method: \`${method.toUpperCase()}\`
|
|
670
|
+
- Path: \`${paths[0] || ''}\`
|
|
671
|
+
${requestBody ? `- Request Body Type: \`${requestBody}\`` : ''}
|
|
672
|
+
${response ? `- Response Type: \`${response}\`` : ''}
|
|
673
|
+
`;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function getEndpointDocumentation(result) {
|
|
677
|
+
const tabs = [];
|
|
678
|
+
tabs.push({
|
|
679
|
+
name: 'Service Method',
|
|
680
|
+
content: (await nestServiceMethodDocs(result)).content
|
|
681
|
+
});
|
|
682
|
+
return tabs;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async function initPlugin(ctx) {
|
|
686
|
+
// NestJS plugin doesn't need any specific initialization tasks currently
|
|
687
|
+
// But we return a postInit function to show instructions to the user
|
|
688
|
+
return {
|
|
689
|
+
postInit: ()=>{
|
|
690
|
+
console.log(chalk.blue('\n📋 Next Steps:'));
|
|
691
|
+
console.log(chalk.white('To complete your NestJS setup, import the generated IntrigModule into your app.module.ts:'));
|
|
692
|
+
console.log(chalk.cyan('\n import { IntrigModule } from \'@intrig/nest\';'));
|
|
693
|
+
console.log(chalk.cyan('\n @Module({'));
|
|
694
|
+
console.log(chalk.cyan(' imports: [IntrigModule],'));
|
|
695
|
+
console.log(chalk.cyan(' })'));
|
|
696
|
+
console.log(chalk.gray('\nThe generated services will be available for dependency injection.\n'));
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function createPlugin() {
|
|
702
|
+
return {
|
|
703
|
+
meta () {
|
|
704
|
+
return {
|
|
705
|
+
name: 'intrig-binding',
|
|
706
|
+
version: '0.0.1',
|
|
707
|
+
compat: '^0.0.15',
|
|
708
|
+
generator: 'nest'
|
|
709
|
+
};
|
|
710
|
+
},
|
|
711
|
+
generate: generateCode,
|
|
712
|
+
getSchemaDocumentation,
|
|
713
|
+
getEndpointDocumentation,
|
|
714
|
+
init: initPlugin
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
exports.createPlugin = createPlugin;
|
|
719
|
+
exports.default = createPlugin;
|