@aloma.io/integration-sdk 3.8.52 → 3.8.54

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.
@@ -45,11 +45,21 @@ export class OpenAPIToConnector {
45
45
  throw new Error(`Failed to parse OpenAPI spec: ${error instanceof Error ? error.message : 'Unknown error'}`);
46
46
  }
47
47
  }
48
- // Validate against OpenAPI 3.x schema
48
+ // Validate against OpenAPI 3.x schema with lenient validation
49
49
  const validationResult = OpenAPISchema.safeParse(parsed);
50
50
  if (!validationResult.success) {
51
- const errors = validationResult.error.errors.map((err) => `${err.path.join('.')}: ${err.message}`).join(', ');
52
- throw new Error(`Invalid OpenAPI 3.x specification: ${errors}`);
51
+ // Check if the errors are just about missing 'type' fields in schemas
52
+ const criticalErrors = validationResult.error.errors.filter((err) => {
53
+ const path = err.path.join('.');
54
+ // Allow missing 'type' in schema definitions as many OpenAPI specs don't include it
55
+ return !path.includes('components.schemas') || !err.message.includes('Required');
56
+ });
57
+ if (criticalErrors.length > 0) {
58
+ const errors = criticalErrors.map((err) => `${err.path.join('.')}: ${err.message}`).join(', ');
59
+ throw new Error(`Invalid OpenAPI 3.x specification: ${errors}`);
60
+ }
61
+ // Log a warning about lenient validation
62
+ console.warn('⚠️ OpenAPI spec has some validation warnings but proceeding with lenient validation...');
53
63
  }
54
64
  return parsed;
55
65
  }
@@ -132,6 +142,9 @@ export class OpenAPIToConnector {
132
142
  */
133
143
  generateJSDoc(operation) {
134
144
  const lines = [];
145
+ const pathParams = [];
146
+ const queryParams = [];
147
+ const hasBody = !!operation.requestBody;
135
148
  if (operation.summary) {
136
149
  lines.push(` * ${operation.summary}`);
137
150
  }
@@ -139,40 +152,63 @@ export class OpenAPIToConnector {
139
152
  lines.push(` *`);
140
153
  // Split long descriptions into multiple lines
141
154
  const descLines = operation.description.split('\n');
142
- descLines.forEach(line => {
155
+ descLines.forEach((line) => {
143
156
  if (line.trim()) {
144
157
  lines.push(` * ${line.trim()}`);
145
158
  }
146
159
  });
147
160
  }
148
- // Document parameters with full details
149
- if (operation.parameters && operation.parameters.length > 0) {
150
- lines.push(' *');
151
- lines.push(' * @param {Object} args - Request arguments');
161
+ // Identify path and query parameters
162
+ if (operation.parameters) {
152
163
  for (const param of operation.parameters) {
153
- if (typeof param === 'object' && 'name' in param) {
154
- const paramName = param.name;
155
- const paramDesc = param.description || '';
156
- const paramRequired = param.required ? '(required)' : '(optional)';
157
- const paramType = param.schema?.type || 'any';
158
- const paramIn = param.in || '';
159
- let paramDoc = ` * @param {${paramType}} args.${paramName} ${paramRequired}`;
160
- if (paramDesc) {
161
- paramDoc += ` - ${paramDesc}`;
164
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
165
+ if (param.in === 'path') {
166
+ pathParams.push(param);
162
167
  }
163
- if (paramIn) {
164
- paramDoc += ` [${paramIn}]`;
168
+ else if (param.in === 'query') {
169
+ queryParams.push(param);
165
170
  }
166
- lines.push(paramDoc);
167
171
  }
168
172
  }
169
173
  }
170
- // Document request body
171
- if (operation.requestBody) {
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
172
188
  lines.push(' *');
173
- const bodyDesc = operation.requestBody.description || 'Request body';
174
- const required = operation.requestBody.required ? '(required)' : '(optional)';
175
- lines.push(` * @param {Object} args.body ${required} - ${bodyDesc}`);
189
+ lines.push(` * @param {Object} options (optional) - Request options`);
190
+ // Document path parameters
191
+ for (const param of pathParams) {
192
+ const paramType = param.schema?.type || 'string';
193
+ const paramDesc = param.description || '';
194
+ const paramRequired = param.required ? '(required)' : '(optional)';
195
+ lines.push(` * @param {${paramType}} options.${param.name} ${paramRequired} - ${paramDesc} [path]`);
196
+ }
197
+ // Document query parameters
198
+ for (const param of queryParams) {
199
+ const paramType = param.schema?.type || 'any';
200
+ const paramDesc = param.description || '';
201
+ const paramRequired = param.required ? '(required)' : '(optional)';
202
+ lines.push(` * @param {${paramType}} options.${param.name} ${paramRequired} - ${paramDesc} [query]`);
203
+ }
204
+ // Document request body
205
+ if (operation.requestBody) {
206
+ const bodyDesc = operation.requestBody.description || 'Request body';
207
+ const required = operation.requestBody.required ? '(required)' : '(optional)';
208
+ lines.push(` * @param {Object} options.body ${required} - ${bodyDesc}`);
209
+ }
210
+ // Document headers
211
+ lines.push(` * @param {Object} options.headers (optional) - Custom headers to include in the request`);
176
212
  }
177
213
  // Document response
178
214
  lines.push(' *');
@@ -185,6 +221,205 @@ export class OpenAPIToConnector {
185
221
  getOperationsCount() {
186
222
  return this.extractOperations().length;
187
223
  }
224
+ /**
225
+ * Generate method signature with options object
226
+ */
227
+ generateMethodSignature(operation) {
228
+ const pathParams = [];
229
+ const queryParams = [];
230
+ const hasBody = !!operation.requestBody;
231
+ // Identify path and query parameters
232
+ if (operation.parameters) {
233
+ for (const param of operation.parameters) {
234
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
235
+ if (param.in === 'path') {
236
+ pathParams.push(param.name);
237
+ }
238
+ else if (param.in === 'query') {
239
+ queryParams.push(param.name);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ // If there are no query params, no body, and only path params, use simple signature
245
+ if (queryParams.length === 0 && !hasBody && pathParams.length <= 1) {
246
+ const params = [];
247
+ for (const paramName of pathParams) {
248
+ params.push(`${paramName}: string`);
249
+ }
250
+ params.push(`options?: {headers?: {[key: string]: any}}`);
251
+ return `(${params.join(', ')})`;
252
+ }
253
+ // Otherwise, use options object pattern
254
+ return `(options?: {${[
255
+ ...pathParams.map((p) => `${p}?: string`),
256
+ ...queryParams.map((p) => `${p}?: any`),
257
+ hasBody ? 'body?: any' : '',
258
+ 'headers?: {[key: string]: any}',
259
+ ]
260
+ .filter(Boolean)
261
+ .join(', ')}})`;
262
+ }
263
+ /**
264
+ * Generate method implementation code
265
+ */
266
+ generateMethodImplementation(operation) {
267
+ const lines = [];
268
+ // Build URL with path parameters
269
+ let url = operation.path;
270
+ const pathParams = [];
271
+ const queryParams = [];
272
+ const hasBody = !!operation.requestBody;
273
+ // Identify path and query parameters
274
+ if (operation.parameters) {
275
+ for (const param of operation.parameters) {
276
+ if (typeof param === 'object' && 'name' in param && 'in' in param) {
277
+ if (param.in === 'path') {
278
+ pathParams.push(param.name);
279
+ }
280
+ else if (param.in === 'query') {
281
+ queryParams.push(param.name);
282
+ }
283
+ }
284
+ }
285
+ }
286
+ // Check if using simple signature (single path param, no query/body)
287
+ const useSimpleSignature = queryParams.length === 0 && !hasBody && pathParams.length <= 1;
288
+ if (useSimpleSignature && pathParams.length === 1) {
289
+ // Simple signature: (pathParam: string, options?: {headers?: ...})
290
+ const paramName = pathParams[0];
291
+ lines.push(` let url = '${url}';`);
292
+ lines.push(` if (${paramName}) {`);
293
+ lines.push(` url = url.replace('{${paramName}}', ${paramName});`);
294
+ lines.push(` }`);
295
+ lines.push('');
296
+ lines.push(` return this.api.fetch(url, {`);
297
+ lines.push(` method: '${operation.method}',`);
298
+ lines.push(` headers: options?.headers,`);
299
+ lines.push(` });`);
300
+ }
301
+ else {
302
+ // Options object pattern
303
+ lines.push(` options = options || {};`);
304
+ lines.push('');
305
+ // Replace path parameters
306
+ if (pathParams.length > 0) {
307
+ lines.push(` // Build URL with path parameters`);
308
+ lines.push(` let url = '${url}';`);
309
+ for (const paramName of pathParams) {
310
+ lines.push(` if (options.${paramName}) {`);
311
+ lines.push(` url = url.replace('{${paramName}}', options.${paramName});`);
312
+ lines.push(` }`);
313
+ }
314
+ lines.push('');
315
+ }
316
+ else {
317
+ lines.push(` const url = '${url}';`);
318
+ lines.push('');
319
+ }
320
+ // Build fetch options
321
+ lines.push(` const fetchOptions: any = {`);
322
+ lines.push(` method: '${operation.method}',`);
323
+ // Add query parameters
324
+ if (queryParams.length > 0) {
325
+ lines.push(` params: {},`);
326
+ }
327
+ // Add body if present
328
+ if (hasBody) {
329
+ lines.push(` body: options.body,`);
330
+ }
331
+ // Add headers if present
332
+ lines.push(` headers: options.headers,`);
333
+ lines.push(` };`);
334
+ lines.push('');
335
+ // Add query parameters to options
336
+ if (queryParams.length > 0) {
337
+ lines.push(` // Add query parameters`);
338
+ for (const paramName of queryParams) {
339
+ lines.push(` if (options.${paramName} !== undefined) {`);
340
+ lines.push(` fetchOptions.params.${paramName} = options.${paramName};`);
341
+ lines.push(` }`);
342
+ }
343
+ lines.push('');
344
+ }
345
+ // Make the API call
346
+ lines.push(` return this.api.fetch(url, fetchOptions);`);
347
+ }
348
+ return lines.join('\n');
349
+ }
350
+ /**
351
+ * Generate proper import paths with .mjs extensions for TypeScript module resolution
352
+ */
353
+ generateImportPath(relativePath) {
354
+ // For resource classes, we need to reference the compiled .mjs files
355
+ return relativePath.endsWith('.mjs') ? relativePath : `${relativePath}.mjs`;
356
+ }
357
+ /**
358
+ * Generate a resource class (does NOT extend AbstractController, receives controller reference)
359
+ */
360
+ generateResourceClass(className) {
361
+ const operations = this.extractOperations();
362
+ if (operations.length === 0) {
363
+ throw new Error('No operations found in OpenAPI specification');
364
+ }
365
+ const methods = operations
366
+ .map((operation) => {
367
+ const methodName = this.generateMethodName(operation);
368
+ const jsdoc = this.generateJSDoc(operation);
369
+ const signature = this.generateMethodSignature(operation);
370
+ const implementation = this.generateMethodImplementation(operation);
371
+ return ` /**\n${jsdoc}\n */\n async ${methodName}${signature} {\n${implementation}\n }`;
372
+ })
373
+ .join('\n\n');
374
+ return `import {AbstractController} from '@aloma.io/integration-sdk';
375
+
376
+ export default class ${className} {
377
+ private controller: AbstractController;
378
+
379
+ constructor(controller: AbstractController) {
380
+ this.controller = controller;
381
+ }
382
+
383
+ private get api() {
384
+ return this.controller['api'];
385
+ }
386
+
387
+ ${methods}
388
+ }`;
389
+ }
390
+ /**
391
+ * Generate a main controller that composes multiple resources
392
+ */
393
+ generateMainController(resources) {
394
+ // Get base URL from servers if available
395
+ const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'API_BASE_URL';
396
+ const imports = resources
397
+ .map((resource) => `import ${resource.className} from '../resources/${resource.fileName}.mjs';`)
398
+ .join('\n');
399
+ const properties = resources
400
+ .map((resource) => ` ${resource.className.toLowerCase().replace('resource', '')}!: ${resource.className};`)
401
+ .join('\n');
402
+ const initializations = resources
403
+ .map((resource) => ` this.${resource.className.toLowerCase().replace('resource', '')} = new ${resource.className}(this);`)
404
+ .join('\n');
405
+ return `import {AbstractController} from '@aloma.io/integration-sdk';
406
+ ${imports}
407
+
408
+ export default class Controller extends AbstractController {
409
+ ${properties}
410
+
411
+ private api: any;
412
+
413
+ protected async start(): Promise<void> {
414
+ this.api = this.getClient({
415
+ baseUrl: '${baseUrl}',
416
+ });
417
+
418
+ // Initialize each resource - they receive 'this' controller reference
419
+ ${initializations}
420
+ }
421
+ }`;
422
+ }
188
423
  /**
189
424
  * Generate the connector controller code
190
425
  */
@@ -197,13 +432,26 @@ export class OpenAPIToConnector {
197
432
  .map((operation) => {
198
433
  const methodName = this.generateMethodName(operation);
199
434
  const jsdoc = this.generateJSDoc(operation);
200
- return ` /**\n${jsdoc}\n */\n async ${methodName}(args: any) {\n // TODO: Implement ${operation.method} ${operation.path}\n throw new Error('Method not implemented');\n }`;
435
+ const signature = this.generateMethodSignature(operation);
436
+ const implementation = this.generateMethodImplementation(operation);
437
+ return ` /**\n${jsdoc}\n */\n async ${methodName}${signature} {\n${implementation}\n }`;
201
438
  })
202
439
  .join('\n\n');
440
+ // Get base URL from servers if available
441
+ const baseUrl = this.spec.servers && this.spec.servers.length > 0 ? this.spec.servers[0].url : 'API_BASE_URL';
442
+ const startMethod = ` private api: any;
443
+
444
+ protected async start(): Promise<void> {
445
+ this.api = this.getClient({
446
+ baseUrl: '${baseUrl}',
447
+ });
448
+ }`;
203
449
  return `import {AbstractController} from '@aloma.io/integration-sdk';
204
450
 
205
451
  export default class Controller extends AbstractController {
206
452
 
453
+ ${startMethod}
454
+
207
455
  ${methods}
208
456
  }`;
209
457
  }
@@ -0,0 +1,32 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "Test API",
5
+ "version": "1.0.0",
6
+ "description": "API without servers definition"
7
+ },
8
+ "paths": {
9
+ "/users": {
10
+ "get": {
11
+ "summary": "List users",
12
+ "operationId": "listUsers",
13
+ "parameters": [
14
+ {
15
+ "name": "limit",
16
+ "in": "query",
17
+ "schema": {
18
+ "type": "integer"
19
+ },
20
+ "description": "Maximum number of users"
21
+ }
22
+ ],
23
+ "responses": {
24
+ "200": {
25
+ "description": "Success"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+