@aloma.io/integration-sdk 3.8.52 → 3.8.53

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.
@@ -0,0 +1,217 @@
1
+ # Multi-Resource Connector Guide
2
+
3
+ This guide explains how to create and manage multi-resource connectors using the Aloma Integration SDK.
4
+
5
+ ## Overview
6
+
7
+ Multi-resource connectors allow you to organize large APIs into logical resource groups (e.g., `companies`, `contacts`, `deals`) while maintaining a clean, modular architecture. Each resource is generated from its own OpenAPI specification and composed in a main controller.
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ src/
13
+ ├── controller/
14
+ │ └── index.mts # Main controller (extends AbstractController)
15
+ └── resources/
16
+ ├── companies.mts # CompaniesResource (plain class)
17
+ ├── contacts.mts # ContactsResource (plain class)
18
+ └── lists.mts # ListsResource (plain class)
19
+ ```
20
+
21
+ ### Main Controller
22
+ - Extends `AbstractController`
23
+ - Has `private api: any` and `protected async start()`
24
+ - Composes all resources
25
+ - Initializes API client once
26
+
27
+ ### Resource Classes
28
+ - Plain TypeScript classes (no inheritance)
29
+ - Receive controller reference in constructor
30
+ - Access `api` via getter: `this.controller['api']`
31
+ - Focus on their specific domain
32
+
33
+ ## Creating Multi-Resource Connectors
34
+
35
+ ### 1. Create from Multiple OpenAPI Specs
36
+
37
+ ```bash
38
+ npx @aloma.io/integration-sdk@latest create-multi-resource "MyConnector" \
39
+ --connector-id "my-connector-123" \
40
+ --resources "CompaniesResource:companies.json,ContactsResource:contacts.json,ListsResource:lists.json" \
41
+ --base-url "https://api.example.com"
42
+ ```
43
+
44
+ **Parameters:**
45
+ - `--resources`: Comma-separated list of `ClassName:specFile` pairs
46
+ - `--base-url`: API base URL (optional, extracted from first spec if not provided)
47
+ - `--no-build`: Skip dependency installation and building
48
+
49
+ ### 2. Resource Specification Format
50
+
51
+ The `--resources` parameter accepts a comma-separated list in this format:
52
+ ```
53
+ "ClassName1:specFile1,ClassName2:specFile2,ClassName3:specFile3"
54
+ ```
55
+
56
+ **Examples:**
57
+ ```bash
58
+ # HubSpot connector
59
+ --resources "CompaniesResource:hubspot-companies.json,ContactsResource:hubspot-contacts.json,ListsResource:hubspot-lists.json"
60
+
61
+ # Salesforce connector
62
+ --resources "AccountsResource:sf-accounts.json,ContactsResource:sf-contacts.json,OpportunitiesResource:sf-opportunities.json"
63
+
64
+ # Stripe connector
65
+ --resources "CustomersResource:stripe-customers.json,PaymentsResource:stripe-payments.json,SubscriptionsResource:stripe-subscriptions.json"
66
+ ```
67
+
68
+ ## Adding Resources to Existing Projects
69
+
70
+ ### 1. Add New Resource
71
+
72
+ ```bash
73
+ npx @aloma.io/integration-sdk@latest add-resource ./existing-project \
74
+ --className "DealsResource" \
75
+ --spec "deals.json"
76
+ ```
77
+
78
+ This will:
79
+ - Generate the new resource class
80
+ - Save it to `src/resources/deals.mts`
81
+ - Provide instructions for updating the main controller
82
+
83
+ ### 2. Update Main Controller Manually
84
+
85
+ After adding a resource, you need to update the main controller:
86
+
87
+ ```typescript
88
+ // 1. Add import
89
+ import DealsResource from '../resources/deals.mjs';
90
+
91
+ // 2. Add property
92
+ deals!: DealsResource;
93
+
94
+ // 3. Add initialization in start()
95
+ this.deals = new DealsResource(this);
96
+ ```
97
+
98
+ ## Usage Examples
99
+
100
+ ### Generated API Calls
101
+
102
+ ```typescript
103
+ // Companies resource
104
+ await controller.companies.create({
105
+ body: { properties: { name: 'Acme Corp', domain: 'acme.com' } }
106
+ });
107
+
108
+ await controller.companies.getPage({
109
+ limit: 10,
110
+ properties: ['name', 'domain', 'industry']
111
+ });
112
+
113
+ await controller.companies.getById('12345', {
114
+ properties: ['name', 'email']
115
+ });
116
+
117
+ // Contacts resource
118
+ await controller.contacts.create({
119
+ body: { properties: { firstname: 'John', lastname: 'Doe', email: 'john@example.com' } }
120
+ });
121
+
122
+ await controller.contacts.getPage({
123
+ limit: 20,
124
+ properties: ['firstname', 'lastname', 'email']
125
+ });
126
+
127
+ // Lists resource
128
+ await controller.lists.getAll({ limit: 50 });
129
+
130
+ await controller.lists.create({
131
+ body: { name: 'My Contact List', processingType: 'MANUAL' }
132
+ });
133
+ ```
134
+
135
+ ### With Custom Headers
136
+
137
+ ```typescript
138
+ await controller.companies.getPage({
139
+ limit: 10,
140
+ headers: {
141
+ 'X-Request-ID': 'unique-id-123',
142
+ 'X-Custom-Header': 'value'
143
+ }
144
+ });
145
+ ```
146
+
147
+ ## Best Practices
148
+
149
+ ### 1. Resource Naming
150
+ - Use descriptive class names: `CompaniesResource`, `ContactsResource`
151
+ - File names are auto-generated: `companies.mts`, `contacts.mts`
152
+ - Property names in controller: `companies`, `contacts`
153
+
154
+ ### 2. OpenAPI Specifications
155
+ - Each resource should have its own focused OpenAPI spec
156
+ - Ensure all specs use the same base URL or provide `--base-url`
157
+ - Use consistent parameter naming across specs
158
+
159
+ ### 3. Error Handling
160
+ - All generated methods return promises
161
+ - API errors are handled by the underlying `api.fetch()` method
162
+ - Add custom error handling in your connector logic as needed
163
+
164
+ ### 4. Type Safety
165
+ - Generated code uses `any` types for flexibility
166
+ - Consider adding TypeScript interfaces for better type safety
167
+ - Use JSDoc comments for parameter documentation
168
+
169
+ ## Example: Complete HubSpot Connector
170
+
171
+ ```bash
172
+ # Create the connector
173
+ npx @aloma.io/integration-sdk@latest create-multi-resource "HubSpot-v2" \
174
+ --connector-id "hubspot-123" \
175
+ --resources "CompaniesResource:examples/hubspot-companies.json,ContactsResource:examples/hubspot-contacts.json,ListsResource:examples/hubspot-lists.json" \
176
+ --base-url "https://api.hubapi.com"
177
+
178
+ # Install dependencies
179
+ cd HubSpot-v2
180
+ yarn --ignore-engines
181
+
182
+ # Build
183
+ yarn build
184
+
185
+ # Start
186
+ yarn start
187
+ ```
188
+
189
+ ## Troubleshooting
190
+
191
+ ### Import Path Errors
192
+ If you see TypeScript errors about missing file extensions:
193
+ - Ensure you're using `.mjs` extensions in imports
194
+ - The generator handles this automatically in new projects
195
+
196
+ ### Resource Not Found
197
+ If a resource property is undefined:
198
+ - Check that the resource is properly initialized in `start()`
199
+ - Verify the import path in the main controller
200
+ - Ensure the resource file was generated correctly
201
+
202
+ ### Build Errors
203
+ If the project fails to build:
204
+ - Try using `--no-build` flag during creation
205
+ - Install dependencies manually: `yarn --ignore-engines`
206
+ - Check for TypeScript errors in generated files
207
+
208
+ ## Migration from Single-Resource
209
+
210
+ To convert an existing single-resource connector to multi-resource:
211
+
212
+ 1. Extract the relevant endpoints into separate OpenAPI specs
213
+ 2. Create new resource classes using `from-openapi --resource`
214
+ 3. Update the main controller to compose the resources
215
+ 4. Remove the old single controller methods
216
+
217
+ This approach maintains backward compatibility while providing better organization for large APIs.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Creating a new Connector
4
4
 
5
- With the aloma integration SDK cli you can create a new connector in two ways:
5
+ With the aloma integration SDK cli you can create a new connector in three ways:
6
6
 
7
7
  ### 1. Create from scratch
8
8
  ```bash
@@ -16,4 +16,57 @@ npx @aloma.io/integration-sdk@latest from-openapi connectorName --connector-id 1
16
16
 
17
17
  This will automatically generate a complete connector project with methods for all OpenAPI endpoints, ready for implementation.
18
18
 
19
- For more details, see [OPENAPI_TO_CONNECTOR.md](./OPENAPI_TO_CONNECTOR.md).
19
+ ### 3. Generate with Resource Architecture (Recommended for large APIs)
20
+ ```bash
21
+ npx @aloma.io/integration-sdk@latest from-openapi connectorName \
22
+ --connector-id 1234 \
23
+ --spec api.yaml \
24
+ --out src/resources/myresource.mts \
25
+ --resource MyResource \
26
+ --no-build
27
+ ```
28
+
29
+ ### 4. Create Multi-Resource Connector (Recommended for complex APIs)
30
+ ```bash
31
+ npx @aloma.io/integration-sdk@latest create-multi-resource "HubSpot-v2" \
32
+ --connector-id "hubspot-123" \
33
+ --resources "CompaniesResource:examples/hubspot-companies.json,ContactsResource:examples/hubspot-contacts.json,ListsResource:examples/hubspot-lists.json" \
34
+ --base-url "https://api.hubapi.com"
35
+ ```
36
+
37
+ This creates a complete multi-resource connector with:
38
+ - Individual resource classes for each OpenAPI spec
39
+ - Main controller that composes all resources
40
+ - Proper TypeScript imports and architecture
41
+
42
+ ```typescript
43
+ // Usage
44
+ await controller.companies.create({ body: { properties: { name: 'Acme' } } });
45
+ await controller.contacts.getPage({ limit: 10 });
46
+ await controller.lists.getAll({ limit: 50 });
47
+ ```
48
+
49
+ ### 5. Add Resource to Existing Project
50
+ ```bash
51
+ npx @aloma.io/integration-sdk@latest add-resource ./existing-project \
52
+ --className "DealsResource" \
53
+ --spec "deals.json"
54
+ ```
55
+
56
+ This adds a new resource to an existing multi-resource connector.
57
+
58
+ ## 📚 Documentation
59
+
60
+ - **[OPENAPI_TO_CONNECTOR.md](./OPENAPI_TO_CONNECTOR.md)** - OpenAPI generator details
61
+ - **[MULTI_RESOURCE_GUIDE.md](./MULTI_RESOURCE_GUIDE.md)** - Complete guide for multi-resource connectors
62
+
63
+ ## 🚀 Quick Examples
64
+
65
+ ### HubSpot Multi-Resource Connector
66
+ ```bash
67
+ # Create complete HubSpot connector with 3 resources
68
+ npx @aloma.io/integration-sdk@latest create-multi-resource "HubSpot-v2" \
69
+ --connector-id "hubspot-123" \
70
+ --resources "CompaniesResource:examples/hubspot-companies.json,ContactsResource:examples/hubspot-contacts.json,ListsResource:examples/hubspot-lists.json"
71
+
72
+ ```
package/build/cli.mjs CHANGED
@@ -109,6 +109,9 @@ program
109
109
  .requiredOption('--connector-id <id>', 'id of the connector')
110
110
  .requiredOption('--spec <file>', 'OpenAPI specification file (JSON or YAML)')
111
111
  .option('--out <file>', 'output file path for the controller', 'src/controller/index.mts')
112
+ .option('--resource <className>', 'Generate as a resource class with the specified class name (e.g., CompaniesResource)')
113
+ .option('--multi-resource', 'Generate multiple resource files + main controller (requires multiple --spec files)')
114
+ .option('--no-build', 'Skip installing dependencies and building the project')
112
115
  .action(async (name, options) => {
113
116
  name = name.replace(/[\/\.]/gi, '');
114
117
  if (!name)
@@ -123,30 +126,48 @@ program
123
126
  console.log('Creating connector project...');
124
127
  extract({ ...options, target, name });
125
128
  // Generate the controller from OpenAPI spec
126
- console.log('Generating controller from OpenAPI specification...');
127
129
  const generator = new OpenAPIToConnector(spec, name);
128
- const controllerCode = generator.generateController();
130
+ let controllerCode;
131
+ if (options.resource) {
132
+ console.log(`Generating resource class '${options.resource}' from OpenAPI specification...`);
133
+ controllerCode = generator.generateResourceClass(options.resource);
134
+ }
135
+ else {
136
+ console.log('Generating controller from OpenAPI specification...');
137
+ controllerCode = generator.generateController();
138
+ }
129
139
  // Write the generated controller
130
140
  const controllerPath = `${target}/${options.out}`;
131
141
  fs.mkdirSync(path.dirname(controllerPath), { recursive: true });
132
142
  fs.writeFileSync(controllerPath, controllerCode);
133
143
  console.log('Generating keys...');
134
144
  await generateKeys({ target });
135
- console.log('Installing dependencies...');
136
- await exec(`cd ${target}; yarn --ignore-engines`);
137
- console.log('Building...');
138
- await exec(`cd ${target}; yarn build`);
145
+ if (options.build !== false) {
146
+ console.log('Installing dependencies...');
147
+ await exec(`cd ${target}; yarn --ignore-engines`);
148
+ console.log('Building...');
149
+ await exec(`cd ${target}; yarn build`);
150
+ }
151
+ const nextSteps = options.build !== false
152
+ ? `Next steps:
153
+ 1.) Add the connector to a workspace
154
+ 2.) Edit ./${name}/.env and insert the registration token
155
+ 3.) Implement the actual API calls in each method in ${options.out}
156
+ 4.) Start the connector with cd ./${name}/; yarn start`
157
+ : `Next steps:
158
+ 1.) Install dependencies: cd ./${name}/ && yarn --ignore-engines
159
+ 2.) Implement the actual API calls in each method in ${options.out}
160
+ 3.) Build the project: yarn build
161
+ 4.) Add the connector to a workspace
162
+ 5.) Edit ./${name}/.env and insert the registration token
163
+ 6.) Start the connector: yarn start`;
139
164
  console.log(`
140
165
  ✅ Success! Generated connector from OpenAPI specification
141
166
  📝 Connector name: ${name}
142
167
  📊 Found ${generator.getOperationsCount()} operations
143
168
  📄 Controller generated: ${options.out}
144
169
 
145
- Next steps:
146
- 1.) Add the connector to a workspace
147
- 2.) Edit ./${name}/.env and insert the registration token
148
- 3.) Implement the actual API calls in each method in ${options.out}
149
- 4.) Start the connector with cd ./${name}/; yarn start`);
170
+ ${nextSteps}`);
150
171
  }
151
172
  catch (error) {
152
173
  console.error('❌ Error:', error instanceof Error ? error.message : 'Unknown error');
@@ -173,4 +194,134 @@ class Extractor {
173
194
  }), { encoding: 'utf-8' });
174
195
  }
175
196
  }
197
+ // Multi-resource connector creation
198
+ program
199
+ .command('create-multi-resource')
200
+ .description('Create a multi-resource connector project with multiple OpenAPI specifications')
201
+ .argument('<name>', 'name of the connector project')
202
+ .requiredOption('--connector-id <id>', 'id of the connector')
203
+ .requiredOption('--resources <specs>', 'comma-separated list of "className:specFile" pairs (e.g., "CompaniesResource:companies.json,ContactsResource:contacts.json")')
204
+ .option('--base-url <url>', 'base URL for the API (if not specified, will be extracted from first OpenAPI spec)')
205
+ .option('--no-build', 'Skip installing dependencies and building the project')
206
+ .action(async (name, options) => {
207
+ name = name.replace(/[\/\.]/gi, '');
208
+ if (!name)
209
+ throw new Error('name is empty');
210
+ const target = `${process.cwd()}/${name}`;
211
+ try {
212
+ // Parse resources specification
213
+ const resourceSpecs = options.resources.split(',').map((spec) => {
214
+ const [className, specFile] = spec.split(':');
215
+ if (!className || !specFile) {
216
+ throw new Error(`Invalid resource specification: ${spec}. Expected format: "ClassName:specFile"`);
217
+ }
218
+ return { className: className.trim(), specFile: specFile.trim() };
219
+ });
220
+ console.log(`Creating multi-resource connector '${name}' with ${resourceSpecs.length} resources...`);
221
+ // Create the connector project structure
222
+ fs.mkdirSync(target);
223
+ extract({ ...options, target, name });
224
+ // Generate each resource
225
+ const resources = [];
226
+ let baseUrl = options.baseUrl;
227
+ for (const { className, specFile } of resourceSpecs) {
228
+ console.log(`Generating ${className} from ${specFile}...`);
229
+ // Read and parse the OpenAPI spec
230
+ const specContent = fs.readFileSync(specFile, 'utf-8');
231
+ const spec = OpenAPIToConnector.parseSpec(specContent);
232
+ // Extract base URL from first spec if not provided
233
+ if (!baseUrl && spec.servers && spec.servers.length > 0) {
234
+ baseUrl = spec.servers[0].url;
235
+ }
236
+ // Generate the resource class
237
+ const generator = new OpenAPIToConnector(spec, name);
238
+ const resourceCode = generator.generateResourceClass(className);
239
+ // Write the resource file
240
+ const fileName = className.toLowerCase().replace('resource', '');
241
+ const resourcePath = `${target}/src/resources/${fileName}.mts`;
242
+ fs.mkdirSync(path.dirname(resourcePath), { recursive: true });
243
+ fs.writeFileSync(resourcePath, resourceCode);
244
+ resources.push({ className, fileName });
245
+ }
246
+ // Generate the main controller
247
+ console.log('Generating main controller...');
248
+ const firstSpec = OpenAPIToConnector.parseSpec(fs.readFileSync(resourceSpecs[0].specFile, 'utf-8'));
249
+ const mainGenerator = new OpenAPIToConnector(firstSpec, name);
250
+ const mainControllerCode = mainGenerator.generateMainController(resources);
251
+ // Write the main controller
252
+ const controllerPath = `${target}/src/controller/index.mts`;
253
+ fs.writeFileSync(controllerPath, mainControllerCode);
254
+ console.log('Generating keys...');
255
+ await generateKeys({ target });
256
+ if (options.build !== false) {
257
+ console.log('Installing dependencies...');
258
+ await exec(`cd "${target}"; yarn --ignore-engines`);
259
+ console.log('Building...');
260
+ await exec(`cd "${target}"; yarn build`);
261
+ }
262
+ const nextSteps = options.build !== false
263
+ ? `Next steps:
264
+ 1.) Add the connector to a workspace
265
+ 2.) Edit ./${name}/.env and insert the registration token
266
+ 3.) Start the connector with cd ./${name}/; yarn start`
267
+ : `Next steps:
268
+ 1.) Install dependencies: cd ./${name}/ && yarn --ignore-engines
269
+ 2.) Build the project: yarn build
270
+ 3.) Add the connector to a workspace
271
+ 4.) Edit ./${name}/.env and insert the registration token
272
+ 5.) Start the connector with yarn start`;
273
+ console.log(`\n✅ Multi-resource connector created successfully!
274
+
275
+ Generated resources:
276
+ ${resources.map((r) => `- ${r.className} (${r.fileName}.mts)`).join('\n')}
277
+
278
+ Main controller: src/controller/index.mts
279
+ ${nextSteps}`);
280
+ }
281
+ catch (error) {
282
+ console.error('Error creating multi-resource connector:', error.message);
283
+ process.exit(1);
284
+ }
285
+ });
286
+ // Add resource to existing project
287
+ program
288
+ .command('add-resource')
289
+ .description('Add a new resource to an existing multi-resource connector')
290
+ .argument('<projectPath>', 'path to the existing connector project')
291
+ .requiredOption('--className <name>', 'class name for the resource (e.g., DealsResource)')
292
+ .requiredOption('--spec <file>', 'OpenAPI specification file for the new resource')
293
+ .option('--no-build', 'Skip building the project after adding the resource')
294
+ .action(async (projectPath, options) => {
295
+ const target = path.resolve(projectPath);
296
+ if (!fs.existsSync(target)) {
297
+ throw new Error(`Project path does not exist: ${target}`);
298
+ }
299
+ try {
300
+ console.log(`Adding ${options.className} resource to existing project...`);
301
+ // Read and parse the OpenAPI spec
302
+ const specContent = fs.readFileSync(options.spec, 'utf-8');
303
+ const spec = OpenAPIToConnector.parseSpec(specContent);
304
+ // Generate the resource class
305
+ const generator = new OpenAPIToConnector(spec, 'Resource');
306
+ const resourceCode = generator.generateResourceClass(options.className);
307
+ // Write the resource file
308
+ const fileName = options.className.toLowerCase().replace('resource', '');
309
+ const resourcePath = `${target}/src/resources/${fileName}.mts`;
310
+ fs.mkdirSync(path.dirname(resourcePath), { recursive: true });
311
+ fs.writeFileSync(resourcePath, resourceCode);
312
+ console.log(`✅ Resource ${options.className} added successfully at ${resourcePath}`);
313
+ console.log('\n⚠️ You need to manually update the main controller to include this new resource:');
314
+ console.log(`1.) Add import: import ${options.className} from '../resources/${fileName}.mjs';`);
315
+ console.log(`2.) Add property: ${fileName}!: ${options.className};`);
316
+ console.log(`3.) Add initialization in start(): this.${fileName} = new ${options.className}(this);`);
317
+ if (options.build !== false) {
318
+ console.log('\nBuilding project...');
319
+ await exec(`cd "${target}"; yarn build`);
320
+ }
321
+ }
322
+ catch (error) {
323
+ console.error('Error adding resource:', error.message);
324
+ process.exit(1);
325
+ }
326
+ });
176
327
  program.parse();
@@ -28,6 +28,29 @@ export declare class OpenAPIToConnector {
28
28
  * Get the number of operations in the OpenAPI spec
29
29
  */
30
30
  getOperationsCount(): number;
31
+ /**
32
+ * Generate method signature with options object
33
+ */
34
+ private generateMethodSignature;
35
+ /**
36
+ * Generate method implementation code
37
+ */
38
+ private generateMethodImplementation;
39
+ /**
40
+ * Generate proper import paths with .mjs extensions for TypeScript module resolution
41
+ */
42
+ private generateImportPath;
43
+ /**
44
+ * Generate a resource class (does NOT extend AbstractController, receives controller reference)
45
+ */
46
+ generateResourceClass(className: string): string;
47
+ /**
48
+ * Generate a main controller that composes multiple resources
49
+ */
50
+ generateMainController(resources: Array<{
51
+ className: string;
52
+ fileName: string;
53
+ }>): string;
31
54
  /**
32
55
  * Generate the connector controller code
33
56
  */