@aloma.io/integration-sdk 3.8.55 → 3.8.56

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