@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.
@@ -137,89 +137,6 @@ export class OpenAPIToConnector {
137
137
  const pathSuffix = pathParts.join('_') || 'root';
138
138
  return `${methodPrefix}_${pathSuffix}`;
139
139
  }
140
- /**
141
- * Generate JSDoc comment for an operation
142
- */
143
- generateJSDoc(operation) {
144
- const lines = [];
145
- const pathParams = [];
146
- const queryParams = [];
147
- const hasBody = !!operation.requestBody;
148
- if (operation.summary) {
149
- lines.push(` * ${operation.summary}`);
150
- }
151
- if (operation.description) {
152
- lines.push(` *`);
153
- // Split long descriptions into multiple lines
154
- const descLines = operation.description.split('\n');
155
- descLines.forEach((line) => {
156
- if (line.trim()) {
157
- lines.push(` * ${line.trim()}`);
158
- }
159
- });
160
- }
161
- // Identify path and query parameters
162
- if (operation.parameters) {
163
- for (const param of operation.parameters) {
164
- if (typeof param === 'object' && 'name' in param && 'in' in param) {
165
- if (param.in === 'path') {
166
- pathParams.push(param);
167
- }
168
- else if (param.in === 'query') {
169
- queryParams.push(param);
170
- }
171
- }
172
- }
173
- }
174
- // Check if using simple signature
175
- const useSimpleSignature = queryParams.length === 0 && !hasBody && pathParams.length <= 1;
176
- if (useSimpleSignature && pathParams.length === 1) {
177
- // Simple signature documentation
178
- const param = pathParams[0];
179
- const paramType = param.schema?.type || 'string';
180
- const paramDesc = param.description || '';
181
- lines.push(' *');
182
- lines.push(` * @param {${paramType}} ${param.name} ${paramDesc}`);
183
- lines.push(` * @param {Object} options (optional) - Request options`);
184
- lines.push(` * @param {Object} options.headers - Custom headers`);
185
- }
186
- else {
187
- // Options object documentation
188
- lines.push(' *');
189
- // Check if there are any required parameters
190
- const hasRequiredParams = pathParams.some(p => p.required) ||
191
- queryParams.some(p => p.required) ||
192
- (operation.requestBody && operation.requestBody.required);
193
- const optionsRequired = hasRequiredParams ? '(required)' : '(optional)';
194
- lines.push(` * @param {Object} options ${optionsRequired} - Request options`);
195
- // Document path parameters
196
- for (const param of pathParams) {
197
- const paramType = param.schema?.type || 'string';
198
- const paramDesc = param.description || '';
199
- const paramRequired = param.required ? '(required)' : '(optional)';
200
- lines.push(` * @param {${paramType}} options.${param.name} ${paramRequired} - ${paramDesc} [path]`);
201
- }
202
- // Document query parameters
203
- for (const param of queryParams) {
204
- const paramType = param.schema?.type || 'any';
205
- const paramDesc = param.description || '';
206
- const paramRequired = param.required ? '(required)' : '(optional)';
207
- lines.push(` * @param {${paramType}} options.${param.name} ${paramRequired} - ${paramDesc} [query]`);
208
- }
209
- // Document request body
210
- if (operation.requestBody) {
211
- const bodyDesc = operation.requestBody.description || 'Request body';
212
- const required = operation.requestBody.required ? '(required)' : '(optional)';
213
- lines.push(` * @param {Object} options.body ${required} - ${bodyDesc}`);
214
- }
215
- // Document headers
216
- lines.push(` * @param {Object} options.headers (optional) - Custom headers to include in the request`);
217
- }
218
- // Document response
219
- lines.push(' *');
220
- lines.push(` * @returns {Promise<Object>} ${operation.method} ${operation.path} response`);
221
- return lines.join('\n');
222
- }
223
140
  /**
224
141
  * Get the number of operations in the OpenAPI spec
225
142
  */
@@ -240,7 +157,7 @@ export class OpenAPIToConnector {
240
157
  const paramInfo = {
241
158
  name: param.name,
242
159
  required: param.required || false,
243
- type: this.getParameterType(param)
160
+ type: this.getParameterType(param),
244
161
  };
245
162
  if (param.in === 'path') {
246
163
  pathParams.push(paramInfo);
@@ -251,18 +168,35 @@ export class OpenAPIToConnector {
251
168
  }
252
169
  }
253
170
  }
254
- // If there are no query params, no body, and only path params, use simple signature
255
- if (queryParams.length === 0 && !hasBody && pathParams.length <= 1) {
171
+ // Always extract path parameters as discrete parameters when they exist
172
+ if (pathParams.length > 0) {
256
173
  const params = [];
257
174
  for (const paramInfo of pathParams) {
258
175
  params.push(`${paramInfo.name}: ${paramInfo.type}`);
259
176
  }
260
- params.push(`options?: {headers?: {[key: string]: any}}`);
177
+ // Build options object for query params and body
178
+ const optionProps = [];
179
+ // Add query parameters to options
180
+ for (const prop of queryParams) {
181
+ const optional = prop.required ? '' : '?';
182
+ optionProps.push(`${prop.name}${optional}: ${prop.type}`);
183
+ }
184
+ // Add request body properties directly (flattened)
185
+ if (hasBody) {
186
+ this.addRequestBodyProperties(operation.requestBody, optionProps);
187
+ }
188
+ // Check if options parameter is required (has required query params or required body)
189
+ const hasRequiredNonPathParams = queryParams.some((p) => p.required) || (hasBody && operation.requestBody?.required);
190
+ const optionsRequired = hasRequiredNonPathParams ? '' : '?';
191
+ // Only add options parameter if there are actual options
192
+ if (optionProps.length > 0) {
193
+ params.push(`options${optionsRequired}: {${optionProps.join(', ')}}`);
194
+ }
261
195
  return `(${params.join(', ')})`;
262
196
  }
263
197
  // Check if there are any required parameters
264
- const hasRequiredParams = pathParams.some(p => p.required) ||
265
- queryParams.some(p => p.required) ||
198
+ const hasRequiredParams = pathParams.some((p) => p.required) ||
199
+ queryParams.some((p) => p.required) ||
266
200
  (hasBody && operation.requestBody?.required);
267
201
  // Build detailed options object with proper types
268
202
  // Group nested properties into objects (e.g., PrimaryContact.FirstName -> PrimaryContact: {FirstName: string})
@@ -282,7 +216,7 @@ export class OpenAPIToConnector {
282
216
  nestedObjects.get(objectName).push({
283
217
  name: propertyName,
284
218
  type: paramInfo.type,
285
- required: paramInfo.required
219
+ required: paramInfo.required,
286
220
  });
287
221
  }
288
222
  else {
@@ -290,7 +224,7 @@ export class OpenAPIToConnector {
290
224
  flatProps.push({
291
225
  name: paramInfo.name,
292
226
  type: paramInfo.type,
293
- required: paramInfo.required
227
+ required: paramInfo.required,
294
228
  });
295
229
  }
296
230
  }
@@ -303,121 +237,340 @@ export class OpenAPIToConnector {
303
237
  }
304
238
  // Add nested objects
305
239
  for (const [objectName, properties] of nestedObjects) {
306
- const nestedProps = properties.map(p => {
240
+ const nestedProps = properties
241
+ .map((p) => {
307
242
  const optional = p.required ? '' : '?';
308
243
  return `${p.name}${optional}: ${p.type}`;
309
- }).join(', ');
244
+ })
245
+ .join(', ');
310
246
  // Check if all properties are optional
311
- const allOptional = properties.every(p => !p.required);
247
+ const allOptional = properties.every((p) => !p.required);
312
248
  const optional = allOptional ? '?' : '';
313
249
  optionProps.push(`${objectName}${optional}: {${nestedProps}}`);
314
250
  }
315
- // Add request body
251
+ // Add request body properties directly (flattened)
316
252
  if (hasBody) {
317
- optionProps.push('body?: any');
253
+ this.addRequestBodyProperties(operation.requestBody, optionProps);
318
254
  }
319
- // Add custom headers
320
- optionProps.push('headers?: {[key: string]: any}');
321
255
  // If there are too many parameters, use simplified signature to avoid parsing issues
322
256
  // Also check if any parameter name is too long (over 100 chars) which can cause issues
323
- const hasLongParamNames = optionProps.some(prop => prop.length > 100);
257
+ const hasLongParamNames = optionProps.some((prop) => prop.length > 100);
324
258
  if (optionProps.length > 15 || hasLongParamNames) {
325
259
  const required = hasRequiredParams ? '' : '?';
326
260
  return `(options${required}: {[key: string]: any})`;
327
261
  }
328
- const required = hasRequiredParams ? '' : '?';
329
- return `(options${required}: {${optionProps.join(', ')}})`;
262
+ // Only add options if there are actual options
263
+ if (optionProps.length > 0) {
264
+ const required = hasRequiredParams ? '' : '?';
265
+ return `(options${required}: {${optionProps.join(', ')}})`;
266
+ }
267
+ else {
268
+ return '()';
269
+ }
330
270
  }
331
271
  /**
332
- * Get TypeScript type for a parameter based on its schema
272
+ * Resolve a schema reference to a TypeScript type name
333
273
  */
334
- getParameterType(param) {
335
- if (param.schema) {
336
- const schema = param.schema;
337
- // Handle different schema types
338
- if (schema.type) {
339
- switch (schema.type) {
340
- case 'string':
341
- return 'string';
342
- case 'integer':
343
- case 'number':
344
- return 'number';
345
- case 'boolean':
346
- return 'boolean';
347
- case 'array':
348
- return 'any[]';
349
- case 'object':
350
- return 'any';
351
- default:
352
- return 'any';
274
+ resolveSchemaRef(ref) {
275
+ // Extract the component name from the reference
276
+ // e.g., "#/components/schemas/Company" -> "Company"
277
+ const parts = ref.split('/');
278
+ if (parts.length >= 2) {
279
+ const componentName = parts[parts.length - 1];
280
+ return this.sanitizeTypeName(componentName);
281
+ }
282
+ return 'any';
283
+ }
284
+ /**
285
+ * Sanitize a name to be a valid TypeScript identifier
286
+ */
287
+ sanitizeTypeName(name) {
288
+ return (name
289
+ // Replace dots with underscores
290
+ .replace(/\./g, '_')
291
+ // Replace + with _Plus (common in OpenAPI for enums)
292
+ .replace(/\+/g, '_Plus')
293
+ // Replace other invalid characters with underscores
294
+ .replace(/[^a-zA-Z0-9_$]/g, '_')
295
+ // Ensure it starts with a letter or underscore
296
+ .replace(/^[0-9]/, '_$&')
297
+ // Remove multiple consecutive underscores
298
+ .replace(/_+/g, '_')
299
+ // Remove trailing/leading underscores
300
+ .replace(/^_+|_+$/g, '') ||
301
+ // Ensure it's not empty
302
+ 'UnknownType');
303
+ }
304
+ /**
305
+ * Get TypeScript type from schema object
306
+ */
307
+ getTypeFromSchema(schema) {
308
+ if (!schema)
309
+ return 'any';
310
+ // Handle $ref
311
+ if (schema.$ref) {
312
+ return this.resolveSchemaRef(schema.$ref);
313
+ }
314
+ // Handle arrays
315
+ if (schema.type === 'array') {
316
+ if (schema.items) {
317
+ const itemType = this.getTypeFromSchema(schema.items);
318
+ return `${itemType}[]`;
319
+ }
320
+ return 'any[]';
321
+ }
322
+ // Handle objects with properties
323
+ if (schema.type === 'object' && schema.properties) {
324
+ const propNames = Object.keys(schema.properties);
325
+ // For response objects, generate inline type definitions
326
+ if (propNames.length <= 5) {
327
+ // Reasonable limit for inline types
328
+ const propTypes = Object.entries(schema.properties).map(([key, prop]) => {
329
+ const propType = this.getTypeFromSchema(prop);
330
+ return `${key}: ${propType}`;
331
+ });
332
+ return `{${propTypes.join('; ')}}`;
333
+ }
334
+ // For complex objects, return a generic object type
335
+ return 'any';
336
+ }
337
+ // Handle other primitive types
338
+ if (schema.type) {
339
+ switch (schema.type) {
340
+ case 'string':
341
+ return 'string';
342
+ case 'integer':
343
+ case 'number':
344
+ return 'number';
345
+ case 'boolean':
346
+ return 'boolean';
347
+ case 'object':
348
+ return 'any';
349
+ default:
350
+ return 'any';
351
+ }
352
+ }
353
+ // Handle enum
354
+ if (schema.enum) {
355
+ return 'string'; // Could be expanded to union types
356
+ }
357
+ // Handle allOf, oneOf, anyOf
358
+ if (schema.allOf || schema.oneOf || schema.anyOf) {
359
+ return 'any'; // Could be expanded to intersection/union types
360
+ }
361
+ return 'any';
362
+ }
363
+ /**
364
+ * Get TypeScript type for request body
365
+ */
366
+ getRequestBodyType(requestBody) {
367
+ if (!requestBody)
368
+ return 'any';
369
+ // Handle content types
370
+ if (requestBody.content) {
371
+ // Prefer application/json
372
+ if (requestBody.content['application/json']?.schema) {
373
+ return this.getTypeFromSchema(requestBody.content['application/json'].schema);
374
+ }
375
+ // Fall back to first available content type
376
+ const firstContentType = Object.keys(requestBody.content)[0];
377
+ if (requestBody.content[firstContentType]?.schema) {
378
+ return this.getTypeFromSchema(requestBody.content[firstContentType].schema);
379
+ }
380
+ }
381
+ return 'any';
382
+ }
383
+ /**
384
+ * Add request body properties directly to options array (flatten the body)
385
+ */
386
+ addRequestBodyProperties(requestBody, optionProps) {
387
+ if (!requestBody)
388
+ return;
389
+ let schema = null;
390
+ // Get the schema from the request body
391
+ if (requestBody.content) {
392
+ // Prefer application/json
393
+ if (requestBody.content['application/json']?.schema) {
394
+ schema = requestBody.content['application/json'].schema;
395
+ }
396
+ else {
397
+ // Fall back to first available content type
398
+ const firstContentType = Object.keys(requestBody.content)[0];
399
+ if (requestBody.content[firstContentType]?.schema) {
400
+ schema = requestBody.content[firstContentType].schema;
353
401
  }
354
402
  }
355
- // Handle enum
356
- if (schema.enum) {
357
- return 'string';
403
+ }
404
+ if (!schema)
405
+ return;
406
+ // Handle $ref in schema
407
+ if (schema.$ref) {
408
+ const refType = this.resolveSchemaRef(schema.$ref);
409
+ const referencedSchema = this.spec.components?.schemas?.[refType];
410
+ if (referencedSchema && !('$ref' in referencedSchema)) {
411
+ schema = referencedSchema;
358
412
  }
359
- // Handle $ref
360
- if (schema.$ref) {
361
- return 'any';
413
+ else {
414
+ // If we can't resolve the reference, fall back to the original type
415
+ const bodyType = this.getRequestBodyType(requestBody);
416
+ optionProps.push(`body?: ${bodyType}`);
417
+ return;
418
+ }
419
+ }
420
+ // If schema has properties, add them individually
421
+ if (schema.properties) {
422
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
423
+ const propType = this.getTypeFromSchema(propSchema);
424
+ const required = (schema.required && schema.required.includes(propName)) || requestBody.required;
425
+ const optional = required ? '' : '?';
426
+ // Add description as comment if available
427
+ const description = propSchema?.description;
428
+ if (description) {
429
+ // Clean up description for inline use
430
+ const cleanDesc = description.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
431
+ if (cleanDesc.length < 100) {
432
+ // Only add short descriptions inline
433
+ optionProps.push(`${propName}${optional}: ${propType} /** ${cleanDesc} */`);
434
+ }
435
+ else {
436
+ optionProps.push(`${propName}${optional}: ${propType}`);
437
+ }
438
+ }
439
+ else {
440
+ optionProps.push(`${propName}${optional}: ${propType}`);
441
+ }
442
+ }
443
+ }
444
+ else {
445
+ // If we can't extract individual properties, fall back to body wrapper
446
+ const bodyType = this.getRequestBodyType(requestBody);
447
+ optionProps.push(`body?: ${bodyType}`);
448
+ }
449
+ }
450
+ /**
451
+ * Get TypeScript type for response
452
+ */
453
+ getResponseType(operation) {
454
+ if (!operation.responses)
455
+ return 'any';
456
+ // Try success responses first (200, 201, etc.)
457
+ const successCodes = ['200', '201', '202', '204'];
458
+ for (const code of successCodes) {
459
+ if (operation.responses[code]) {
460
+ const response = operation.responses[code];
461
+ if (response.content) {
462
+ // Prefer application/json
463
+ if (response.content['application/json']?.schema) {
464
+ return this.getTypeFromSchema(response.content['application/json'].schema);
465
+ }
466
+ // Fall back to first available content type
467
+ const firstContentType = Object.keys(response.content)[0];
468
+ if (response.content[firstContentType]?.schema) {
469
+ return this.getTypeFromSchema(response.content[firstContentType].schema);
470
+ }
471
+ }
362
472
  }
363
473
  }
364
474
  return 'any';
365
475
  }
366
476
  /**
367
- * Generate method implementation code
477
+ * Get TypeScript type for a parameter based on its schema
478
+ */
479
+ getParameterType(param) {
480
+ if (param.schema) {
481
+ return this.getTypeFromSchema(param.schema);
482
+ }
483
+ return 'any';
484
+ }
485
+ /**
486
+ * Generate method implementation code for controller methods with discrete path parameters
368
487
  */
369
- generateMethodImplementation(operation) {
488
+ generateControllerMethodImplementation(operation) {
370
489
  const lines = [];
371
- // Build URL with path parameters
372
- let url = operation.path;
490
+ const url = operation.path;
373
491
  const pathParams = [];
374
492
  const queryParams = [];
375
493
  const hasBody = !!operation.requestBody;
376
- // Identify path and query parameters
494
+ // Check if method has any options (query params, body, or headers)
495
+ const hasOptions = queryParams.length > 0 || hasBody;
496
+ // Identify parameters
377
497
  if (operation.parameters) {
378
- for (const param of operation.parameters) {
379
- if (typeof param === 'object' && 'name' in param && 'in' in param) {
380
- if (param.in === 'path') {
381
- pathParams.push(param.name);
382
- }
383
- else if (param.in === 'query') {
384
- queryParams.push(param.name);
385
- }
498
+ operation.parameters.forEach((param) => {
499
+ if (param.in === 'path') {
500
+ pathParams.push(param.name);
386
501
  }
387
- }
502
+ else if (param.in === 'query') {
503
+ queryParams.push(param.name);
504
+ }
505
+ });
388
506
  }
389
- // Check if using simple signature (single path param, no query/body)
390
- const useSimpleSignature = queryParams.length === 0 && !hasBody && pathParams.length <= 1;
391
- if (useSimpleSignature && pathParams.length === 1) {
392
- // Simple signature: (pathParam: string, options?: {headers?: ...})
393
- const paramName = pathParams[0];
507
+ // Update hasOptions after we know about query params
508
+ const actuallyHasOptions = queryParams.length > 0 || hasBody;
509
+ // Always extract path parameters as discrete parameters when they exist
510
+ if (pathParams.length > 0) {
511
+ // Handle path parameters as discrete function parameters
394
512
  lines.push(` let url = '${url}';`);
395
- lines.push(` if (${paramName}) {`);
396
- lines.push(` url = url.replace('{${paramName}}', ${paramName});`);
397
- lines.push(` }`);
513
+ for (const paramName of pathParams) {
514
+ lines.push(` if (${paramName}) {`);
515
+ lines.push(` url = url.replace('{${paramName}}', ${paramName});`);
516
+ lines.push(` }`);
517
+ }
398
518
  lines.push('');
399
- lines.push(` return this.api.fetch(url, {`);
519
+ // Build request body by excluding query parameters and headers (only if we have options)
520
+ if (hasBody && actuallyHasOptions) {
521
+ const excludedParams = ['headers', ...queryParams];
522
+ const destructureList = excludedParams.join(', ');
523
+ lines.push(` const { ${destructureList}, ...bodyData } = options;`);
524
+ lines.push(` const requestBody = Object.keys(bodyData).length > 0 ? bodyData : undefined;`);
525
+ lines.push('');
526
+ }
527
+ // Build fetch options
528
+ lines.push(` const fetchOptions: any = {`);
400
529
  lines.push(` method: '${operation.method}',`);
401
- lines.push(` headers: options?.headers,`);
402
- lines.push(` });`);
403
- }
404
- else {
405
- // Options object pattern
406
- lines.push(` options = options || {};`);
530
+ // Add query parameters
531
+ if (queryParams.length > 0) {
532
+ lines.push(` params: {},`);
533
+ }
534
+ // Add body
535
+ if (hasBody) {
536
+ if (actuallyHasOptions) {
537
+ lines.push(` body: requestBody,`);
538
+ }
539
+ else {
540
+ lines.push(` body: undefined,`);
541
+ }
542
+ }
543
+ // Add headers only if we have options
544
+ if (actuallyHasOptions) {
545
+ lines.push(` headers: options?.headers,`);
546
+ }
547
+ lines.push(` };`);
407
548
  lines.push('');
408
- // Replace path parameters
409
- if (pathParams.length > 0) {
410
- lines.push(` // Build URL with path parameters`);
411
- lines.push(` let url = '${url}';`);
412
- for (const paramName of pathParams) {
413
- lines.push(` if (options.${paramName}) {`);
414
- lines.push(` url = url.replace('{${paramName}}', options.${paramName});`);
549
+ // Add query parameters to options
550
+ if (queryParams.length > 0) {
551
+ lines.push(` // Add query parameters`);
552
+ for (const paramName of queryParams) {
553
+ lines.push(` if (options?.${paramName} !== undefined) {`);
554
+ lines.push(` fetchOptions.params.${paramName} = options.${paramName};`);
415
555
  lines.push(` }`);
416
556
  }
417
557
  lines.push('');
418
558
  }
419
- else {
420
- lines.push(` const url = '${url}';`);
559
+ }
560
+ else {
561
+ // No path parameters - check if we have options
562
+ if (actuallyHasOptions) {
563
+ lines.push(` options = options || {};`);
564
+ lines.push('');
565
+ }
566
+ lines.push(` const url = '${url}';`);
567
+ lines.push('');
568
+ // Build request body by excluding query parameters and headers (only if we have options)
569
+ if (hasBody && actuallyHasOptions) {
570
+ const excludedParams = ['headers', ...queryParams];
571
+ const destructureList = excludedParams.join(', ');
572
+ lines.push(` const { ${destructureList}, ...bodyData } = options;`);
573
+ lines.push(` const requestBody = Object.keys(bodyData).length > 0 ? bodyData : undefined;`);
421
574
  lines.push('');
422
575
  }
423
576
  // Build fetch options
@@ -427,12 +580,19 @@ export class OpenAPIToConnector {
427
580
  if (queryParams.length > 0) {
428
581
  lines.push(` params: {},`);
429
582
  }
430
- // Add body if present
583
+ // Add body
431
584
  if (hasBody) {
432
- lines.push(` body: options.body,`);
585
+ if (actuallyHasOptions) {
586
+ lines.push(` body: requestBody,`);
587
+ }
588
+ else {
589
+ lines.push(` body: undefined,`);
590
+ }
591
+ }
592
+ // Add headers only if we have options
593
+ if (actuallyHasOptions) {
594
+ lines.push(` headers: options.headers,`);
433
595
  }
434
- // Add headers if present
435
- lines.push(` headers: options.headers,`);
436
596
  lines.push(` };`);
437
597
  lines.push('');
438
598
  // Add query parameters to options
@@ -445,66 +605,568 @@ export class OpenAPIToConnector {
445
605
  }
446
606
  lines.push('');
447
607
  }
448
- // Make the API call
449
- lines.push(` return this.api.fetch(url, fetchOptions);`);
450
608
  }
609
+ // Make the API call
610
+ lines.push(` return this.api.fetch(url, fetchOptions);`);
451
611
  return lines.join('\n');
452
612
  }
453
613
  /**
454
- * Generate proper import paths with .mjs extensions for TypeScript module resolution
614
+ * Generate method implementation for resource functions (using this.api instead of this.controller)
455
615
  */
456
- generateImportPath(relativePath) {
457
- // For resource classes, we need to reference the compiled .mjs files
458
- return relativePath.endsWith('.mjs') ? relativePath : `${relativePath}.mjs`;
616
+ generateResourceFunctionImplementation(operation) {
617
+ const lines = [];
618
+ const url = operation.path;
619
+ const pathParams = [];
620
+ const queryParams = [];
621
+ const hasBody = !!operation.requestBody;
622
+ // Identify parameters
623
+ if (operation.parameters) {
624
+ operation.parameters.forEach((param) => {
625
+ if (param.in === 'path') {
626
+ pathParams.push(param.name);
627
+ }
628
+ else if (param.in === 'query') {
629
+ queryParams.push(param.name);
630
+ }
631
+ });
632
+ }
633
+ // Extract path parameters as discrete parameters when no query params or body
634
+ const isSimple = queryParams.length === 0 && !hasBody;
635
+ if (isSimple && pathParams.length > 0) {
636
+ // Handle path parameters as discrete function parameters
637
+ lines.push(` let url = '${url}';`);
638
+ for (const paramName of pathParams) {
639
+ lines.push(` if (${paramName}) {`);
640
+ lines.push(` url = url.replace('{${paramName}}', ${paramName});`);
641
+ lines.push(` }`);
642
+ }
643
+ lines.push('');
644
+ lines.push(` return this.api.fetch(url, {`);
645
+ lines.push(` method: '${operation.method}',`);
646
+ lines.push(` });`);
647
+ }
648
+ else {
649
+ // Options object pattern
650
+ lines.push(` options = options || {};`);
651
+ lines.push('');
652
+ // Replace path parameters - use discrete parameters, not options
653
+ if (pathParams.length > 0) {
654
+ lines.push(` // Build URL with path parameters`);
655
+ lines.push(` let url = '${url}';`);
656
+ for (const paramName of pathParams) {
657
+ lines.push(` if (${paramName}) {`);
658
+ lines.push(` url = url.replace('{${paramName}}', ${paramName});`);
659
+ lines.push(` }`);
660
+ }
661
+ lines.push('');
662
+ }
663
+ else {
664
+ lines.push(` const url = '${url}';`);
665
+ lines.push('');
666
+ }
667
+ // Build request body by excluding query parameters and headers
668
+ if (hasBody) {
669
+ const excludedParams = ['headers', ...queryParams];
670
+ const destructureList = excludedParams.join(', ');
671
+ lines.push(` const { ${destructureList}, ...bodyData } = options;`);
672
+ lines.push(` const requestBody = Object.keys(bodyData).length > 0 ? bodyData : undefined;`);
673
+ lines.push('');
674
+ }
675
+ // Build fetch options
676
+ lines.push(` const fetchOptions: any = {`);
677
+ lines.push(` method: '${operation.method}',`);
678
+ // Add query parameters
679
+ if (queryParams.length > 0) {
680
+ lines.push(` params: {},`);
681
+ }
682
+ // Add body
683
+ if (hasBody) {
684
+ lines.push(` body: requestBody,`);
685
+ }
686
+ // Add headers
687
+ lines.push(` headers: options.headers,`);
688
+ lines.push(` };`);
689
+ lines.push('');
690
+ // Add query parameters to options
691
+ if (queryParams.length > 0) {
692
+ lines.push(` // Add query parameters`);
693
+ for (const paramName of queryParams) {
694
+ lines.push(` if (options.${paramName} !== undefined) {`);
695
+ lines.push(` fetchOptions.params.${paramName} = options.${paramName};`);
696
+ lines.push(` }`);
697
+ }
698
+ lines.push('');
699
+ }
700
+ }
701
+ // Make the API call
702
+ lines.push(` return this.api.fetch(url, fetchOptions);`);
703
+ return lines.join('\n');
704
+ }
705
+ /**
706
+ * Generate exposed resource methods for API introspection
707
+ */
708
+ generateExposedResourceMethods(resources, resourceSpecs) {
709
+ const methods = [];
710
+ for (const resource of resources) {
711
+ const resourceName = resource.fileName;
712
+ // Find the corresponding spec for this resource
713
+ const resourceSpec = resourceSpecs?.find((rs) => rs.fileName === resourceName);
714
+ if (resourceSpec) {
715
+ // Create a temporary generator for this resource's spec
716
+ const resourceGenerator = new OpenAPIToConnector(resourceSpec.spec, resourceName);
717
+ const operations = resourceGenerator.extractOperations();
718
+ for (const operation of operations) {
719
+ const methodName = resourceGenerator.generateMethodName(operation);
720
+ const jsdoc = resourceGenerator.generateDetailedJSDoc(operation);
721
+ const signature = resourceGenerator.generateMethodSignature(operation);
722
+ // Generate the exposed method that delegates to the resource
723
+ const exposedMethodName = `${resourceName}${methodName.charAt(0).toUpperCase() + methodName.slice(1)}`;
724
+ // Generate parameter call based on operation details
725
+ const parameterCall = this.generateParameterCallForOperation(operation, signature);
726
+ methods.push(` /**
727
+ ${jsdoc}
728
+ */
729
+ async ${exposedMethodName}${signature} {
730
+ return this.${resourceName}.${methodName}(${parameterCall});
731
+ }`);
732
+ }
733
+ }
734
+ }
735
+ return methods.join('\n\n');
736
+ }
737
+ /**
738
+ * Generate parameter call for a specific operation based on path parameters
739
+ */
740
+ generateParameterCallForOperation(operation, signature) {
741
+ const pathParams = [];
742
+ const queryParams = [];
743
+ const hasBody = !!operation.requestBody;
744
+ // Identify path and query parameters
745
+ if (operation.parameters) {
746
+ for (const param of operation.parameters) {
747
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
748
+ if (param.in === 'path') {
749
+ pathParams.push(param.name);
750
+ }
751
+ else if (param.in === 'query') {
752
+ queryParams.push(param.name);
753
+ }
754
+ }
755
+ }
756
+ }
757
+ // Extract parameter names from controller signature
758
+ const paramMatch = signature.match(/\(([^)]+)\)/);
759
+ if (!paramMatch || paramMatch[1].trim() === '')
760
+ return '';
761
+ const allParams = paramMatch[1]
762
+ .split(',')
763
+ .map((p) => {
764
+ const paramName = p.trim().split(':')[0].trim();
765
+ return paramName.replace(/[?]/g, '');
766
+ })
767
+ .filter((p) => p.length > 0);
768
+ // Check if signature actually has options parameter
769
+ const hasOptionsParam = allParams.includes('options');
770
+ // Always extract path parameters as discrete parameters when they exist
771
+ if (pathParams.length > 0) {
772
+ // Path parameters are discrete, options is the last parameter (if it exists)
773
+ const pathParamNames = pathParams;
774
+ if (hasOptionsParam) {
775
+ return [...pathParamNames, 'options'].join(', ');
776
+ }
777
+ else {
778
+ return pathParamNames.join(', ');
779
+ }
780
+ }
781
+ else {
782
+ // No path parameters, everything goes in options object (if options exists)
783
+ if (hasOptionsParam) {
784
+ return allParams[0] || 'options';
785
+ }
786
+ else {
787
+ return '';
788
+ }
789
+ }
790
+ }
791
+ /**
792
+ * Generate TypeScript interface from OpenAPI schema
793
+ */
794
+ generateInterfaceFromSchema(name, schema) {
795
+ if (!schema)
796
+ return '';
797
+ const sanitizedName = this.sanitizeTypeName(name);
798
+ const lines = [];
799
+ lines.push(`export interface ${sanitizedName} {`);
800
+ if (schema.properties) {
801
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
802
+ const propType = this.getTypeFromSchema(propSchema);
803
+ const required = schema.required && schema.required.includes(propName);
804
+ const optional = required ? '' : '?';
805
+ // Add description as comment if available
806
+ const description = propSchema?.description;
807
+ if (description) {
808
+ lines.push(` /** ${description} */`);
809
+ }
810
+ lines.push(` ${propName}${optional}: ${propType};`);
811
+ }
812
+ }
813
+ // Handle allOf, oneOf, anyOf
814
+ if (schema.allOf) {
815
+ lines.push(` // Inherits from: ${schema.allOf.map((s) => (s.$ref ? this.resolveSchemaRef(s.$ref) : 'unknown')).join(', ')}`);
816
+ }
817
+ lines.push('}');
818
+ return lines.join('\n');
819
+ }
820
+ /**
821
+ * Generate all TypeScript interfaces from OpenAPI components
822
+ */
823
+ generateAllInterfaces() {
824
+ if (!this.spec.components?.schemas) {
825
+ return '';
826
+ }
827
+ const interfaces = [];
828
+ const usedTypes = this.collectUsedSchemaTypes();
829
+ for (const [schemaName, schema] of Object.entries(this.spec.components.schemas)) {
830
+ const sanitizedSchemaName = this.sanitizeTypeName(schemaName);
831
+ if (usedTypes.has(sanitizedSchemaName)) {
832
+ const interfaceCode = this.generateInterfaceFromSchema(schemaName, schema);
833
+ if (interfaceCode) {
834
+ interfaces.push(interfaceCode);
835
+ }
836
+ }
837
+ }
838
+ if (interfaces.length === 0) {
839
+ return '';
840
+ }
841
+ return `// Generated TypeScript interfaces from OpenAPI schemas\n\n${interfaces.join('\n\n')}\n`;
842
+ }
843
+ /**
844
+ * Generate detailed JSDoc with schema field information
845
+ */
846
+ generateDetailedJSDoc(operation) {
847
+ const lines = [];
848
+ lines.push(` * ${operation.summary || operation.operationId || 'API Operation'}`);
849
+ if (operation.description) {
850
+ lines.push(' *');
851
+ // Split long descriptions into multiple lines
852
+ const descLines = operation.description.split('\n');
853
+ descLines.forEach((line) => {
854
+ lines.push(` * ${line}`);
855
+ });
856
+ }
857
+ lines.push(' *');
858
+ // Document path parameters with details
859
+ const pathParams = [];
860
+ const queryParams = [];
861
+ if (operation.parameters) {
862
+ for (const param of operation.parameters) {
863
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
864
+ if (param.in === 'path') {
865
+ pathParams.push(param);
866
+ }
867
+ else if (param.in === 'query') {
868
+ queryParams.push(param);
869
+ }
870
+ }
871
+ }
872
+ }
873
+ // Document discrete path parameters
874
+ pathParams.forEach((param) => {
875
+ const paramType = this.getParameterType(param);
876
+ const paramDesc = param.description || '';
877
+ lines.push(` * @param {${paramType}} ${param.name} ${paramDesc}`);
878
+ });
879
+ // Document options parameter with detailed schema information
880
+ if (queryParams.length > 0 || operation.requestBody) {
881
+ lines.push(' * @param {Object} options - Request options');
882
+ // Document query parameters
883
+ queryParams.forEach((param) => {
884
+ const paramType = this.getParameterType(param);
885
+ const paramDesc = param.description || '';
886
+ const required = param.required ? '(required)' : '(optional)';
887
+ lines.push(` * @param {${paramType}} options.${param.name} ${required} - ${paramDesc} [query]`);
888
+ });
889
+ // Document request body with detailed schema info (now flattened)
890
+ if (operation.requestBody) {
891
+ this.addFlattenedBodyDocumentation(lines, operation.requestBody);
892
+ }
893
+ }
894
+ else if (pathParams.length > 0) {
895
+ lines.push(` * @param {Object} options (optional) - Request options`);
896
+ }
897
+ // Document response with detailed schema information
898
+ lines.push(' *');
899
+ const returnType = this.getResponseType(operation);
900
+ lines.push(` * @returns {Promise<${returnType}>} ${operation.method.toUpperCase()} ${operation.path} response`);
901
+ // Add detailed schema information for the return type
902
+ this.addSchemaDetails(lines, returnType, 'response');
903
+ return lines.join('\n');
904
+ }
905
+ /**
906
+ * Add flattened request body documentation to JSDoc
907
+ */
908
+ addFlattenedBodyDocumentation(lines, requestBody) {
909
+ if (!requestBody)
910
+ return;
911
+ let schema = null;
912
+ // Get the schema from the request body
913
+ if (requestBody.content) {
914
+ if (requestBody.content['application/json']?.schema) {
915
+ schema = requestBody.content['application/json'].schema;
916
+ }
917
+ else {
918
+ const firstContentType = Object.keys(requestBody.content)[0];
919
+ if (requestBody.content[firstContentType]?.schema) {
920
+ schema = requestBody.content[firstContentType].schema;
921
+ }
922
+ }
923
+ }
924
+ if (!schema)
925
+ return;
926
+ // Handle $ref in schema
927
+ if (schema.$ref) {
928
+ const refType = this.resolveSchemaRef(schema.$ref);
929
+ const referencedSchema = this.spec.components?.schemas?.[refType];
930
+ if (referencedSchema && !('$ref' in referencedSchema)) {
931
+ schema = referencedSchema;
932
+ }
933
+ else {
934
+ // Fallback to original format if we can't resolve
935
+ const bodyType = this.getRequestBodyType(requestBody);
936
+ const bodyDesc = requestBody.description || 'Request body';
937
+ const required = requestBody.required ? '(required)' : '(optional)';
938
+ lines.push(` * @param {${bodyType}} options.body ${required} - ${bodyDesc}`);
939
+ return;
940
+ }
941
+ }
942
+ // Document individual properties from the body schema
943
+ if (schema.properties) {
944
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
945
+ const propType = this.getTypeFromSchema(propSchema);
946
+ const propRequired = (schema.required && schema.required.includes(propName)) || requestBody.required;
947
+ const requiredText = propRequired ? '(required)' : '(optional)';
948
+ const propDesc = propSchema?.description || '';
949
+ lines.push(` * @param {${propType}} options.${propName} ${requiredText} - ${propDesc} [body property]`);
950
+ }
951
+ }
952
+ else {
953
+ // Fallback to original format if no properties
954
+ const bodyType = this.getRequestBodyType(requestBody);
955
+ const bodyDesc = requestBody.description || 'Request body';
956
+ const required = requestBody.required ? '(required)' : '(optional)';
957
+ lines.push(` * @param {${bodyType}} options.body ${required} - ${bodyDesc}`);
958
+ }
959
+ }
960
+ /**
961
+ * Add detailed schema field information to JSDoc
962
+ */
963
+ addSchemaDetails(lines, typeName, context) {
964
+ // Remove array notation to get base type
965
+ const baseType = typeName.replace(/\[\]$/, '');
966
+ const isArray = typeName.endsWith('[]');
967
+ if (['string', 'number', 'boolean', 'any'].includes(baseType)) {
968
+ return; // Skip primitive types
969
+ }
970
+ const schema = this.spec.components?.schemas?.[baseType];
971
+ if (!schema) {
972
+ return;
973
+ }
974
+ // Type guard to check if schema has properties (is not a ReferenceObject)
975
+ if ('$ref' in schema) {
976
+ return; // Skip reference objects
977
+ }
978
+ if (!schema.properties) {
979
+ return;
980
+ }
981
+ lines.push(' *');
982
+ lines.push(` * ${context}${isArray ? '[]' : ''} fields:`);
983
+ const maxFields = 10; // Limit to avoid too much clutter
984
+ const properties = Object.entries(schema.properties).slice(0, maxFields);
985
+ for (const [propName, propSchema] of properties) {
986
+ const propType = this.getTypeFromSchema(propSchema);
987
+ const required = schema.required && schema.required.includes(propName);
988
+ const requiredText = required ? '' : '?';
989
+ const description = propSchema?.description;
990
+ if (description) {
991
+ lines.push(` * - ${propName}${requiredText}: ${propType} - ${description}`);
992
+ }
993
+ else {
994
+ lines.push(` * - ${propName}${requiredText}: ${propType}`);
995
+ }
996
+ }
997
+ const totalFields = Object.keys(schema.properties).length;
998
+ if (totalFields > maxFields) {
999
+ lines.push(` * - ... and ${totalFields - maxFields} more fields`);
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Collect all schema types used in operations (including nested references)
1004
+ */
1005
+ collectUsedSchemaTypes() {
1006
+ const usedTypes = new Set();
1007
+ const visitedSchemas = new Set(); // Track visited schemas to prevent infinite recursion
1008
+ const operations = this.extractOperations();
1009
+ for (const operation of operations) {
1010
+ // Collect from request body schemas
1011
+ if (operation.requestBody) {
1012
+ this.collectTypesFromRequestBody(operation.requestBody, usedTypes, visitedSchemas);
1013
+ }
1014
+ // Collect from response schemas
1015
+ if (operation.responses) {
1016
+ this.collectTypesFromResponses(operation.responses, usedTypes, visitedSchemas);
1017
+ }
1018
+ // Collect from parameter schemas
1019
+ if (operation.parameters) {
1020
+ for (const param of operation.parameters) {
1021
+ if (typeof param === 'object' && 'schema' in param && param.schema) {
1022
+ this.collectTypesFromSchema(param.schema, usedTypes, visitedSchemas);
1023
+ }
1024
+ }
1025
+ }
1026
+ }
1027
+ return usedTypes;
1028
+ }
1029
+ /**
1030
+ * Collect types from request body schema
1031
+ */
1032
+ collectTypesFromRequestBody(requestBody, usedTypes, visitedSchemas) {
1033
+ if (!requestBody || !requestBody.content)
1034
+ return;
1035
+ // Check all content types
1036
+ for (const contentType of Object.keys(requestBody.content)) {
1037
+ const content = requestBody.content[contentType];
1038
+ if (content.schema) {
1039
+ this.collectTypesFromSchema(content.schema, usedTypes, visitedSchemas);
1040
+ }
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Collect types from response schemas
1045
+ */
1046
+ collectTypesFromResponses(responses, usedTypes, visitedSchemas) {
1047
+ if (!responses)
1048
+ return;
1049
+ // Check success responses
1050
+ const successCodes = ['200', '201', '202', '204'];
1051
+ for (const code of successCodes) {
1052
+ const response = responses[code];
1053
+ if (response && response.content) {
1054
+ for (const contentType of Object.keys(response.content)) {
1055
+ const content = response.content[contentType];
1056
+ if (content.schema) {
1057
+ this.collectTypesFromSchema(content.schema, usedTypes, visitedSchemas);
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Collect types from a schema object
1065
+ */
1066
+ collectTypesFromSchema(schema, usedTypes, visitedSchemas) {
1067
+ if (!schema)
1068
+ return;
1069
+ // Handle $ref
1070
+ if (schema.$ref) {
1071
+ const parts = schema.$ref.split('/');
1072
+ const originalSchemaName = parts[parts.length - 1];
1073
+ const sanitizedRefType = this.resolveSchemaRef(schema.$ref);
1074
+ if (sanitizedRefType !== 'any' && !['string', 'number', 'boolean'].includes(sanitizedRefType)) {
1075
+ // Add the sanitized type name to used types
1076
+ usedTypes.add(sanitizedRefType);
1077
+ // Only recurse if we haven't visited this schema before (use original name for lookup)
1078
+ if (!visitedSchemas.has(originalSchemaName)) {
1079
+ visitedSchemas.add(originalSchemaName);
1080
+ const referencedSchema = this.spec.components?.schemas?.[originalSchemaName];
1081
+ if (referencedSchema) {
1082
+ this.collectTypesFromSchema(referencedSchema, usedTypes, visitedSchemas);
1083
+ }
1084
+ }
1085
+ }
1086
+ return;
1087
+ }
1088
+ // Handle arrays
1089
+ if (schema.type === 'array' && schema.items) {
1090
+ this.collectTypesFromSchema(schema.items, usedTypes, visitedSchemas);
1091
+ return;
1092
+ }
1093
+ // Handle objects with properties
1094
+ if (schema.type === 'object' && schema.properties) {
1095
+ for (const propSchema of Object.values(schema.properties)) {
1096
+ this.collectTypesFromSchema(propSchema, usedTypes, visitedSchemas);
1097
+ }
1098
+ return;
1099
+ }
1100
+ // Handle allOf, oneOf, anyOf
1101
+ if (schema.allOf) {
1102
+ for (const subSchema of schema.allOf) {
1103
+ this.collectTypesFromSchema(subSchema, usedTypes, visitedSchemas);
1104
+ }
1105
+ }
1106
+ if (schema.oneOf) {
1107
+ for (const subSchema of schema.oneOf) {
1108
+ this.collectTypesFromSchema(subSchema, usedTypes, visitedSchemas);
1109
+ }
1110
+ }
1111
+ if (schema.anyOf) {
1112
+ for (const subSchema of schema.anyOf) {
1113
+ this.collectTypesFromSchema(subSchema, usedTypes, visitedSchemas);
1114
+ }
1115
+ }
1116
+ }
1117
+ /**
1118
+ * Generate type imports for used schema types
1119
+ */
1120
+ generateTypeImports() {
1121
+ const usedTypes = this.collectUsedSchemaTypes();
1122
+ if (usedTypes.size === 0) {
1123
+ return '';
1124
+ }
1125
+ const typeList = Array.from(usedTypes).sort().join(', ');
1126
+ return `// Type imports (you may need to adjust the import path)\n// import type { ${typeList} } from './types';\n\n`;
459
1127
  }
460
1128
  /**
461
- * Generate a resource class (does NOT extend AbstractController, receives controller reference)
1129
+ * Generate a resource file with exported functions (new pattern for proper introspection)
462
1130
  */
463
1131
  generateResourceClass(className) {
464
1132
  const operations = this.extractOperations();
465
1133
  if (operations.length === 0) {
466
1134
  throw new Error('No operations found in OpenAPI specification');
467
1135
  }
468
- const methods = operations
1136
+ const resourceName = className.replace('Resource', '').toLowerCase();
1137
+ const functions = operations
469
1138
  .map((operation) => {
470
1139
  const methodName = this.generateMethodName(operation);
471
- const jsdoc = this.generateJSDoc(operation);
1140
+ const jsdoc = this.generateDetailedJSDoc(operation);
472
1141
  const signature = this.generateMethodSignature(operation);
473
- const implementation = this.generateMethodImplementation(operation);
474
- return ` /**\n${jsdoc}\n */\n async ${methodName}${signature} {\n${implementation}\n }`;
1142
+ const implementation = this.generateResourceFunctionImplementation(operation);
1143
+ return `/**\n${jsdoc}\n */\nexport function ${methodName}(this: any, ${signature.replace('(', '').replace(')', '')}) {\n${implementation}\n}`;
475
1144
  })
476
1145
  .join('\n\n');
477
- return `import {AbstractController} from '@aloma.io/integration-sdk';
478
-
479
- export default class ${className} {
480
- private controller: AbstractController;
1146
+ // Generate actual TypeScript interfaces
1147
+ const interfaces = this.generateAllInterfaces();
1148
+ return `// ${className} resource functions
1149
+ // These functions will be bound to the controller instance and accessible as ${resourceName}.method()
481
1150
 
482
- constructor(controller: AbstractController) {
483
- this.controller = controller;
484
- }
485
-
486
- private get api() {
487
- return this.controller['api'];
488
- }
1151
+ ${interfaces}
489
1152
 
490
- ${methods}
491
- }`;
1153
+ ${functions}`;
492
1154
  }
493
1155
  /**
494
- * Generate a main controller that composes multiple resources
1156
+ * Generate a main controller that composes multiple resources using function binding
495
1157
  */
496
- generateMainController(resources) {
1158
+ generateMainController(resources, resourceSpecs) {
497
1159
  // Get base URL from servers if available
498
- const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'API_BASE_URL';
1160
+ const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'https://api.example.com';
499
1161
  const imports = resources
500
- .map((resource) => `import ${resource.className} from '../resources/${resource.fileName}.mjs';`)
501
- .join('\n');
502
- const properties = resources
503
- .map((resource) => ` ${resource.className.toLowerCase().replace('resource', '')}!: ${resource.className};`)
1162
+ .map((resource) => `import * as ${resource.fileName}Functions from '../resources/${resource.fileName}.mjs';`)
504
1163
  .join('\n');
505
- const initializations = resources
506
- .map((resource) => ` this.${resource.className.toLowerCase().replace('resource', '')} = new ${resource.className}(this);`)
1164
+ const properties = resources.map((resource) => ` ${resource.fileName}: any = {};`).join('\n');
1165
+ const bindings = resources
1166
+ .map((resource) => ` this.bindResourceFunctions('${resource.fileName}', ${resource.fileName}Functions);`)
507
1167
  .join('\n');
1168
+ // Generate exposed methods for each resource to enable API introspection
1169
+ const exposedMethods = this.generateExposedResourceMethods(resources, resourceSpecs);
508
1170
  return `import {AbstractController} from '@aloma.io/integration-sdk';
509
1171
  ${imports}
510
1172
 
@@ -514,17 +1176,44 @@ ${properties}
514
1176
  private api: any;
515
1177
 
516
1178
  protected async start(): Promise<void> {
1179
+ const config = this.config;
1180
+
517
1181
  this.api = this.getClient({
518
1182
  baseUrl: '${baseUrl}',
1183
+ customize(request) {
1184
+ request.headers ||= {};
1185
+ // Add authentication headers based on your API requirements
1186
+ // Example: request.headers["Authorization"] = \`Bearer \${config.apiToken}\`;
1187
+ },
519
1188
  });
520
1189
 
521
- // Initialize each resource - they receive 'this' controller reference
522
- ${initializations}
1190
+ // Bind resource functions to this controller context
1191
+ // This allows using this.resourceName.method() syntax
1192
+ ${bindings}
1193
+ }
1194
+
1195
+ private bindResourceFunctions(resourceName: string, functions: any) {
1196
+ for (const [functionName, func] of Object.entries(functions)) {
1197
+ if (typeof func === 'function') {
1198
+ this[resourceName][functionName] = func.bind(this);
1199
+ }
1200
+ }
1201
+ }
1202
+
1203
+ /**
1204
+ * Generic API request method
1205
+ * @param url - API endpoint
1206
+ * @param options - Request options
1207
+ */
1208
+ async request({ url, options }: { url: string; options?: any }) {
1209
+ return this.api.fetch(url, options);
523
1210
  }
1211
+
1212
+ ${exposedMethods}
524
1213
  }`;
525
1214
  }
526
1215
  /**
527
- * Generate the connector controller code
1216
+ * Generate the connector controller code with improved pattern
528
1217
  */
529
1218
  generateController() {
530
1219
  const operations = this.extractOperations();
@@ -534,24 +1223,35 @@ ${initializations}
534
1223
  const methods = operations
535
1224
  .map((operation) => {
536
1225
  const methodName = this.generateMethodName(operation);
537
- const jsdoc = this.generateJSDoc(operation);
1226
+ const jsdoc = this.generateDetailedJSDoc(operation); // Use detailed JSDoc like multi-resource
538
1227
  const signature = this.generateMethodSignature(operation);
539
- const implementation = this.generateMethodImplementation(operation);
1228
+ const implementation = this.generateControllerMethodImplementation(operation); // Use improved implementation
540
1229
  return ` /**\n${jsdoc}\n */\n async ${methodName}${signature} {\n${implementation}\n }`;
541
1230
  })
542
1231
  .join('\n\n');
543
1232
  // Get base URL from servers if available
544
- const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'API_BASE_URL';
1233
+ const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'https://api.example.com';
1234
+ // Generate TypeScript interfaces
1235
+ const interfaces = this.generateAllInterfaces();
1236
+ // Generate type imports
1237
+ const typeImports = this.generateTypeImports();
545
1238
  const startMethod = ` private api: any;
546
1239
 
547
1240
  protected async start(): Promise<void> {
1241
+ const config = this.config;
1242
+
548
1243
  this.api = this.getClient({
549
1244
  baseUrl: '${baseUrl}',
1245
+ customize(request) {
1246
+ request.headers ||= {};
1247
+ // Add authentication headers based on your API requirements
1248
+ // Example: request.headers["Authorization"] = \`Bearer \${config.apiToken}\`;
1249
+ },
550
1250
  });
551
1251
  }`;
552
1252
  return `import {AbstractController} from '@aloma.io/integration-sdk';
553
1253
 
554
- export default class Controller extends AbstractController {
1254
+ ${typeImports}${interfaces}export default class Controller extends AbstractController {
555
1255
 
556
1256
  ${startMethod}
557
1257