@aloma.io/integration-sdk 3.8.55 ā 3.8.57
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/MULTI_RESOURCE_GUIDE.md +24 -21
- package/OPENAPI_TO_CONNECTOR.md +146 -16
- package/README.md +62 -10
- package/build/cli.mjs +122 -33
- package/build/internal/dispatcher/index.mjs +3 -2
- package/build/openapi-to-connector.d.mts +88 -11
- package/build/openapi-to-connector.mjs +909 -209
- package/package.json +15 -1
- package/src/cli.mts +140 -37
- package/src/internal/dispatcher/index.mts +4 -2
- package/src/openapi-to-connector.mts +1006 -217
- package/test/scenarios/README.md +148 -0
- package/test/scenarios/complex/expected/controller.mts +271 -0
- package/test/scenarios/complex/expected/orders-resource.mts +264 -0
- package/test/scenarios/complex/expected/products-resource.mts +239 -0
- package/test/scenarios/complex/specs/orders.json +362 -0
- package/test/scenarios/complex/specs/products.json +308 -0
- package/test/scenarios/simple/expected-controller.mts +60 -0
- package/test/scenarios/simple/simple-api.json +39 -0
- package/test/scenarios.test.mts +286 -0
- package/test/verify-scenarios.mjs +298 -0
package/build/cli.mjs
CHANGED
|
@@ -111,20 +111,16 @@ program
|
|
|
111
111
|
.option('--out <file>', 'output file path for the controller', 'src/controller/index.mts')
|
|
112
112
|
.option('--resource <className>', 'Generate as a resource class with the specified class name (e.g., CompaniesResource)')
|
|
113
113
|
.option('--multi-resource', 'Generate multiple resource files + main controller (requires multiple --spec files)')
|
|
114
|
+
.option('--controller-only', 'Generate only the controller file, do not create full project structure')
|
|
114
115
|
.option('--no-build', 'Skip installing dependencies and building the project')
|
|
115
116
|
.action(async (name, options) => {
|
|
116
117
|
name = name.replace(/[\/\.]/gi, '');
|
|
117
118
|
if (!name)
|
|
118
119
|
throw new Error('name is empty');
|
|
119
|
-
const target = `${process.cwd()}/${name}`;
|
|
120
120
|
try {
|
|
121
121
|
// Read and parse the OpenAPI spec
|
|
122
122
|
const specContent = fs.readFileSync(options.spec, 'utf-8');
|
|
123
123
|
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
124
|
-
// Create the connector project structure
|
|
125
|
-
fs.mkdirSync(target);
|
|
126
|
-
console.log('Creating connector project...');
|
|
127
|
-
extract({ ...options, target, name });
|
|
128
124
|
// Generate the controller from OpenAPI spec
|
|
129
125
|
const generator = new OpenAPIToConnector(spec, name);
|
|
130
126
|
let controllerCode;
|
|
@@ -136,44 +132,66 @@ program
|
|
|
136
132
|
console.log('Generating controller from OpenAPI specification...');
|
|
137
133
|
controllerCode = generator.generateController();
|
|
138
134
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
135
|
+
if (options.controllerOnly) {
|
|
136
|
+
// Controller-only mode: just generate the controller file
|
|
137
|
+
const controllerPath = path.resolve(options.out);
|
|
138
|
+
fs.mkdirSync(path.dirname(controllerPath), { recursive: true });
|
|
139
|
+
fs.writeFileSync(controllerPath, controllerCode);
|
|
140
|
+
console.log(`\nā
Success! Generated controller from OpenAPI specification
|
|
141
|
+
š Connector name: ${name}
|
|
142
|
+
š Found ${generator.getOperationsCount()} operations
|
|
143
|
+
š Controller file: ${controllerPath}
|
|
144
|
+
|
|
145
|
+
Controller file generated successfully! You can now use it in your existing project.`);
|
|
150
146
|
}
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
else {
|
|
148
|
+
// Full project mode: create complete project structure
|
|
149
|
+
const target = `${process.cwd()}/${name}`;
|
|
150
|
+
// Create the connector project structure
|
|
151
|
+
fs.mkdirSync(target);
|
|
152
|
+
console.log('Creating connector project...');
|
|
153
|
+
extract({ ...options, target, name });
|
|
154
|
+
// Write the generated controller
|
|
155
|
+
const controllerPath = `${target}/${options.out}`;
|
|
156
|
+
fs.mkdirSync(path.dirname(controllerPath), { recursive: true });
|
|
157
|
+
fs.writeFileSync(controllerPath, controllerCode);
|
|
158
|
+
console.log('Generating keys...');
|
|
159
|
+
await generateKeys({ target });
|
|
160
|
+
if (options.build !== false) {
|
|
161
|
+
console.log('Installing dependencies...');
|
|
162
|
+
await exec(`cd ${target}; yarn --ignore-engines`);
|
|
163
|
+
console.log('Building...');
|
|
164
|
+
await exec(`cd ${target}; yarn build`);
|
|
165
|
+
}
|
|
166
|
+
const nextSteps = options.build !== false
|
|
167
|
+
? `Next steps:
|
|
153
168
|
1.) Add the connector to a workspace
|
|
154
169
|
2.) Edit ./${name}/.env and insert the registration token
|
|
155
170
|
3.) Implement the actual API calls in each method in ${options.out}
|
|
156
171
|
4.) Start the connector with cd ./${name}/; yarn start`
|
|
157
|
-
|
|
172
|
+
: `Next steps:
|
|
158
173
|
1.) Install dependencies: cd ./${name}/ && yarn --ignore-engines
|
|
159
174
|
2.) Implement the actual API calls in each method in ${options.out}
|
|
160
175
|
3.) Build the project: yarn build
|
|
161
176
|
4.) Add the connector to a workspace
|
|
162
177
|
5.) Edit ./${name}/.env and insert the registration token
|
|
163
178
|
6.) Start the connector: yarn start`;
|
|
164
|
-
|
|
165
|
-
ā
Success! Generated connector from OpenAPI specification
|
|
179
|
+
console.log(`\nā
Success! Generated connector from OpenAPI specification
|
|
166
180
|
š Connector name: ${name}
|
|
167
181
|
š Found ${generator.getOperationsCount()} operations
|
|
168
182
|
š Controller generated: ${options.out}
|
|
169
183
|
|
|
170
184
|
${nextSteps}`);
|
|
185
|
+
}
|
|
171
186
|
}
|
|
172
187
|
catch (error) {
|
|
173
188
|
console.error('ā Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
174
|
-
// Clean up on error
|
|
175
|
-
if (
|
|
176
|
-
|
|
189
|
+
// Clean up on error (only if we created a project directory)
|
|
190
|
+
if (!options.controllerOnly) {
|
|
191
|
+
const target = `${process.cwd()}/${name}`;
|
|
192
|
+
if (fs.existsSync(target)) {
|
|
193
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
194
|
+
}
|
|
177
195
|
}
|
|
178
196
|
process.exit(1);
|
|
179
197
|
}
|
|
@@ -223,6 +241,7 @@ program
|
|
|
223
241
|
extract({ ...options, target, name });
|
|
224
242
|
// Generate each resource
|
|
225
243
|
const resources = [];
|
|
244
|
+
const parsedResourceSpecs = [];
|
|
226
245
|
let baseUrl = options.baseUrl;
|
|
227
246
|
for (const { className, specFile } of resourceSpecs) {
|
|
228
247
|
console.log(`Generating ${className} from ${specFile}...`);
|
|
@@ -242,12 +261,13 @@ program
|
|
|
242
261
|
fs.mkdirSync(path.dirname(resourcePath), { recursive: true });
|
|
243
262
|
fs.writeFileSync(resourcePath, resourceCode);
|
|
244
263
|
resources.push({ className, fileName });
|
|
264
|
+
parsedResourceSpecs.push({ fileName, spec });
|
|
245
265
|
}
|
|
246
266
|
// Generate the main controller
|
|
247
267
|
console.log('Generating main controller...');
|
|
248
268
|
const firstSpec = OpenAPIToConnector.parseSpec(fs.readFileSync(resourceSpecs[0].specFile, 'utf-8'));
|
|
249
269
|
const mainGenerator = new OpenAPIToConnector(firstSpec, name);
|
|
250
|
-
const mainControllerCode = mainGenerator.generateMainController(resources);
|
|
270
|
+
const mainControllerCode = mainGenerator.generateMainController(resources, parsedResourceSpecs);
|
|
251
271
|
// Write the main controller
|
|
252
272
|
const controllerPath = `${target}/src/controller/index.mts`;
|
|
253
273
|
fs.writeFileSync(controllerPath, mainControllerCode);
|
|
@@ -296,12 +316,16 @@ program
|
|
|
296
316
|
if (!fs.existsSync(target)) {
|
|
297
317
|
throw new Error(`Project path does not exist: ${target}`);
|
|
298
318
|
}
|
|
319
|
+
const controllerPath = `${target}/src/controller/index.mts`;
|
|
320
|
+
if (!fs.existsSync(controllerPath)) {
|
|
321
|
+
throw new Error(`Controller file not found: ${controllerPath}. This might not be a multi-resource connector project.`);
|
|
322
|
+
}
|
|
299
323
|
try {
|
|
300
324
|
console.log(`Adding ${options.className} resource to existing project...`);
|
|
301
325
|
// Read and parse the OpenAPI spec
|
|
302
326
|
const specContent = fs.readFileSync(options.spec, 'utf-8');
|
|
303
327
|
const spec = OpenAPIToConnector.parseSpec(specContent);
|
|
304
|
-
// Generate the resource
|
|
328
|
+
// Generate the resource functions file (new function-based pattern)
|
|
305
329
|
const generator = new OpenAPIToConnector(spec, 'Resource');
|
|
306
330
|
const resourceCode = generator.generateResourceClass(options.className);
|
|
307
331
|
// Write the resource file
|
|
@@ -309,18 +333,83 @@ program
|
|
|
309
333
|
const resourcePath = `${target}/src/resources/${fileName}.mts`;
|
|
310
334
|
fs.mkdirSync(path.dirname(resourcePath), { recursive: true });
|
|
311
335
|
fs.writeFileSync(resourcePath, resourceCode);
|
|
312
|
-
|
|
313
|
-
console.log('
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
336
|
+
// Update the main controller to include the new resource
|
|
337
|
+
console.log('Updating main controller...');
|
|
338
|
+
let controllerContent = fs.readFileSync(controllerPath, 'utf-8');
|
|
339
|
+
// Add import
|
|
340
|
+
const importStatement = `import * as ${fileName}Functions from '../resources/${fileName}.mjs';`;
|
|
341
|
+
if (!controllerContent.includes(importStatement)) {
|
|
342
|
+
// Find the last import and add after it
|
|
343
|
+
const importRegex = /^import.*from.*?;$/gm;
|
|
344
|
+
const imports = controllerContent.match(importRegex);
|
|
345
|
+
if (imports && imports.length > 0) {
|
|
346
|
+
const lastImport = imports[imports.length - 1];
|
|
347
|
+
controllerContent = controllerContent.replace(lastImport, `${lastImport}\n${importStatement}`);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// If no imports found, add at the beginning
|
|
351
|
+
controllerContent = `${importStatement}\n\n${controllerContent}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Add property declaration
|
|
355
|
+
const propertyDeclaration = ` ${fileName}: any = {};`;
|
|
356
|
+
if (!controllerContent.includes(propertyDeclaration)) {
|
|
357
|
+
// Find the existing properties and add the new one
|
|
358
|
+
const propertyRegex = /^ \w+: any = \{\};$/gm;
|
|
359
|
+
const properties = controllerContent.match(propertyRegex);
|
|
360
|
+
if (properties && properties.length > 0) {
|
|
361
|
+
const lastProperty = properties[properties.length - 1];
|
|
362
|
+
controllerContent = controllerContent.replace(lastProperty, `${lastProperty}\n${propertyDeclaration}`);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// Add after class declaration
|
|
366
|
+
controllerContent = controllerContent.replace(/^export default class Controller extends AbstractController \{$/gm, `export default class Controller extends AbstractController {\n${propertyDeclaration}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Add binding in start() method
|
|
370
|
+
const bindingStatement = ` this.bindResourceFunctions('${fileName}', ${fileName}Functions);`;
|
|
371
|
+
if (!controllerContent.includes(bindingStatement)) {
|
|
372
|
+
// Find the existing bindings and add the new one
|
|
373
|
+
const bindingRegex = /^ this\.bindResourceFunctions\(.*?\);$/gm;
|
|
374
|
+
const bindings = controllerContent.match(bindingRegex);
|
|
375
|
+
if (bindings && bindings.length > 0) {
|
|
376
|
+
const lastBinding = bindings[bindings.length - 1];
|
|
377
|
+
controllerContent = controllerContent.replace(lastBinding, `${lastBinding}\n${bindingStatement}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Write the updated controller
|
|
381
|
+
fs.writeFileSync(controllerPath, controllerContent);
|
|
382
|
+
// Generate exposed methods for the new resource
|
|
383
|
+
console.log('Generating exposed methods for the new resource...');
|
|
384
|
+
const resources = [{ className: options.className, fileName }];
|
|
385
|
+
const resourceSpecs = [{ fileName, spec }];
|
|
386
|
+
// Create a temporary generator to generate just the exposed methods for this resource
|
|
387
|
+
const tempGenerator = new OpenAPIToConnector(spec, 'temp');
|
|
388
|
+
const exposedMethods = tempGenerator.generateExposedResourceMethods(resources, resourceSpecs);
|
|
389
|
+
// Add the exposed methods to the controller before the closing brace
|
|
390
|
+
if (exposedMethods.trim()) {
|
|
391
|
+
controllerContent = fs.readFileSync(controllerPath, 'utf-8');
|
|
392
|
+
// Find the last method and add the new exposed methods
|
|
393
|
+
const lastBrace = controllerContent.lastIndexOf('}');
|
|
394
|
+
if (lastBrace !== -1) {
|
|
395
|
+
const beforeBrace = controllerContent.substring(0, lastBrace);
|
|
396
|
+
const afterBrace = controllerContent.substring(lastBrace);
|
|
397
|
+
const updatedContent = `${beforeBrace}\n${exposedMethods}\n${afterBrace}`;
|
|
398
|
+
fs.writeFileSync(controllerPath, updatedContent);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
console.log(`ā
Resource ${options.className} added successfully!`);
|
|
402
|
+
console.log(`š Resource functions: ${resourcePath}`);
|
|
403
|
+
console.log(`šļø Controller updated: ${controllerPath}`);
|
|
404
|
+
console.log(`\nš The new resource is fully integrated and ready to use!`);
|
|
317
405
|
if (options.build !== false) {
|
|
318
|
-
console.log('\
|
|
406
|
+
console.log('\nšØ Building project...');
|
|
319
407
|
await exec(`cd "${target}"; yarn build`);
|
|
408
|
+
console.log('ā
Build complete!');
|
|
320
409
|
}
|
|
321
410
|
}
|
|
322
411
|
catch (error) {
|
|
323
|
-
console.error('Error adding resource:', error.message);
|
|
412
|
+
console.error('ā Error adding resource:', error.message);
|
|
324
413
|
process.exit(1);
|
|
325
414
|
}
|
|
326
415
|
});
|
|
@@ -139,10 +139,11 @@ ${arg.configurableClientScope}
|
|
|
139
139
|
query = query
|
|
140
140
|
.filter((what) => !!what?.trim() && !['constructor', '__proto__', 'toString', 'toSource', 'prototype'].includes(what))
|
|
141
141
|
.slice(0, 20);
|
|
142
|
+
const originalQuery = [...query];
|
|
142
143
|
const method = resolveMethod(query);
|
|
143
144
|
if (!method && !_resolvers.__default)
|
|
144
|
-
throw new Error(`${
|
|
145
|
-
return method ? method(variables) : _resolvers.__default(variables ? { ...variables, __method:
|
|
145
|
+
throw new Error(`${originalQuery} not found`);
|
|
146
|
+
return method ? method(variables) : _resolvers.__default(variables ? { ...variables, __method: originalQuery } : variables);
|
|
146
147
|
};
|
|
147
148
|
const introspect = () => local._types;
|
|
148
149
|
const configSchema = () => local._config;
|
|
@@ -20,10 +20,6 @@ export declare class OpenAPIToConnector {
|
|
|
20
20
|
* Generate method name from operation info
|
|
21
21
|
*/
|
|
22
22
|
private generateMethodName;
|
|
23
|
-
/**
|
|
24
|
-
* Generate JSDoc comment for an operation
|
|
25
|
-
*/
|
|
26
|
-
private generateJSDoc;
|
|
27
23
|
/**
|
|
28
24
|
* Get the number of operations in the OpenAPI spec
|
|
29
25
|
*/
|
|
@@ -32,31 +28,112 @@ export declare class OpenAPIToConnector {
|
|
|
32
28
|
* Generate method signature with options object
|
|
33
29
|
*/
|
|
34
30
|
private generateMethodSignature;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a schema reference to a TypeScript type name
|
|
33
|
+
*/
|
|
34
|
+
private resolveSchemaRef;
|
|
35
|
+
/**
|
|
36
|
+
* Sanitize a name to be a valid TypeScript identifier
|
|
37
|
+
*/
|
|
38
|
+
private sanitizeTypeName;
|
|
39
|
+
/**
|
|
40
|
+
* Get TypeScript type from schema object
|
|
41
|
+
*/
|
|
42
|
+
private getTypeFromSchema;
|
|
43
|
+
/**
|
|
44
|
+
* Get TypeScript type for request body
|
|
45
|
+
*/
|
|
46
|
+
private getRequestBodyType;
|
|
47
|
+
/**
|
|
48
|
+
* Add request body properties directly to options array (flatten the body)
|
|
49
|
+
*/
|
|
50
|
+
private addRequestBodyProperties;
|
|
51
|
+
/**
|
|
52
|
+
* Get TypeScript type for response
|
|
53
|
+
*/
|
|
54
|
+
private getResponseType;
|
|
35
55
|
/**
|
|
36
56
|
* Get TypeScript type for a parameter based on its schema
|
|
37
57
|
*/
|
|
38
58
|
private getParameterType;
|
|
39
59
|
/**
|
|
40
|
-
* Generate method implementation code
|
|
60
|
+
* Generate method implementation code for controller methods with discrete path parameters
|
|
41
61
|
*/
|
|
42
|
-
private
|
|
62
|
+
private generateControllerMethodImplementation;
|
|
63
|
+
/**
|
|
64
|
+
* Generate method implementation for resource functions (using this.api instead of this.controller)
|
|
65
|
+
*/
|
|
66
|
+
private generateResourceFunctionImplementation;
|
|
67
|
+
/**
|
|
68
|
+
* Generate exposed resource methods for API introspection
|
|
69
|
+
*/
|
|
70
|
+
generateExposedResourceMethods(resources: Array<{
|
|
71
|
+
className: string;
|
|
72
|
+
fileName: string;
|
|
73
|
+
}>, resourceSpecs?: Array<{
|
|
74
|
+
fileName: string;
|
|
75
|
+
spec: OpenAPIV3.Document;
|
|
76
|
+
}>): string;
|
|
43
77
|
/**
|
|
44
|
-
* Generate
|
|
78
|
+
* Generate parameter call for a specific operation based on path parameters
|
|
45
79
|
*/
|
|
46
|
-
private
|
|
80
|
+
private generateParameterCallForOperation;
|
|
47
81
|
/**
|
|
48
|
-
* Generate
|
|
82
|
+
* Generate TypeScript interface from OpenAPI schema
|
|
83
|
+
*/
|
|
84
|
+
private generateInterfaceFromSchema;
|
|
85
|
+
/**
|
|
86
|
+
* Generate all TypeScript interfaces from OpenAPI components
|
|
87
|
+
*/
|
|
88
|
+
private generateAllInterfaces;
|
|
89
|
+
/**
|
|
90
|
+
* Generate detailed JSDoc with schema field information
|
|
91
|
+
*/
|
|
92
|
+
private generateDetailedJSDoc;
|
|
93
|
+
/**
|
|
94
|
+
* Add flattened request body documentation to JSDoc
|
|
95
|
+
*/
|
|
96
|
+
private addFlattenedBodyDocumentation;
|
|
97
|
+
/**
|
|
98
|
+
* Add detailed schema field information to JSDoc
|
|
99
|
+
*/
|
|
100
|
+
private addSchemaDetails;
|
|
101
|
+
/**
|
|
102
|
+
* Collect all schema types used in operations (including nested references)
|
|
103
|
+
*/
|
|
104
|
+
private collectUsedSchemaTypes;
|
|
105
|
+
/**
|
|
106
|
+
* Collect types from request body schema
|
|
107
|
+
*/
|
|
108
|
+
private collectTypesFromRequestBody;
|
|
109
|
+
/**
|
|
110
|
+
* Collect types from response schemas
|
|
111
|
+
*/
|
|
112
|
+
private collectTypesFromResponses;
|
|
113
|
+
/**
|
|
114
|
+
* Collect types from a schema object
|
|
115
|
+
*/
|
|
116
|
+
private collectTypesFromSchema;
|
|
117
|
+
/**
|
|
118
|
+
* Generate type imports for used schema types
|
|
119
|
+
*/
|
|
120
|
+
private generateTypeImports;
|
|
121
|
+
/**
|
|
122
|
+
* Generate a resource file with exported functions (new pattern for proper introspection)
|
|
49
123
|
*/
|
|
50
124
|
generateResourceClass(className: string): string;
|
|
51
125
|
/**
|
|
52
|
-
* Generate a main controller that composes multiple resources
|
|
126
|
+
* Generate a main controller that composes multiple resources using function binding
|
|
53
127
|
*/
|
|
54
128
|
generateMainController(resources: Array<{
|
|
55
129
|
className: string;
|
|
56
130
|
fileName: string;
|
|
131
|
+
}>, resourceSpecs?: Array<{
|
|
132
|
+
fileName: string;
|
|
133
|
+
spec: OpenAPIV3.Document;
|
|
57
134
|
}>): string;
|
|
58
135
|
/**
|
|
59
|
-
* Generate the connector controller code
|
|
136
|
+
* Generate the connector controller code with improved pattern
|
|
60
137
|
*/
|
|
61
138
|
generateController(): string;
|
|
62
139
|
}
|