@aloma.io/integration-sdk 3.8.64 → 3.8.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.mjs CHANGED
@@ -392,16 +392,27 @@ program
392
392
  // Create a temporary generator to generate just the exposed methods for this resource
393
393
  const tempGenerator = new OpenAPIToConnector(spec, 'temp', generatorOptions);
394
394
  const exposedMethods = tempGenerator.generateExposedResourceMethods(resources, resourceSpecs);
395
- // Add the exposed methods to the controller before the closing brace
395
+ // Add the exposed methods to the controller, deduplicating by method name
396
396
  if (exposedMethods.trim()) {
397
397
  controllerContent = fs.readFileSync(controllerPath, 'utf-8');
398
- // Find the last method and add the new exposed methods
399
- const lastBrace = controllerContent.lastIndexOf('}');
400
- if (lastBrace !== -1) {
401
- const beforeBrace = controllerContent.substring(0, lastBrace);
402
- const afterBrace = controllerContent.substring(lastBrace);
403
- const updatedContent = `${beforeBrace}\n${exposedMethods}\n${afterBrace}`;
404
- fs.writeFileSync(controllerPath, updatedContent);
398
+ // Extract existing method names from controller (async 'method.name' pattern)
399
+ const existingNames = new Set((controllerContent.match(/async\s+['"]([^'"]+)['"]\s*\(/g) || [])
400
+ .map(m => m.match(/['"]([^'"]+)['"]/)?.[1])
401
+ .filter(Boolean));
402
+ // Filter exposed methods to only include new ones
403
+ const methodBlocks = exposedMethods.split(/(?=\n async ['"])/);
404
+ const newMethods = methodBlocks.filter(block => {
405
+ const nameMatch = block.match(/async\s+['"]([^'"]+)['"]\s*\(/);
406
+ return !nameMatch || !existingNames.has(nameMatch[1]);
407
+ }).join('');
408
+ if (newMethods.trim()) {
409
+ const lastBrace = controllerContent.lastIndexOf('}');
410
+ if (lastBrace !== -1) {
411
+ const beforeBrace = controllerContent.substring(0, lastBrace);
412
+ const afterBrace = controllerContent.substring(lastBrace);
413
+ const updatedContent = `${beforeBrace}\n${newMethods}\n${afterBrace}`;
414
+ fs.writeFileSync(controllerPath, updatedContent);
415
+ }
405
416
  }
406
417
  }
407
418
  console.log(`✅ Resource ${options.className} added successfully!`);
@@ -255,6 +255,8 @@ export class OpenAPIToConnector {
255
255
  if (hasBody) {
256
256
  this.addRequestBodyProperties(operation.requestBody, optionProps);
257
257
  }
258
+ // Always include headers in options type (generated code references options.headers)
259
+ optionProps.push('headers?: Record<string, string>');
258
260
  // Check if options parameter is required (has required query params or required body)
259
261
  const hasRequiredNonPathParams = queryParams.some((p) => p.required) || (hasBody && operation.requestBody?.required);
260
262
  const optionsRequired = hasRequiredNonPathParams ? '' : '?';
@@ -322,6 +324,8 @@ export class OpenAPIToConnector {
322
324
  if (hasBody) {
323
325
  this.addRequestBodyProperties(operation.requestBody, optionProps);
324
326
  }
327
+ // Always include headers in options type (generated code references options.headers)
328
+ optionProps.push('headers?: Record<string, string>');
325
329
  // If there are too many parameters, use simplified signature to avoid parsing issues
326
330
  // Also check if any parameter name is too long (over 100 chars) which can cause issues
327
331
  const hasLongParamNames = optionProps.some((prop) => prop.length > 100);
@@ -342,11 +346,12 @@ export class OpenAPIToConnector {
342
346
  * Resolve a schema reference to a TypeScript type name
343
347
  */
344
348
  resolveSchemaRef(ref) {
345
- // Extract the component name from the reference
346
- // e.g., "#/components/schemas/Company" -> "Company"
347
349
  const parts = ref.split('/');
348
350
  if (parts.length >= 2) {
349
351
  const componentName = parts[parts.length - 1];
352
+ if (!this.spec.components?.schemas?.[componentName]) {
353
+ return 'any';
354
+ }
350
355
  return this.sanitizeTypeName(componentName);
351
356
  }
352
357
  return 'any';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aloma.io/integration-sdk",
3
- "version": "3.8.64",
3
+ "version": "3.8.66",
4
4
  "description": "",
5
5
  "author": "aloma.io",
6
6
  "license": "Apache-2.0",
package/src/cli.mts CHANGED
@@ -485,16 +485,32 @@ program
485
485
  const tempGenerator = new OpenAPIToConnector(spec, 'temp', generatorOptions);
486
486
  const exposedMethods = tempGenerator.generateExposedResourceMethods(resources, resourceSpecs);
487
487
 
488
- // Add the exposed methods to the controller before the closing brace
488
+ // Add the exposed methods to the controller, deduplicating by method name
489
489
  if (exposedMethods.trim()) {
490
490
  controllerContent = fs.readFileSync(controllerPath, 'utf-8');
491
- // Find the last method and add the new exposed methods
492
- const lastBrace = controllerContent.lastIndexOf('}');
493
- if (lastBrace !== -1) {
494
- const beforeBrace = controllerContent.substring(0, lastBrace);
495
- const afterBrace = controllerContent.substring(lastBrace);
496
- const updatedContent = `${beforeBrace}\n${exposedMethods}\n${afterBrace}`;
497
- fs.writeFileSync(controllerPath, updatedContent);
491
+
492
+ // Extract existing method names from controller (async 'method.name' pattern)
493
+ const existingNames = new Set(
494
+ (controllerContent.match(/async\s+['"]([^'"]+)['"]\s*\(/g) || [])
495
+ .map(m => m.match(/['"]([^'"]+)['"]/)?.[1])
496
+ .filter(Boolean)
497
+ );
498
+
499
+ // Filter exposed methods to only include new ones
500
+ const methodBlocks = exposedMethods.split(/(?=\n async ['"])/);
501
+ const newMethods = methodBlocks.filter(block => {
502
+ const nameMatch = block.match(/async\s+['"]([^'"]+)['"]\s*\(/);
503
+ return !nameMatch || !existingNames.has(nameMatch[1]);
504
+ }).join('');
505
+
506
+ if (newMethods.trim()) {
507
+ const lastBrace = controllerContent.lastIndexOf('}');
508
+ if (lastBrace !== -1) {
509
+ const beforeBrace = controllerContent.substring(0, lastBrace);
510
+ const afterBrace = controllerContent.substring(lastBrace);
511
+ const updatedContent = `${beforeBrace}\n${newMethods}\n${afterBrace}`;
512
+ fs.writeFileSync(controllerPath, updatedContent);
513
+ }
498
514
  }
499
515
  }
500
516
 
@@ -310,6 +310,9 @@ export class OpenAPIToConnector {
310
310
  this.addRequestBodyProperties(operation.requestBody, optionProps);
311
311
  }
312
312
 
313
+ // Always include headers in options type (generated code references options.headers)
314
+ optionProps.push('headers?: Record<string, string>');
315
+
313
316
  // Check if options parameter is required (has required query params or required body)
314
317
  const hasRequiredNonPathParams =
315
318
  queryParams.some((p) => p.required) || (hasBody && operation.requestBody?.required);
@@ -392,6 +395,9 @@ export class OpenAPIToConnector {
392
395
  this.addRequestBodyProperties(operation.requestBody, optionProps);
393
396
  }
394
397
 
398
+ // Always include headers in options type (generated code references options.headers)
399
+ optionProps.push('headers?: Record<string, string>');
400
+
395
401
  // If there are too many parameters, use simplified signature to avoid parsing issues
396
402
  // Also check if any parameter name is too long (over 100 chars) which can cause issues
397
403
  const hasLongParamNames = optionProps.some((prop) => prop.length > 100);
@@ -413,11 +419,12 @@ export class OpenAPIToConnector {
413
419
  * Resolve a schema reference to a TypeScript type name
414
420
  */
415
421
  private resolveSchemaRef(ref: string): string {
416
- // Extract the component name from the reference
417
- // e.g., "#/components/schemas/Company" -> "Company"
418
422
  const parts = ref.split('/');
419
423
  if (parts.length >= 2) {
420
424
  const componentName = parts[parts.length - 1];
425
+ if (!this.spec.components?.schemas?.[componentName]) {
426
+ return 'any';
427
+ }
421
428
  return this.sanitizeTypeName(componentName);
422
429
  }
423
430
  return 'any';
@@ -0,0 +1,178 @@
1
+ import assert from 'assert';
2
+ import { writeFileSync, readFileSync, mkdirSync, rmSync, existsSync } from 'fs';
3
+ import { execSync } from 'child_process';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const SDK_ROOT = join(__dirname, '..');
9
+
10
+ const { OpenAPIToConnector } = await import('../build/openapi-to-connector.mjs');
11
+
12
+ let passed = 0;
13
+ let failed = 0;
14
+
15
+ function test(name, fn) {
16
+ try { fn(); console.log(`✓ PASS: ${name}`); passed++; }
17
+ catch(e) { console.log(`✗ FAIL: ${name} — ${e.message}`); failed++; }
18
+ }
19
+
20
+ // --- Bug A: schema type references that are not locally defined ---
21
+ // When a $ref points to a schema NOT present in the spec's components.schemas,
22
+ // resolveSchemaRef still emits the type name (e.g., PublicAssociationsForObject)
23
+ // in the function signature. Since no interface is generated, tsc fails with TS2304.
24
+
25
+ test('Bug A: resolveSchemaRef emits any for types not in components.schemas', () => {
26
+ const spec = {
27
+ openapi: '3.0.0',
28
+ info: { title: 'Test', version: '1.0' },
29
+ paths: {
30
+ '/crm/contacts/{contactId}': {
31
+ put: {
32
+ operationId: 'updateContact',
33
+ parameters: [{ name: 'contactId', in: 'path', required: true, schema: { type: 'string' } }],
34
+ requestBody: {
35
+ required: true,
36
+ content: {
37
+ 'application/json': {
38
+ schema: {
39
+ type: 'object',
40
+ properties: {
41
+ associations: { $ref: '#/components/schemas/PublicAssociationsForObject' },
42
+ properties: { type: 'object' }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ },
48
+ responses: { '200': { description: 'OK' } }
49
+ }
50
+ }
51
+ },
52
+ // Schema is referenced but NOT defined — simulates cross-file ref or incomplete spec
53
+ components: { schemas: {} }
54
+ };
55
+
56
+ const gen = new OpenAPIToConnector(spec, 'contacts', { nestedPaths: true });
57
+ const resourceCode = gen.generateResourceClass('ContactsResource');
58
+
59
+ // The type name must NOT appear in generated code when no interface is emitted
60
+ const hasTypeRef = resourceCode.includes('PublicAssociationsForObject');
61
+ const hasInterface = resourceCode.includes('interface PublicAssociationsForObject');
62
+
63
+ assert(!hasTypeRef || hasInterface,
64
+ `Type 'PublicAssociationsForObject' referenced in function signature but not defined. ` +
65
+ `resolveSchemaRef must emit 'any' when the schema is not in components.schemas.`);
66
+ });
67
+
68
+ // --- Bug B: add-resource produces duplicate functions on re-run ---
69
+ // The add-resource CLI command appends exposed methods to the controller
70
+ // without checking if they already exist. Running it twice duplicates functions.
71
+
72
+ test('Bug B: add-resource CLI deduplicates exposed methods on re-run', () => {
73
+ const spec = {
74
+ openapi: '3.0.0',
75
+ info: { title: 'Test', version: '1.0' },
76
+ paths: {
77
+ '/crm/contacts': {
78
+ get: { operationId: 'getContacts', responses: { '200': { description: 'OK' } } }
79
+ },
80
+ '/crm/contacts/{id}': {
81
+ get: {
82
+ operationId: 'getContactById',
83
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
84
+ responses: { '200': { description: 'OK' } }
85
+ }
86
+ }
87
+ }
88
+ };
89
+
90
+ const gen = new OpenAPIToConnector(spec, 'contacts', { nestedPaths: true });
91
+ const resources = [{ className: 'ContactsResource', fileName: 'contacts' }];
92
+ const resourceSpecs = [{ fileName: 'contacts', spec }];
93
+
94
+ const exposedMethods = gen.generateExposedResourceMethods(resources, resourceSpecs);
95
+
96
+ // Simulate a controller that already contains these methods (from first add-resource run)
97
+ const existingController = `export default class Controller {\n${exposedMethods}\n}`;
98
+
99
+ // Simulate the fixed CLI dedup logic: extract existing names, filter new methods
100
+ const existingNames = new Set(
101
+ (existingController.match(/async\s+['"]([^'"]+)['"]\s*\(/g) || [])
102
+ .map(m => m.match(/['"]([^'"]+)['"]/)?.[1])
103
+ .filter(Boolean)
104
+ );
105
+
106
+ const methodBlocks = exposedMethods.split(/(?=\n async ['"])/);
107
+ const newMethods = methodBlocks.filter(block => {
108
+ const nameMatch = block.match(/async\s+['"]([^'"]+)['"]\s*\(/);
109
+ return !nameMatch || !existingNames.has(nameMatch[1]);
110
+ }).join('');
111
+
112
+ // After dedup, no new methods should be added (all already exist)
113
+ const remainingDeclarations = newMethods.match(/async\s+['"]([^'"]+)['"]\s*\(/g) || [];
114
+ assert(remainingDeclarations.length === 0,
115
+ `Dedup failed: ${remainingDeclarations.length} methods would be re-added. ` +
116
+ `CLI must filter out methods already present in the controller.`);
117
+ });
118
+
119
+ // --- Bug C: generated code references options.headers but type lacks headers field ---
120
+ // The generator emits `const { headers, ...bodyData } = options;` and
121
+ // `headers: options.headers` in fetchOptions, but the options type generated
122
+ // for the function signature does NOT include `headers?: Record<string, string>`.
123
+
124
+ test('Bug C: generated method with body includes headers in options type', () => {
125
+ const spec = {
126
+ openapi: '3.0.0',
127
+ info: { title: 'Test', version: '1.0' },
128
+ paths: {
129
+ '/crm/contacts': {
130
+ post: {
131
+ operationId: 'createContact',
132
+ requestBody: {
133
+ required: true,
134
+ content: {
135
+ 'application/json': {
136
+ schema: {
137
+ type: 'object',
138
+ properties: {
139
+ email: { type: 'string' },
140
+ firstname: { type: 'string' }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ },
146
+ responses: { '201': { description: 'Created' } }
147
+ }
148
+ }
149
+ }
150
+ };
151
+
152
+ const gen = new OpenAPIToConnector(spec, 'contacts', { nestedPaths: true });
153
+ const resourceCode = gen.generateResourceClass('ContactsResource');
154
+
155
+ // The generator emits `const { headers, ...bodyData } = options;` and
156
+ // `headers: options.headers` — so the options type MUST include headers
157
+ const usesHeaders = resourceCode.includes('options.headers') ||
158
+ resourceCode.includes('options?.headers') ||
159
+ resourceCode.includes('{ headers,') ||
160
+ resourceCode.includes('{ headers }');
161
+
162
+ if (usesHeaders) {
163
+ // Extract the function signature to check just the type definition
164
+ const sigMatch = resourceCode.match(/export function createContact\(this: any,([^)]+)\)/s);
165
+ const signature = sigMatch ? sigMatch[1] : '';
166
+ const hasHeadersInType = signature.includes('headers');
167
+ assert(hasHeadersInType,
168
+ `Generated code destructures/accesses 'headers' from options but the function ` +
169
+ `signature's options type does not include a headers field. ` +
170
+ `Signature: ${signature.trim()}`);
171
+ } else {
172
+ // If headers is not used, that's also a valid fix (removing the reference)
173
+ assert(true);
174
+ }
175
+ });
176
+
177
+ console.log(`\n${passed} passing / ${failed} failing`);
178
+ if (failed > 0) process.exit(1);
@@ -58,7 +58,7 @@ export default class Controller extends AbstractController {
58
58
  * - total?: number - Total number of products
59
59
  * - hasMore?: boolean - Whether there are more products available
60
60
  */
61
- async productsGetProducts(options?: {limit?: number, category?: string, archived?: boolean}) {
61
+ async productsGetProducts(options?: {limit?: number, category?: string, archived?: boolean, headers?: Record<string, string>}) {
62
62
  return this.products.getProducts(options);
63
63
  }
64
64
 
@@ -85,7 +85,7 @@ export default class Controller extends AbstractController {
85
85
  * - createdAt?: string - Creation timestamp
86
86
  * - updatedAt?: string - Last update timestamp
87
87
  */
88
- async productsCreateProduct(options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, tags: string[] /** Product tags */}) {
88
+ async productsCreateProduct(options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, tags: string[] /** Product tags */, headers?: Record<string, string>}) {
89
89
  return this.products.createProduct(options);
90
90
  }
91
91
 
@@ -108,8 +108,8 @@ export default class Controller extends AbstractController {
108
108
  * - createdAt?: string - Creation timestamp
109
109
  * - updatedAt?: string - Last update timestamp
110
110
  */
111
- async productsGetProduct(productId: string) {
112
- return this.products.getProduct(productId);
111
+ async productsGetProduct(productId: string, options?: {headers?: Record<string, string>}) {
112
+ return this.products.getProduct(productId, options);
113
113
  }
114
114
 
115
115
  /**
@@ -137,7 +137,7 @@ export default class Controller extends AbstractController {
137
137
  * - createdAt?: string - Creation timestamp
138
138
  * - updatedAt?: string - Last update timestamp
139
139
  */
140
- async productsUpdateProduct(productId: string, options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, inStock: boolean /** Whether product is in stock */, tags: string[] /** Product tags */}) {
140
+ async productsUpdateProduct(productId: string, options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, inStock: boolean /** Whether product is in stock */, tags: string[] /** Product tags */, headers?: Record<string, string>}) {
141
141
  return this.products.updateProduct(productId, options);
142
142
  }
143
143
 
@@ -149,8 +149,8 @@ export default class Controller extends AbstractController {
149
149
  *
150
150
  * @returns {Promise<any>} DELETE /products/{productId} response
151
151
  */
152
- async productsDeleteProduct(productId: string) {
153
- return this.products.deleteProduct(productId);
152
+ async productsDeleteProduct(productId: string, options?: {headers?: Record<string, string>}) {
153
+ return this.products.deleteProduct(productId, options);
154
154
  }
155
155
 
156
156
  /**
@@ -168,7 +168,7 @@ export default class Controller extends AbstractController {
168
168
  * - total?: number - Total number of orders
169
169
  * - hasMore?: boolean - Whether there are more orders available
170
170
  */
171
- async ordersGetOrders(options?: {status?: string, customerId?: string, limit?: number}) {
171
+ async ordersGetOrders(options?: {status?: string, customerId?: string, limit?: number, headers?: Record<string, string>}) {
172
172
  return this.orders.getOrders(options);
173
173
  }
174
174
 
@@ -194,7 +194,7 @@ export default class Controller extends AbstractController {
194
194
  * - createdAt?: string - Order creation timestamp
195
195
  * - updatedAt?: string - Last update timestamp
196
196
  */
197
- async ordersCreateOrder(options: {customerId: string /** Customer who is placing the order */, items: OrderItemRequest[] /** Items to order */, shippingAddress: Address, billingAddress: Address}) {
197
+ async ordersCreateOrder(options: {customerId: string /** Customer who is placing the order */, items: OrderItemRequest[] /** Items to order */, shippingAddress: Address, billingAddress: Address, headers?: Record<string, string>}) {
198
198
  return this.orders.createOrder(options);
199
199
  }
200
200
 
@@ -217,8 +217,8 @@ export default class Controller extends AbstractController {
217
217
  * - createdAt?: string - Order creation timestamp
218
218
  * - updatedAt?: string - Last update timestamp
219
219
  */
220
- async ordersGetOrder(orderId: string) {
221
- return this.orders.getOrder(orderId);
220
+ async ordersGetOrder(orderId: string, options?: {headers?: Record<string, string>}) {
221
+ return this.orders.getOrder(orderId, options);
222
222
  }
223
223
 
224
224
  /**
@@ -242,7 +242,7 @@ export default class Controller extends AbstractController {
242
242
  * - createdAt?: string - Order creation timestamp
243
243
  * - updatedAt?: string - Last update timestamp
244
244
  */
245
- async ordersUpdateOrderStatus(orderId: string, options: {status: string /** New order status */, trackingNumber: string /** Tracking number (for shipped status) */}) {
245
+ async ordersUpdateOrderStatus(orderId: string, options: {status: string /** New order status */, trackingNumber: string /** Tracking number (for shipped status) */, headers?: Record<string, string>}) {
246
246
  return this.orders.updateOrderStatus(orderId, options);
247
247
  }
248
248
 
@@ -265,7 +265,7 @@ export default class Controller extends AbstractController {
265
265
  * - createdAt?: string - Order creation timestamp
266
266
  * - updatedAt?: string - Last update timestamp
267
267
  */
268
- async ordersCancelOrder(orderId: string) {
269
- return this.orders.cancelOrder(orderId);
268
+ async ordersCancelOrder(orderId: string, options?: {headers?: Record<string, string>}) {
269
+ return this.orders.cancelOrder(orderId, options);
270
270
  }
271
271
  }
@@ -95,7 +95,7 @@ export interface UpdateOrderStatusRequest {
95
95
  * - total?: number - Total number of orders
96
96
  * - hasMore?: boolean - Whether there are more orders available
97
97
  */
98
- export function getOrders(this: any, options?: {status?: string, customerId?: string, limit?: number}) {
98
+ export function getOrders(this: any, options?: {status?: string, customerId?: string, limit?: number, headers?: Record<string, string>}) {
99
99
  options = options || {};
100
100
 
101
101
  const url = '/orders';
@@ -142,7 +142,7 @@ export function getOrders(this: any, options?: {status?: string, customerId?: st
142
142
  * - createdAt?: string - Order creation timestamp
143
143
  * - updatedAt?: string - Last update timestamp
144
144
  */
145
- export function createOrder(this: any, options: {customerId: string /** Customer who is placing the order */, items: OrderItemRequest[] /** Items to order */, shippingAddress: Address, billingAddress: Address}) {
145
+ export function createOrder(this: any, options: {customerId: string /** Customer who is placing the order */, items: OrderItemRequest[] /** Items to order */, shippingAddress: Address, billingAddress: Address, headers?: Record<string, string>}) {
146
146
  options = options || {};
147
147
 
148
148
  const url = '/orders';
@@ -178,7 +178,7 @@ export function createOrder(this: any, options: {customerId: string /** Customer
178
178
  * - createdAt?: string - Order creation timestamp
179
179
  * - updatedAt?: string - Last update timestamp
180
180
  */
181
- export function getOrder(this: any, orderId: string) {
181
+ export function getOrder(this: any, orderId: string, options?: {headers?: Record<string, string>}) {
182
182
  let url = '/orders/{orderId}';
183
183
  if (orderId) {
184
184
  url = url.replace('{orderId}', orderId);
@@ -210,7 +210,7 @@ export function getOrder(this: any, orderId: string) {
210
210
  * - createdAt?: string - Order creation timestamp
211
211
  * - updatedAt?: string - Last update timestamp
212
212
  */
213
- export function updateOrderStatus(this: any, orderId: string, options: {status: string /** New order status */, trackingNumber: string /** Tracking number (for shipped status */})) {
213
+ export function updateOrderStatus(this: any, orderId: string, options: {status: string /** New order status */, trackingNumber: string /** Tracking number (for shipped status */, headers?: Record<string, string>})) {
214
214
  options = options || {};
215
215
 
216
216
  // Build URL with path parameters
@@ -250,7 +250,7 @@ export function updateOrderStatus(this: any, orderId: string, options: {status:
250
250
  * - createdAt?: string - Order creation timestamp
251
251
  * - updatedAt?: string - Last update timestamp
252
252
  */
253
- export function cancelOrder(this: any, orderId: string) {
253
+ export function cancelOrder(this: any, orderId: string, options?: {headers?: Record<string, string>}) {
254
254
  let url = '/orders/{orderId}/cancel';
255
255
  if (orderId) {
256
256
  url = url.replace('{orderId}', orderId);
@@ -76,7 +76,7 @@ export interface UpdateProductRequest {
76
76
  * - total?: number - Total number of products
77
77
  * - hasMore?: boolean - Whether there are more products available
78
78
  */
79
- export function getProducts(this: any, options?: {limit?: number, category?: string, archived?: boolean}) {
79
+ export function getProducts(this: any, options?: {limit?: number, category?: string, archived?: boolean, headers?: Record<string, string>}) {
80
80
  options = options || {};
81
81
 
82
82
  const url = '/products';
@@ -124,7 +124,7 @@ export function getProducts(this: any, options?: {limit?: number, category?: str
124
124
  * - createdAt?: string - Creation timestamp
125
125
  * - updatedAt?: string - Last update timestamp
126
126
  */
127
- export function createProduct(this: any, options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, tags: string[] /** Product tags */}) {
127
+ export function createProduct(this: any, options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, tags: string[] /** Product tags */, headers?: Record<string, string>}) {
128
128
  options = options || {};
129
129
 
130
130
  const url = '/products';
@@ -160,7 +160,7 @@ export function createProduct(this: any, options: {name: string /** Product name
160
160
  * - createdAt?: string - Creation timestamp
161
161
  * - updatedAt?: string - Last update timestamp
162
162
  */
163
- export function getProduct(this: any, productId: string) {
163
+ export function getProduct(this: any, productId: string, options?: {headers?: Record<string, string>}) {
164
164
  let url = '/products/{productId}';
165
165
  if (productId) {
166
166
  url = url.replace('{productId}', productId);
@@ -196,7 +196,7 @@ export function getProduct(this: any, productId: string) {
196
196
  * - createdAt?: string - Creation timestamp
197
197
  * - updatedAt?: string - Last update timestamp
198
198
  */
199
- export function updateProduct(this: any, productId: string, options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, inStock: boolean /** Whether product is in stock */, tags: string[] /** Product tags */}) {
199
+ export function updateProduct(this: any, productId: string, options: {name: string /** Product name */, description: string /** Product description */, price: number /** Product price */, category: string /** Product category */, inStock: boolean /** Whether product is in stock */, tags: string[] /** Product tags */, headers?: Record<string, string>}) {
200
200
  options = options || {};
201
201
 
202
202
  // Build URL with path parameters
@@ -225,7 +225,7 @@ export function updateProduct(this: any, productId: string, options: {name: stri
225
225
  *
226
226
  * @returns {Promise<any>} DELETE /products/{productId} response
227
227
  */
228
- export function deleteProduct(this: any, productId: string) {
228
+ export function deleteProduct(this: any, productId: string, options?: {headers?: Record<string, string>}) {
229
229
  let url = '/products/{productId}';
230
230
  if (productId) {
231
231
  url = url.replace('{productId}', productId);
@@ -23,7 +23,7 @@ export default class Controller extends AbstractController {
23
23
  *
24
24
  * @returns {Promise<any>} GET /products response
25
25
  */
26
- async getProducts() {
26
+ async getProducts(options?: {headers?: Record<string, string>}) {
27
27
  const url = '/products';
28
28
 
29
29
  const fetchOptions: any = {
@@ -41,7 +41,7 @@ export default class Controller extends AbstractController {
41
41
  *
42
42
  * @returns {Promise<any>} POST /products response
43
43
  */
44
- async createProduct(options: {body?: any}) {
44
+ async createProduct(options: {body?: any, headers?: Record<string, string>}) {
45
45
  options = options || {};
46
46
 
47
47
  const url = '/products';
@@ -86,7 +86,7 @@ test('Simple Scenario - methods without options should be clean', () => {
86
86
  const generator = new OpenAPIToConnector(spec, 'simple-test');
87
87
  const actualOutput = generator.generateController();
88
88
 
89
- expect(actualOutput).toInclude('async getProducts() {');
89
+ expect(actualOutput).toInclude('async getProducts(options?: {headers?: Record<string, string>}) {');
90
90
  expect(actualOutput).toInclude('async createProduct(options:');
91
91
  });
92
92
 
@@ -173,9 +173,9 @@ test('Path parameters and options should be handled correctly', () => {
173
173
  const generator = new OpenAPIToConnector(spec, 'test-shop');
174
174
  const actualOutput = generator.generateResourceClass('OrdersResource');
175
175
 
176
- expect(actualOutput).toInclude('export function getOrder(this: any, orderId: string)');
176
+ expect(actualOutput).toInclude('export function getOrder(this: any, orderId: string, options?: {headers?: Record<string, string>})');
177
177
  expect(actualOutput).toInclude('export function updateOrderStatus(this: any, orderId: string, options');
178
- expect(actualOutput).toInclude('export function cancelOrder(this: any, orderId: string)');
178
+ expect(actualOutput).toInclude('export function cancelOrder(this: any, orderId: string, options?: {headers?: Record<string, string>})');
179
179
  });
180
180
 
181
181
  // Test 8: Exposed Methods in Main Controller
@@ -279,7 +279,7 @@ test('Simple methods should not have options parameter', () => {
279
279
  const generator = new OpenAPIToConnector(simpleSpec, 'test');
280
280
  const output = generator.generateController();
281
281
 
282
- expect(output).toInclude('async getSimple() {');
282
+ expect(output).toInclude('async getSimple(options?: {headers?: Record<string, string>}) {');
283
283
  expect(output).toNotInclude('options = options || {}');
284
284
  expect(output).toNotInclude('headers: options');
285
285
  });