@aloma.io/integration-sdk 3.8.63 → 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 +19 -8
- package/build/openapi-to-connector.mjs +11 -4
- package/package.json +1 -1
- package/src/cli.mts +24 -8
- package/src/openapi-to-connector.mts +13 -4
- package/test/openapi-ts-compilation.test.mjs +178 -0
- package/test/scenarios/complex/expected/controller.mts +14 -14
- package/test/scenarios/complex/expected/orders-resource.mts +5 -5
- package/test/scenarios/complex/expected/products-resource.mts +5 -5
- package/test/scenarios/simple/expected-controller.mts +2 -2
- package/test/verify-scenarios.mjs +4 -4
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
|
|
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
|
-
//
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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!`);
|
|
@@ -158,7 +158,9 @@ export class OpenAPIToConnector {
|
|
|
158
158
|
// Extract the last part after underscore if it exists
|
|
159
159
|
const parts = cleaned.split('_');
|
|
160
160
|
if (parts.length > 1) {
|
|
161
|
-
|
|
161
|
+
let lastPart = parts[parts.length - 1];
|
|
162
|
+
// Convert hyphens to camelCase before testing
|
|
163
|
+
lastPart = lastPart.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
162
164
|
// If the last part looks like a method name (camelCase), use it
|
|
163
165
|
if (lastPart && /^[a-z][a-zA-Z0-9]*$/.test(lastPart)) {
|
|
164
166
|
cleaned = lastPart;
|
|
@@ -198,7 +200,7 @@ export class OpenAPIToConnector {
|
|
|
198
200
|
.filter((p) => p.toLowerCase() !== baseName.toLowerCase());
|
|
199
201
|
// Use the last path segment before the method name as a distinguishing prefix
|
|
200
202
|
if (pathParts.length > 0) {
|
|
201
|
-
const prefix = pathParts[pathParts.length - 1];
|
|
203
|
+
const prefix = pathParts[pathParts.length - 1].replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
202
204
|
const capitalizedBase = baseName.charAt(0).toUpperCase() + baseName.slice(1);
|
|
203
205
|
return `${prefix}${capitalizedBase}`;
|
|
204
206
|
}
|
|
@@ -253,6 +255,8 @@ export class OpenAPIToConnector {
|
|
|
253
255
|
if (hasBody) {
|
|
254
256
|
this.addRequestBodyProperties(operation.requestBody, optionProps);
|
|
255
257
|
}
|
|
258
|
+
// Always include headers in options type (generated code references options.headers)
|
|
259
|
+
optionProps.push('headers?: Record<string, string>');
|
|
256
260
|
// Check if options parameter is required (has required query params or required body)
|
|
257
261
|
const hasRequiredNonPathParams = queryParams.some((p) => p.required) || (hasBody && operation.requestBody?.required);
|
|
258
262
|
const optionsRequired = hasRequiredNonPathParams ? '' : '?';
|
|
@@ -320,6 +324,8 @@ export class OpenAPIToConnector {
|
|
|
320
324
|
if (hasBody) {
|
|
321
325
|
this.addRequestBodyProperties(operation.requestBody, optionProps);
|
|
322
326
|
}
|
|
327
|
+
// Always include headers in options type (generated code references options.headers)
|
|
328
|
+
optionProps.push('headers?: Record<string, string>');
|
|
323
329
|
// If there are too many parameters, use simplified signature to avoid parsing issues
|
|
324
330
|
// Also check if any parameter name is too long (over 100 chars) which can cause issues
|
|
325
331
|
const hasLongParamNames = optionProps.some((prop) => prop.length > 100);
|
|
@@ -340,11 +346,12 @@ export class OpenAPIToConnector {
|
|
|
340
346
|
* Resolve a schema reference to a TypeScript type name
|
|
341
347
|
*/
|
|
342
348
|
resolveSchemaRef(ref) {
|
|
343
|
-
// Extract the component name from the reference
|
|
344
|
-
// e.g., "#/components/schemas/Company" -> "Company"
|
|
345
349
|
const parts = ref.split('/');
|
|
346
350
|
if (parts.length >= 2) {
|
|
347
351
|
const componentName = parts[parts.length - 1];
|
|
352
|
+
if (!this.spec.components?.schemas?.[componentName]) {
|
|
353
|
+
return 'any';
|
|
354
|
+
}
|
|
348
355
|
return this.sanitizeTypeName(componentName);
|
|
349
356
|
}
|
|
350
357
|
return 'any';
|
package/package.json
CHANGED
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
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
|
|
@@ -197,7 +197,9 @@ export class OpenAPIToConnector {
|
|
|
197
197
|
// Extract the last part after underscore if it exists
|
|
198
198
|
const parts = cleaned.split('_');
|
|
199
199
|
if (parts.length > 1) {
|
|
200
|
-
|
|
200
|
+
let lastPart = parts[parts.length - 1];
|
|
201
|
+
// Convert hyphens to camelCase before testing
|
|
202
|
+
lastPart = lastPart.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
201
203
|
// If the last part looks like a method name (camelCase), use it
|
|
202
204
|
if (lastPart && /^[a-z][a-zA-Z0-9]*$/.test(lastPart)) {
|
|
203
205
|
cleaned = lastPart;
|
|
@@ -244,7 +246,7 @@ export class OpenAPIToConnector {
|
|
|
244
246
|
|
|
245
247
|
// Use the last path segment before the method name as a distinguishing prefix
|
|
246
248
|
if (pathParts.length > 0) {
|
|
247
|
-
const prefix = pathParts[pathParts.length - 1];
|
|
249
|
+
const prefix = pathParts[pathParts.length - 1].replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
248
250
|
const capitalizedBase = baseName.charAt(0).toUpperCase() + baseName.slice(1);
|
|
249
251
|
return `${prefix}${capitalizedBase}`;
|
|
250
252
|
}
|
|
@@ -308,6 +310,9 @@ export class OpenAPIToConnector {
|
|
|
308
310
|
this.addRequestBodyProperties(operation.requestBody, optionProps);
|
|
309
311
|
}
|
|
310
312
|
|
|
313
|
+
// Always include headers in options type (generated code references options.headers)
|
|
314
|
+
optionProps.push('headers?: Record<string, string>');
|
|
315
|
+
|
|
311
316
|
// Check if options parameter is required (has required query params or required body)
|
|
312
317
|
const hasRequiredNonPathParams =
|
|
313
318
|
queryParams.some((p) => p.required) || (hasBody && operation.requestBody?.required);
|
|
@@ -390,6 +395,9 @@ export class OpenAPIToConnector {
|
|
|
390
395
|
this.addRequestBodyProperties(operation.requestBody, optionProps);
|
|
391
396
|
}
|
|
392
397
|
|
|
398
|
+
// Always include headers in options type (generated code references options.headers)
|
|
399
|
+
optionProps.push('headers?: Record<string, string>');
|
|
400
|
+
|
|
393
401
|
// If there are too many parameters, use simplified signature to avoid parsing issues
|
|
394
402
|
// Also check if any parameter name is too long (over 100 chars) which can cause issues
|
|
395
403
|
const hasLongParamNames = optionProps.some((prop) => prop.length > 100);
|
|
@@ -411,11 +419,12 @@ export class OpenAPIToConnector {
|
|
|
411
419
|
* Resolve a schema reference to a TypeScript type name
|
|
412
420
|
*/
|
|
413
421
|
private resolveSchemaRef(ref: string): string {
|
|
414
|
-
// Extract the component name from the reference
|
|
415
|
-
// e.g., "#/components/schemas/Company" -> "Company"
|
|
416
422
|
const parts = ref.split('/');
|
|
417
423
|
if (parts.length >= 2) {
|
|
418
424
|
const componentName = parts[parts.length - 1];
|
|
425
|
+
if (!this.spec.components?.schemas?.[componentName]) {
|
|
426
|
+
return 'any';
|
|
427
|
+
}
|
|
419
428
|
return this.sanitizeTypeName(componentName);
|
|
420
429
|
}
|
|
421
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
|
});
|