@aloma.io/integration-sdk 3.8.51 → 3.8.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aloma.io/integration-sdk",
3
- "version": "3.8.51",
3
+ "version": "3.8.53",
4
4
  "description": "",
5
5
  "author": "aloma.io",
6
6
  "license": "Apache-2.0",
package/src/cli.mts CHANGED
@@ -139,6 +139,12 @@ program
139
139
  .requiredOption('--connector-id <id>', 'id of the connector')
140
140
  .requiredOption('--spec <file>', 'OpenAPI specification file (JSON or YAML)')
141
141
  .option('--out <file>', 'output file path for the controller', 'src/controller/index.mts')
142
+ .option(
143
+ '--resource <className>',
144
+ 'Generate as a resource class with the specified class name (e.g., CompaniesResource)'
145
+ )
146
+ .option('--multi-resource', 'Generate multiple resource files + main controller (requires multiple --spec files)')
147
+ .option('--no-build', 'Skip installing dependencies and building the project')
142
148
  .action(async (name, options) => {
143
149
  name = name.replace(/[\/\.]/gi, '');
144
150
  if (!name) throw new Error('name is empty');
@@ -156,9 +162,16 @@ program
156
162
  extract({...options, target, name});
157
163
 
158
164
  // Generate the controller from OpenAPI spec
159
- console.log('Generating controller from OpenAPI specification...');
160
165
  const generator = new OpenAPIToConnector(spec, name);
161
- const controllerCode = generator.generateController();
166
+ let controllerCode: string;
167
+
168
+ if (options.resource) {
169
+ console.log(`Generating resource class '${options.resource}' from OpenAPI specification...`);
170
+ controllerCode = generator.generateResourceClass(options.resource);
171
+ } else {
172
+ console.log('Generating controller from OpenAPI specification...');
173
+ controllerCode = generator.generateController();
174
+ }
162
175
 
163
176
  // Write the generated controller
164
177
  const controllerPath = `${target}/${options.out}`;
@@ -168,11 +181,28 @@ program
168
181
  console.log('Generating keys...');
169
182
  await generateKeys({target});
170
183
 
171
- console.log('Installing dependencies...');
172
- await exec(`cd ${target}; yarn --ignore-engines`);
184
+ if (options.build !== false) {
185
+ console.log('Installing dependencies...');
186
+ await exec(`cd ${target}; yarn --ignore-engines`);
173
187
 
174
- console.log('Building...');
175
- await exec(`cd ${target}; yarn build`);
188
+ console.log('Building...');
189
+ await exec(`cd ${target}; yarn build`);
190
+ }
191
+
192
+ const nextSteps =
193
+ options.build !== false
194
+ ? `Next steps:
195
+ 1.) Add the connector to a workspace
196
+ 2.) Edit ./${name}/.env and insert the registration token
197
+ 3.) Implement the actual API calls in each method in ${options.out}
198
+ 4.) Start the connector with cd ./${name}/; yarn start`
199
+ : `Next steps:
200
+ 1.) Install dependencies: cd ./${name}/ && yarn --ignore-engines
201
+ 2.) Implement the actual API calls in each method in ${options.out}
202
+ 3.) Build the project: yarn build
203
+ 4.) Add the connector to a workspace
204
+ 5.) Edit ./${name}/.env and insert the registration token
205
+ 6.) Start the connector: yarn start`;
176
206
 
177
207
  console.log(`
178
208
  ✅ Success! Generated connector from OpenAPI specification
@@ -180,11 +210,7 @@ program
180
210
  📊 Found ${generator.getOperationsCount()} operations
181
211
  📄 Controller generated: ${options.out}
182
212
 
183
- Next steps:
184
- 1.) Add the connector to a workspace
185
- 2.) Edit ./${name}/.env and insert the registration token
186
- 3.) Implement the actual API calls in each method in ${options.out}
187
- 4.) Start the connector with cd ./${name}/; yarn start`);
213
+ ${nextSteps}`);
188
214
  } catch (error) {
189
215
  console.error('❌ Error:', error instanceof Error ? error.message : 'Unknown error');
190
216
  // Clean up on error
@@ -221,4 +247,162 @@ class Extractor {
221
247
  }
222
248
  }
223
249
 
250
+ // Multi-resource connector creation
251
+ program
252
+ .command('create-multi-resource')
253
+ .description('Create a multi-resource connector project with multiple OpenAPI specifications')
254
+ .argument('<name>', 'name of the connector project')
255
+ .requiredOption('--connector-id <id>', 'id of the connector')
256
+ .requiredOption(
257
+ '--resources <specs>',
258
+ 'comma-separated list of "className:specFile" pairs (e.g., "CompaniesResource:companies.json,ContactsResource:contacts.json")'
259
+ )
260
+ .option('--base-url <url>', 'base URL for the API (if not specified, will be extracted from first OpenAPI spec)')
261
+ .option('--no-build', 'Skip installing dependencies and building the project')
262
+ .action(async (name, options) => {
263
+ name = name.replace(/[\/\.]/gi, '');
264
+ if (!name) throw new Error('name is empty');
265
+
266
+ const target = `${process.cwd()}/${name}`;
267
+
268
+ try {
269
+ // Parse resources specification
270
+ const resourceSpecs = options.resources.split(',').map((spec) => {
271
+ const [className, specFile] = spec.split(':');
272
+ if (!className || !specFile) {
273
+ throw new Error(`Invalid resource specification: ${spec}. Expected format: "ClassName:specFile"`);
274
+ }
275
+ return {className: className.trim(), specFile: specFile.trim()};
276
+ });
277
+
278
+ console.log(`Creating multi-resource connector '${name}' with ${resourceSpecs.length} resources...`);
279
+
280
+ // Create the connector project structure
281
+ fs.mkdirSync(target);
282
+ extract({...options, target, name});
283
+
284
+ // Generate each resource
285
+ const resources: Array<{className: string; fileName: string}> = [];
286
+ let baseUrl = options.baseUrl;
287
+
288
+ for (const {className, specFile} of resourceSpecs) {
289
+ console.log(`Generating ${className} from ${specFile}...`);
290
+
291
+ // Read and parse the OpenAPI spec
292
+ const specContent = fs.readFileSync(specFile, 'utf-8');
293
+ const spec = OpenAPIToConnector.parseSpec(specContent);
294
+
295
+ // Extract base URL from first spec if not provided
296
+ if (!baseUrl && spec.servers && spec.servers.length > 0) {
297
+ baseUrl = spec.servers[0].url;
298
+ }
299
+
300
+ // Generate the resource class
301
+ const generator = new OpenAPIToConnector(spec, name);
302
+ const resourceCode = generator.generateResourceClass(className);
303
+
304
+ // Write the resource file
305
+ const fileName = className.toLowerCase().replace('resource', '');
306
+ const resourcePath = `${target}/src/resources/${fileName}.mts`;
307
+ fs.mkdirSync(path.dirname(resourcePath), {recursive: true});
308
+ fs.writeFileSync(resourcePath, resourceCode);
309
+
310
+ resources.push({className, fileName});
311
+ }
312
+
313
+ // Generate the main controller
314
+ console.log('Generating main controller...');
315
+ const firstSpec = OpenAPIToConnector.parseSpec(fs.readFileSync(resourceSpecs[0].specFile, 'utf-8'));
316
+ const mainGenerator = new OpenAPIToConnector(firstSpec, name);
317
+ const mainControllerCode = mainGenerator.generateMainController(resources);
318
+
319
+ // Write the main controller
320
+ const controllerPath = `${target}/src/controller/index.mts`;
321
+ fs.writeFileSync(controllerPath, mainControllerCode);
322
+
323
+ console.log('Generating keys...');
324
+ await generateKeys({target});
325
+
326
+ if (options.build !== false) {
327
+ console.log('Installing dependencies...');
328
+ await exec(`cd "${target}"; yarn --ignore-engines`);
329
+
330
+ console.log('Building...');
331
+ await exec(`cd "${target}"; yarn build`);
332
+ }
333
+
334
+ const nextSteps =
335
+ options.build !== false
336
+ ? `Next steps:
337
+ 1.) Add the connector to a workspace
338
+ 2.) Edit ./${name}/.env and insert the registration token
339
+ 3.) Start the connector with cd ./${name}/; yarn start`
340
+ : `Next steps:
341
+ 1.) Install dependencies: cd ./${name}/ && yarn --ignore-engines
342
+ 2.) Build the project: yarn build
343
+ 3.) Add the connector to a workspace
344
+ 4.) Edit ./${name}/.env and insert the registration token
345
+ 5.) Start the connector with yarn start`;
346
+
347
+ console.log(`\n✅ Multi-resource connector created successfully!
348
+
349
+ Generated resources:
350
+ ${resources.map((r) => `- ${r.className} (${r.fileName}.mts)`).join('\n')}
351
+
352
+ Main controller: src/controller/index.mts
353
+ ${nextSteps}`);
354
+ } catch (error) {
355
+ console.error('Error creating multi-resource connector:', (error as Error).message);
356
+ process.exit(1);
357
+ }
358
+ });
359
+
360
+ // Add resource to existing project
361
+ program
362
+ .command('add-resource')
363
+ .description('Add a new resource to an existing multi-resource connector')
364
+ .argument('<projectPath>', 'path to the existing connector project')
365
+ .requiredOption('--className <name>', 'class name for the resource (e.g., DealsResource)')
366
+ .requiredOption('--spec <file>', 'OpenAPI specification file for the new resource')
367
+ .option('--no-build', 'Skip building the project after adding the resource')
368
+ .action(async (projectPath, options) => {
369
+ const target = path.resolve(projectPath);
370
+
371
+ if (!fs.existsSync(target)) {
372
+ throw new Error(`Project path does not exist: ${target}`);
373
+ }
374
+
375
+ try {
376
+ console.log(`Adding ${options.className} resource to existing project...`);
377
+
378
+ // Read and parse the OpenAPI spec
379
+ const specContent = fs.readFileSync(options.spec, 'utf-8');
380
+ const spec = OpenAPIToConnector.parseSpec(specContent);
381
+
382
+ // Generate the resource class
383
+ const generator = new OpenAPIToConnector(spec, 'Resource');
384
+ const resourceCode = generator.generateResourceClass(options.className);
385
+
386
+ // Write the resource file
387
+ const fileName = options.className.toLowerCase().replace('resource', '');
388
+ const resourcePath = `${target}/src/resources/${fileName}.mts`;
389
+ fs.mkdirSync(path.dirname(resourcePath), {recursive: true});
390
+ fs.writeFileSync(resourcePath, resourceCode);
391
+
392
+ console.log(`✅ Resource ${options.className} added successfully at ${resourcePath}`);
393
+ console.log('\n⚠️ You need to manually update the main controller to include this new resource:');
394
+ console.log(`1.) Add import: import ${options.className} from '../resources/${fileName}.mjs';`);
395
+ console.log(`2.) Add property: ${fileName}!: ${options.className};`);
396
+ console.log(`3.) Add initialization in start(): this.${fileName} = new ${options.className}(this);`);
397
+
398
+ if (options.build !== false) {
399
+ console.log('\nBuilding project...');
400
+ await exec(`cd "${target}"; yarn build`);
401
+ }
402
+ } catch (error) {
403
+ console.error('Error adding resource:', (error as Error).message);
404
+ process.exit(1);
405
+ }
406
+ });
407
+
224
408
  program.parse();
@@ -122,7 +122,30 @@ export class OpenAPIToConnector {
122
122
  */
123
123
  private generateMethodName(operation: OperationInfo): string {
124
124
  if (operation.operationId) {
125
- return this.toValidIdentifier(operation.operationId);
125
+ // Clean up HubSpot-style operationIds like "get-/crm/v3/objects/companies_getPage"
126
+ let cleaned = operation.operationId;
127
+
128
+ // Extract the last part after underscore if it exists
129
+ const parts = cleaned.split('_');
130
+ if (parts.length > 1) {
131
+ const lastPart = parts[parts.length - 1];
132
+ // If the last part looks like a method name (camelCase), use it
133
+ if (lastPart && /^[a-z][a-zA-Z0-9]*$/.test(lastPart)) {
134
+ cleaned = lastPart;
135
+ }
136
+ }
137
+
138
+ // Remove any remaining special characters and clean up
139
+ cleaned = cleaned
140
+ .replace(/^(get|post|put|patch|delete|head|options)-/i, '') // Remove HTTP method prefix
141
+ .replace(/[^a-zA-Z0-9_]/g, '_')
142
+ .replace(/_+/g, '_')
143
+ .replace(/^_|_$/g, '');
144
+
145
+ // If we still have a valid identifier, use it
146
+ if (cleaned && /^[a-zA-Z]/.test(cleaned)) {
147
+ return cleaned;
148
+ }
126
149
  }
127
150
 
128
151
  // Generate from method + path
@@ -143,31 +166,85 @@ export class OpenAPIToConnector {
143
166
  */
144
167
  private generateJSDoc(operation: OperationInfo): string {
145
168
  const lines: string[] = [];
169
+ const pathParams: any[] = [];
170
+ const queryParams: any[] = [];
171
+ const hasBody = !!operation.requestBody;
146
172
 
147
173
  if (operation.summary) {
148
174
  lines.push(` * ${operation.summary}`);
149
175
  }
150
176
 
151
177
  if (operation.description) {
152
- lines.push(` * ${operation.description}`);
178
+ lines.push(` *`);
179
+ // Split long descriptions into multiple lines
180
+ const descLines = operation.description.split('\n');
181
+ descLines.forEach((line) => {
182
+ if (line.trim()) {
183
+ lines.push(` * ${line.trim()}`);
184
+ }
185
+ });
153
186
  }
154
187
 
155
- if (operation.parameters && operation.parameters.length > 0) {
156
- lines.push(' *');
157
- lines.push(' * @param args - Request arguments');
188
+ // Identify path and query parameters
189
+ if (operation.parameters) {
158
190
  for (const param of operation.parameters) {
159
- if (typeof param === 'object' && 'name' in param && 'description' in param) {
160
- lines.push(` * @param args.${param.name} - ${param.description || 'Parameter'}`);
191
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
192
+ if (param.in === 'path') {
193
+ pathParams.push(param);
194
+ } else if (param.in === 'query') {
195
+ queryParams.push(param);
196
+ }
161
197
  }
162
198
  }
163
199
  }
164
200
 
165
- if (operation.requestBody) {
201
+ // Check if using simple signature
202
+ const useSimpleSignature = queryParams.length === 0 && !hasBody && pathParams.length <= 1;
203
+
204
+ if (useSimpleSignature && pathParams.length === 1) {
205
+ // Simple signature documentation
206
+ const param = pathParams[0];
207
+ const paramType = param.schema?.type || 'string';
208
+ const paramDesc = param.description || '';
209
+ lines.push(' *');
210
+ lines.push(` * @param {${paramType}} ${param.name} ${paramDesc}`);
211
+ lines.push(` * @param {Object} options (optional) - Request options`);
212
+ lines.push(` * @param {Object} options.headers - Custom headers`);
213
+ } else {
214
+ // Options object documentation
166
215
  lines.push(' *');
167
- lines.push(' * @param args.body - Request body');
216
+ lines.push(` * @param {Object} options (optional) - Request options`);
217
+
218
+ // Document path parameters
219
+ for (const param of pathParams) {
220
+ const paramType = param.schema?.type || 'string';
221
+ const paramDesc = param.description || '';
222
+ const paramRequired = param.required ? '(required)' : '(optional)';
223
+ lines.push(` * @param {${paramType}} options.${param.name} ${paramRequired} - ${paramDesc} [path]`);
224
+ }
225
+
226
+ // Document query parameters
227
+ for (const param of queryParams) {
228
+ const paramType = param.schema?.type || 'any';
229
+ const paramDesc = param.description || '';
230
+ const paramRequired = param.required ? '(required)' : '(optional)';
231
+ lines.push(` * @param {${paramType}} options.${param.name} ${paramRequired} - ${paramDesc} [query]`);
232
+ }
233
+
234
+ // Document request body
235
+ if (operation.requestBody) {
236
+ const bodyDesc = operation.requestBody.description || 'Request body';
237
+ const required = operation.requestBody.required ? '(required)' : '(optional)';
238
+ lines.push(` * @param {Object} options.body ${required} - ${bodyDesc}`);
239
+ }
240
+
241
+ // Document headers
242
+ lines.push(` * @param {Object} options.headers (optional) - Custom headers to include in the request`);
168
243
  }
169
244
 
170
- lines.push(' * @returns Response data');
245
+ // Document response
246
+ lines.push(' *');
247
+ lines.push(` * @returns {Promise<Object>} ${operation.method} ${operation.path} response`);
171
248
 
172
249
  return lines.join('\n');
173
250
  }
@@ -179,6 +256,233 @@ export class OpenAPIToConnector {
179
256
  return this.extractOperations().length;
180
257
  }
181
258
 
259
+ /**
260
+ * Generate method signature with options object
261
+ */
262
+ private generateMethodSignature(operation: OperationInfo): string {
263
+ const pathParams: string[] = [];
264
+ const queryParams: string[] = [];
265
+ const hasBody = !!operation.requestBody;
266
+
267
+ // Identify path and query parameters
268
+ if (operation.parameters) {
269
+ for (const param of operation.parameters) {
270
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
271
+ if (param.in === 'path') {
272
+ pathParams.push(param.name);
273
+ } else if (param.in === 'query') {
274
+ queryParams.push(param.name);
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ // If there are no query params, no body, and only path params, use simple signature
281
+ if (queryParams.length === 0 && !hasBody && pathParams.length <= 1) {
282
+ const params: string[] = [];
283
+ for (const paramName of pathParams) {
284
+ params.push(`${paramName}: string`);
285
+ }
286
+ params.push(`options?: {headers?: {[key: string]: any}}`);
287
+ return `(${params.join(', ')})`;
288
+ }
289
+
290
+ // Otherwise, use options object pattern
291
+ return `(options?: {${[
292
+ ...pathParams.map((p) => `${p}?: string`),
293
+ ...queryParams.map((p) => `${p}?: any`),
294
+ hasBody ? 'body?: any' : '',
295
+ 'headers?: {[key: string]: any}',
296
+ ]
297
+ .filter(Boolean)
298
+ .join(', ')}})`;
299
+ }
300
+
301
+ /**
302
+ * Generate method implementation code
303
+ */
304
+ private generateMethodImplementation(operation: OperationInfo): string {
305
+ const lines: string[] = [];
306
+
307
+ // Build URL with path parameters
308
+ let url = operation.path;
309
+ const pathParams: string[] = [];
310
+ const queryParams: string[] = [];
311
+ const hasBody = !!operation.requestBody;
312
+
313
+ // Identify path and query parameters
314
+ if (operation.parameters) {
315
+ for (const param of operation.parameters) {
316
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
317
+ if (param.in === 'path') {
318
+ pathParams.push(param.name);
319
+ } else if (param.in === 'query') {
320
+ queryParams.push(param.name);
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ // Check if using simple signature (single path param, no query/body)
327
+ const useSimpleSignature = queryParams.length === 0 && !hasBody && pathParams.length <= 1;
328
+
329
+ if (useSimpleSignature && pathParams.length === 1) {
330
+ // Simple signature: (pathParam: string, options?: {headers?: ...})
331
+ const paramName = pathParams[0];
332
+ lines.push(` let url = '${url}';`);
333
+ lines.push(` if (${paramName}) {`);
334
+ lines.push(` url = url.replace('{${paramName}}', ${paramName});`);
335
+ lines.push(` }`);
336
+ lines.push('');
337
+ lines.push(` return this.api.fetch(url, {`);
338
+ lines.push(` method: '${operation.method}',`);
339
+ lines.push(` headers: options?.headers,`);
340
+ lines.push(` });`);
341
+ } else {
342
+ // Options object pattern
343
+ lines.push(` options = options || {};`);
344
+ lines.push('');
345
+
346
+ // Replace path parameters
347
+ if (pathParams.length > 0) {
348
+ lines.push(` // Build URL with path parameters`);
349
+ lines.push(` let url = '${url}';`);
350
+ for (const paramName of pathParams) {
351
+ lines.push(` if (options.${paramName}) {`);
352
+ lines.push(` url = url.replace('{${paramName}}', options.${paramName});`);
353
+ lines.push(` }`);
354
+ }
355
+ lines.push('');
356
+ } else {
357
+ lines.push(` const url = '${url}';`);
358
+ lines.push('');
359
+ }
360
+
361
+ // Build fetch options
362
+ lines.push(` const fetchOptions: any = {`);
363
+ lines.push(` method: '${operation.method}',`);
364
+
365
+ // Add query parameters
366
+ if (queryParams.length > 0) {
367
+ lines.push(` params: {},`);
368
+ }
369
+
370
+ // Add body if present
371
+ if (hasBody) {
372
+ lines.push(` body: options.body,`);
373
+ }
374
+
375
+ // Add headers if present
376
+ lines.push(` headers: options.headers,`);
377
+
378
+ lines.push(` };`);
379
+ lines.push('');
380
+
381
+ // Add query parameters to options
382
+ if (queryParams.length > 0) {
383
+ lines.push(` // Add query parameters`);
384
+ for (const paramName of queryParams) {
385
+ lines.push(` if (options.${paramName} !== undefined) {`);
386
+ lines.push(` fetchOptions.params.${paramName} = options.${paramName};`);
387
+ lines.push(` }`);
388
+ }
389
+ lines.push('');
390
+ }
391
+
392
+ // Make the API call
393
+ lines.push(` return this.api.fetch(url, fetchOptions);`);
394
+ }
395
+
396
+ return lines.join('\n');
397
+ }
398
+
399
+ /**
400
+ * Generate proper import paths with .mjs extensions for TypeScript module resolution
401
+ */
402
+ private generateImportPath(relativePath: string): string {
403
+ // For resource classes, we need to reference the compiled .mjs files
404
+ return relativePath.endsWith('.mjs') ? relativePath : `${relativePath}.mjs`;
405
+ }
406
+
407
+ /**
408
+ * Generate a resource class (does NOT extend AbstractController, receives controller reference)
409
+ */
410
+ generateResourceClass(className: string): string {
411
+ const operations = this.extractOperations();
412
+
413
+ if (operations.length === 0) {
414
+ throw new Error('No operations found in OpenAPI specification');
415
+ }
416
+
417
+ const methods = operations
418
+ .map((operation) => {
419
+ const methodName = this.generateMethodName(operation);
420
+ const jsdoc = this.generateJSDoc(operation);
421
+ const signature = this.generateMethodSignature(operation);
422
+ const implementation = this.generateMethodImplementation(operation);
423
+
424
+ return ` /**\n${jsdoc}\n */\n async ${methodName}${signature} {\n${implementation}\n }`;
425
+ })
426
+ .join('\n\n');
427
+
428
+ return `import {AbstractController} from '@aloma.io/integration-sdk';
429
+
430
+ export default class ${className} {
431
+ private controller: AbstractController;
432
+
433
+ constructor(controller: AbstractController) {
434
+ this.controller = controller;
435
+ }
436
+
437
+ private get api() {
438
+ return this.controller['api'];
439
+ }
440
+
441
+ ${methods}
442
+ }`;
443
+ }
444
+
445
+ /**
446
+ * Generate a main controller that composes multiple resources
447
+ */
448
+ generateMainController(resources: Array<{className: string; fileName: string}>): string {
449
+ // Get base URL from servers if available
450
+ const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'API_BASE_URL';
451
+
452
+ const imports = resources
453
+ .map((resource) => `import ${resource.className} from '../resources/${resource.fileName}.mjs';`)
454
+ .join('\n');
455
+
456
+ const properties = resources
457
+ .map((resource) => ` ${resource.className.toLowerCase().replace('resource', '')}!: ${resource.className};`)
458
+ .join('\n');
459
+
460
+ const initializations = resources
461
+ .map(
462
+ (resource) =>
463
+ ` this.${resource.className.toLowerCase().replace('resource', '')} = new ${resource.className}(this);`
464
+ )
465
+ .join('\n');
466
+
467
+ return `import {AbstractController} from '@aloma.io/integration-sdk';
468
+ ${imports}
469
+
470
+ export default class Controller extends AbstractController {
471
+ ${properties}
472
+
473
+ private api: any;
474
+
475
+ protected async start(): Promise<void> {
476
+ this.api = this.getClient({
477
+ baseUrl: '${baseUrl}',
478
+ });
479
+
480
+ // Initialize each resource - they receive 'this' controller reference
481
+ ${initializations}
482
+ }
483
+ }`;
484
+ }
485
+
182
486
  /**
183
487
  * Generate the connector controller code
184
488
  */
@@ -193,15 +497,30 @@ export class OpenAPIToConnector {
193
497
  .map((operation) => {
194
498
  const methodName = this.generateMethodName(operation);
195
499
  const jsdoc = this.generateJSDoc(operation);
500
+ const signature = this.generateMethodSignature(operation);
501
+ const implementation = this.generateMethodImplementation(operation);
196
502
 
197
- return ` /**\n${jsdoc}\n */\n async ${methodName}(args: any) {\n // TODO: Implement ${operation.method} ${operation.path}\n throw new Error('Method not implemented');\n }`;
503
+ return ` /**\n${jsdoc}\n */\n async ${methodName}${signature} {\n${implementation}\n }`;
198
504
  })
199
505
  .join('\n\n');
200
506
 
507
+ // Get base URL from servers if available
508
+ const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'API_BASE_URL';
509
+
510
+ const startMethod = ` private api: any;
511
+
512
+ protected async start(): Promise<void> {
513
+ this.api = this.getClient({
514
+ baseUrl: '${baseUrl}',
515
+ });
516
+ }`;
517
+
201
518
  return `import {AbstractController} from '@aloma.io/integration-sdk';
202
519
 
203
520
  export default class Controller extends AbstractController {
204
521
 
522
+ ${startMethod}
523
+
205
524
  ${methods}
206
525
  }`;
207
526
  }