@etohq/connector-engine 1.5.1-alpha.4

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.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +253 -0
  5. package/dist/engine/clean-connector-engine.d.ts +81 -0
  6. package/dist/engine/clean-connector-engine.d.ts.map +1 -0
  7. package/dist/engine/clean-connector-engine.js +350 -0
  8. package/dist/engine/clean-connector-engine.js.map +1 -0
  9. package/dist/engine/connector-engine-impl.d.ts +73 -0
  10. package/dist/engine/connector-engine-impl.d.ts.map +1 -0
  11. package/dist/engine/connector-engine-impl.js +332 -0
  12. package/dist/engine/connector-engine-impl.js.map +1 -0
  13. package/dist/engine/connector-engine.d.ts +54 -0
  14. package/dist/engine/connector-engine.d.ts.map +1 -0
  15. package/dist/engine/connector-engine.js +694 -0
  16. package/dist/engine/connector-engine.js.map +1 -0
  17. package/dist/engine/index.d.ts +7 -0
  18. package/dist/engine/index.d.ts.map +1 -0
  19. package/dist/engine/index.js +10 -0
  20. package/dist/engine/index.js.map +1 -0
  21. package/dist/engine/routing-engine.d.ts +26 -0
  22. package/dist/engine/routing-engine.d.ts.map +1 -0
  23. package/dist/engine/routing-engine.js +329 -0
  24. package/dist/engine/routing-engine.js.map +1 -0
  25. package/dist/examples/booking-connector-example.d.ts +7 -0
  26. package/dist/examples/booking-connector-example.d.ts.map +1 -0
  27. package/dist/examples/booking-connector-example.js +221 -0
  28. package/dist/examples/booking-connector-example.js.map +1 -0
  29. package/dist/examples/dynamic-methods-example.d.ts +7 -0
  30. package/dist/examples/dynamic-methods-example.d.ts.map +1 -0
  31. package/dist/examples/dynamic-methods-example.js +163 -0
  32. package/dist/examples/dynamic-methods-example.js.map +1 -0
  33. package/dist/index.d.ts +9 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +14 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/types/base-plugin.d.ts +170 -0
  38. package/dist/types/base-plugin.d.ts.map +1 -0
  39. package/dist/types/base-plugin.js +68 -0
  40. package/dist/types/base-plugin.js.map +1 -0
  41. package/dist/types/connector-plugin.d.ts +22 -0
  42. package/dist/types/connector-plugin.d.ts.map +1 -0
  43. package/dist/types/connector-plugin.js +11 -0
  44. package/dist/types/connector-plugin.js.map +1 -0
  45. package/dist/types/engine.d.ts +223 -0
  46. package/dist/types/engine.d.ts.map +1 -0
  47. package/dist/types/engine.js +7 -0
  48. package/dist/types/engine.js.map +1 -0
  49. package/dist/types/index.d.ts +5 -0
  50. package/dist/types/index.d.ts.map +1 -0
  51. package/dist/types/index.js +9 -0
  52. package/dist/types/index.js.map +1 -0
  53. package/dist/types/operation-groups.d.ts +78 -0
  54. package/dist/types/operation-groups.d.ts.map +1 -0
  55. package/dist/types/operation-groups.js +60 -0
  56. package/dist/types/operation-groups.js.map +1 -0
  57. package/dist/types/routing-config.d.ts +116 -0
  58. package/dist/types/routing-config.d.ts.map +1 -0
  59. package/dist/types/routing-config.js +6 -0
  60. package/dist/types/routing-config.js.map +1 -0
  61. package/dist/utils/create-connector-engine.d.ts +31 -0
  62. package/dist/utils/create-connector-engine.d.ts.map +1 -0
  63. package/dist/utils/create-connector-engine.js +30 -0
  64. package/dist/utils/create-connector-engine.js.map +1 -0
  65. package/examples/booking-example.ts +168 -0
  66. package/examples/booking-test.ts +231 -0
  67. package/hyperswitch-example.ts +263 -0
  68. package/jest.config.js +2 -0
  69. package/package.json +54 -0
  70. package/src/engine/clean-connector-engine.ts +726 -0
  71. package/src/engine/index.ts +13 -0
  72. package/src/engine/routing-engine.ts +394 -0
  73. package/src/index.ts +32 -0
  74. package/src/types/connector-plugin.ts +34 -0
  75. package/src/types/index.ts +5 -0
  76. package/src/types/routing-config.ts +196 -0
  77. package/tsconfig.json +3 -0
@@ -0,0 +1,694 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Universal Connector Engine
4
+ * @description Plugin-first connector engine with operation groups and full type safety
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.UniversalConnectorEngine = void 0;
8
+ exports.ConnectorEngine = ConnectorEngine;
9
+ const utils_1 = require("@etohq/framework/utils");
10
+ class PluginRegistryImpl {
11
+ constructor() {
12
+ this.plugins = new Map();
13
+ }
14
+ register(name, plugin) {
15
+ if (!this.validate(plugin)) {
16
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Plugin ${name} failed validation`);
17
+ }
18
+ this.plugins.set(name, plugin);
19
+ }
20
+ get(name) {
21
+ return this.plugins.get(name);
22
+ }
23
+ list() {
24
+ return Array.from(this.plugins.keys());
25
+ }
26
+ validate(plugin) {
27
+ return (typeof plugin.getName === 'function' &&
28
+ typeof plugin.getVersion === 'function' &&
29
+ typeof plugin.getCapabilities === 'function' &&
30
+ typeof plugin.initialize === 'function' &&
31
+ typeof plugin.destroy === 'function' &&
32
+ typeof plugin.healthCheck === 'function');
33
+ }
34
+ getOperations(pluginName) {
35
+ const plugin = this.get(pluginName);
36
+ if (!plugin) {
37
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Plugin ${pluginName} not found`);
38
+ }
39
+ return Object.getOwnPropertyNames(plugin)
40
+ .filter(name => typeof plugin[name] === 'function' &&
41
+ !['getName', 'getVersion', 'getCapabilities', 'initialize', 'destroy', 'healthCheck', 'parseWebhook', 'validateWebhook'].includes(name));
42
+ }
43
+ }
44
+ // ===== PLUGIN-FIRST CONNECTOR ENGINE FACTORY =====
45
+ function ConnectorEngine(plugins, operationGroups) {
46
+ as `execute${Capitalize}${Capitalize}`;
47
+ ;
48
+ }
49
+ [keyof, TPlugins];
50
+ `execute${Capitalize}${Capitalize}`;
51
+ ;
52
+ [(operation_groups_1.ExtractGroupNames)];
53
+ { }
54
+ ;
55
+ class ConnectorEngineImpl {
56
+ constructor() {
57
+ this.plugins = plugins;
58
+ this.operationGroups = operationGroups || {};
59
+ this.pluginRegistry = new PluginRegistryImpl();
60
+ // ✅ Validate plugins against operation groups
61
+ this.validatePluginConfiguration();
62
+ // ✅ Register all plugins
63
+ this.registerPlugins();
64
+ // ✅ Generate dynamic methods
65
+ this.generateDynamicMethods();
66
+ }
67
+ // ✅ Generate dynamic methods: execute<Domain><Operation> and execute<GroupName><Operation>
68
+ generateDynamicMethods() {
69
+ // Generate domain methods: executeCalendarCreateEvent, executeNotificationSendEmail
70
+ this.generateDomainMethods();
71
+ // Generate group methods: executeBookingCreateEvent, executeBookingSendEmail
72
+ this.generateGroupMethods();
73
+ }
74
+ // ✅ Generate execute<Domain><Operation> methods
75
+ generateDomainMethods() {
76
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
77
+ const operations = this.pluginRegistry.getOperations(domain);
78
+ for (const operation of operations) {
79
+ // Pattern: execute<Domain><Operation>
80
+ const methodName = `execute${capitalize(domain)}${capitalize(operation)}`;
81
+ this[methodName] = async (input, sharedContext) => {
82
+ return await this.executeDomainOperation(domain, operation, input, sharedContext);
83
+ };
84
+ }
85
+ }
86
+ }
87
+ // ✅ Generate execute<GroupName><Operation> methods
88
+ generateGroupMethods() {
89
+ if (!this.operationGroups)
90
+ return;
91
+ for (const [groupName, groupConfig] of Object.entries(this.operationGroups)) {
92
+ for (const operation of groupConfig.operations) {
93
+ // Pattern: execute<GroupName><Operation>
94
+ const methodName = `execute${capitalize(groupName)}${capitalize(operation)}`;
95
+ this[methodName] = async (input, sharedContext) => {
96
+ return await this.executeGroupOperation(groupName, operation, input, sharedContext);
97
+ };
98
+ }
99
+ }
100
+ }
101
+ // ✅ Execute single domain operation (like EtoInternalService method)
102
+ async executeDomainOperation(domain, operation, input, sharedContext) {
103
+ const plugin = this.plugins[domain];
104
+ if (!plugin) {
105
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Plugin not found for domain: ${String(domain)}`);
106
+ }
107
+ const pluginMethod = plugin[operation];
108
+ if (!pluginMethod || typeof pluginMethod !== 'function') {
109
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Operation ${operation} not supported by plugin for domain ${String(domain)}`);
110
+ }
111
+ try {
112
+ // ✅ Call plugin method - can return value or workflow step
113
+ const result = await pluginMethod.call(plugin, {}, // config
114
+ input, { container: this.getContainerFromContext(sharedContext) });
115
+ // ✅ Handle both normal methods and workflow steps
116
+ if (this.isStepDefinition(result)) {
117
+ return await result.run(input, { container: this.getContainerFromContext(sharedContext) });
118
+ }
119
+ return result;
120
+ }
121
+ catch (error) {
122
+ throw new utils_1.EtoError(utils_1.EtoError.Types.UNKNOWN_ERROR, `Plugin operation ${String(domain)}.${operation} failed: ${error instanceof Error ? error.message : String(error)}`);
123
+ }
124
+ }
125
+ // ✅ Execute operation group by group ID
126
+ async executeGroupOperation(groupId, operation, input, sharedContext) {
127
+ const groupConfig = this.operationGroups[groupId];
128
+ if (!groupConfig) {
129
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Operation group '${String(groupId)}' not found`);
130
+ }
131
+ if (!groupConfig.operations.includes(operation)) {
132
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Operation '${operation}' not supported by group '${String(groupId)}'`);
133
+ }
134
+ const { behavior } = groupConfig.options;
135
+ const startTime = Date.now();
136
+ switch (behavior) {
137
+ case 'execute_all':
138
+ return await this.executeAllDomains(groupConfig.domains, operation, input, sharedContext, String(groupId), startTime);
139
+ case 'route_by_domain':
140
+ return await this.routeByDomain(groupConfig.domains, operation, input, groupConfig.options, sharedContext, String(groupId), startTime);
141
+ case 'first_success':
142
+ return await this.executeFirstSuccess(groupConfig.domains, operation, input, sharedContext, String(groupId), startTime);
143
+ default:
144
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Unsupported behavior: ${behavior}`);
145
+ }
146
+ }
147
+ // ===== PRIVATE HELPER METHODS =====
148
+ // ✅ Register all plugins in the registry
149
+ registerPlugins() {
150
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
151
+ this.pluginRegistry.register(domain, plugin);
152
+ }
153
+ }
154
+ // ✅ Extract container from ETO shared context
155
+ getContainerFromContext(sharedContext) {
156
+ // In ETO, container is available in shared context
157
+ return sharedContext?.__container__ || {};
158
+ }
159
+ // ✅ Execute all domains in parallel
160
+ async executeAllDomains(domains, operation, input, sharedContext, groupName, startTime) {
161
+ const results = {};
162
+ const errors = {};
163
+ // Execute all domains in parallel
164
+ const domainPromises = domains.map(async (domain) => {
165
+ try {
166
+ const result = await this.executeDomainOperation(domain, operation, input, sharedContext);
167
+ results[domain] = result;
168
+ }
169
+ catch (error) {
170
+ errors[domain] = error instanceof Error ? error : new Error(String(error));
171
+ }
172
+ });
173
+ await Promise.all(domainPromises);
174
+ return {
175
+ groupName,
176
+ executedDomains: domains,
177
+ results,
178
+ metadata: {
179
+ behavior: 'execute_all',
180
+ executionTime: Date.now() - startTime,
181
+ errors: Object.keys(errors).length > 0 ? errors : undefined
182
+ }
183
+ };
184
+ }
185
+ // ✅ Route to specific domain
186
+ async routeByDomain(groupDomains, operation, input, options, sharedContext, groupName, startTime) {
187
+ // ✅ Domain precedence: options.connectorId > first group domain
188
+ const targetDomain = options.connectorId || groupDomains[0];
189
+ // ✅ Validate domain is supported by this group
190
+ if (!groupDomains.includes(targetDomain)) {
191
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Domain '${targetDomain}' not supported by group '${groupName}'. Supported domains: ${groupDomains.join(', ')}`);
192
+ }
193
+ try {
194
+ const result = await this.executeDomainOperation(targetDomain, operation, input, sharedContext);
195
+ return {
196
+ groupName,
197
+ executedDomains: [targetDomain],
198
+ results: { [targetDomain]: result },
199
+ metadata: {
200
+ behavior: 'route_by_domain',
201
+ connectorId: options.connectorId,
202
+ executionTime: Date.now() - startTime,
203
+ domainSource: options.connectorId ? 'options' : 'group_default'
204
+ }
205
+ };
206
+ }
207
+ catch (error) {
208
+ return {
209
+ groupName,
210
+ executedDomains: [targetDomain],
211
+ results: {},
212
+ metadata: {
213
+ behavior: 'route_by_domain',
214
+ connectorId: options.connectorId,
215
+ executionTime: Date.now() - startTime,
216
+ domainSource: options.connectorId ? 'options' : 'group_default',
217
+ errors: { [targetDomain]: error instanceof Error ? error : new Error(String(error)) }
218
+ }
219
+ };
220
+ }
221
+ }
222
+ // ✅ Execute domains until first success
223
+ async executeFirstSuccess(domains, operation, input, sharedContext, groupName, startTime) {
224
+ const errors = {};
225
+ for (const domain of domains) {
226
+ try {
227
+ const result = await this.executeDomainOperation(domain, operation, input, sharedContext);
228
+ return {
229
+ groupName,
230
+ executedDomains: [domain],
231
+ results: { [domain]: result },
232
+ metadata: {
233
+ behavior: 'first_success',
234
+ executionTime: Date.now() - startTime
235
+ }
236
+ };
237
+ }
238
+ catch (error) {
239
+ errors[domain] = error instanceof Error ? error : new Error(String(error));
240
+ continue; // Try next domain
241
+ }
242
+ }
243
+ // All domains failed
244
+ throw new utils_1.EtoError(utils_1.EtoError.Types.UNKNOWN_ERROR, `All domains failed for operation ${operation} in group ${groupName}. Errors: ${JSON.stringify(errors)}`);
245
+ }
246
+ // ===== PRIVATE HELPER METHODS =====
247
+ // ✅ Register all plugins in the registry
248
+ registerPlugins() {
249
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
250
+ this.pluginRegistry.register(domain, plugin);
251
+ }
252
+ }
253
+ // ✅ Extract container from ETO shared context
254
+ getContainerFromContext(sharedContext) {
255
+ // In ETO, container is available in shared context
256
+ return sharedContext?.__container__ || {};
257
+ }
258
+ // ✅ Execute all domains in parallel
259
+ async executeAllDomains(domains, operation, input, sharedContext, groupName, startTime) {
260
+ const results = {};
261
+ const errors = {};
262
+ // Execute all domains in parallel
263
+ const domainPromises = domains.map(async (domain) => {
264
+ try {
265
+ const result = await this.executeDomainOperation(domain, operation, input, sharedContext);
266
+ results[domain] = result;
267
+ }
268
+ catch (error) {
269
+ errors[domain] = error instanceof Error ? error : new Error(String(error));
270
+ }
271
+ });
272
+ await Promise.all(domainPromises);
273
+ return {
274
+ groupName,
275
+ executedDomains: domains,
276
+ results,
277
+ metadata: {
278
+ behavior: 'execute_all',
279
+ executionTime: Date.now() - startTime,
280
+ errors: Object.keys(errors).length > 0 ? errors : undefined
281
+ }
282
+ };
283
+ }
284
+ // ✅ Route to specific domain
285
+ async routeByDomain(groupDomains, operation, input, options, sharedContext, groupName, startTime) {
286
+ // ✅ Domain precedence: options.connectorId > first group domain
287
+ const targetDomain = options.connectorId || groupDomains[0];
288
+ // ✅ Validate domain is supported by this group
289
+ if (!groupDomains.includes(targetDomain)) {
290
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Domain '${targetDomain}' not supported by group '${groupName}'. Supported domains: ${groupDomains.join(', ')}`);
291
+ }
292
+ try {
293
+ const result = await this.executeDomainOperation(targetDomain, operation, input, sharedContext);
294
+ return {
295
+ groupName,
296
+ executedDomains: [targetDomain],
297
+ results: { [targetDomain]: result },
298
+ metadata: {
299
+ behavior: 'route_by_domain',
300
+ connectorId: options.connectorId,
301
+ executionTime: Date.now() - startTime,
302
+ domainSource: options.connectorId ? 'options' : 'group_default'
303
+ }
304
+ };
305
+ }
306
+ catch (error) {
307
+ return {
308
+ groupName,
309
+ executedDomains: [targetDomain],
310
+ results: {},
311
+ metadata: {
312
+ behavior: 'route_by_domain',
313
+ connectorId: options.connectorId,
314
+ executionTime: Date.now() - startTime,
315
+ domainSource: options.connectorId ? 'options' : 'group_default',
316
+ errors: { [targetDomain]: error instanceof Error ? error : new Error(String(error)) }
317
+ }
318
+ };
319
+ }
320
+ }
321
+ // ✅ Execute domains until first success
322
+ async executeFirstSuccess(domains, operation, input, sharedContext, groupName, startTime) {
323
+ const errors = {};
324
+ for (const domain of domains) {
325
+ try {
326
+ const result = await this.executeDomainOperation(domain, operation, input, sharedContext);
327
+ return {
328
+ groupName,
329
+ executedDomains: [domain],
330
+ results: { [domain]: result },
331
+ metadata: {
332
+ behavior: 'first_success',
333
+ executionTime: Date.now() - startTime
334
+ }
335
+ };
336
+ }
337
+ catch (error) {
338
+ errors[domain] = error instanceof Error ? error : new Error(String(error));
339
+ continue; // Try next domain
340
+ }
341
+ }
342
+ // All domains failed
343
+ throw new utils_1.EtoError(utils_1.EtoError.Types.UNKNOWN_ERROR, `All domains failed for operation ${operation} in group ${groupName}. Errors: ${JSON.stringify(errors)}`);
344
+ }
345
+ // ✅ Validate that operation groups reference valid plugins and operations
346
+ validatePluginConfiguration() {
347
+ if (!this.operationGroups)
348
+ return;
349
+ for (const [groupName, groupConfig] of Object.entries(this.operationGroups)) {
350
+ // Validate domains exist in plugins
351
+ for (const domain of groupConfig.domains) {
352
+ if (!this.plugins[domain]) {
353
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Operation group '${groupName}' references unknown domain '${domain}'. Available domains: ${Object.keys(this.plugins).join(', ')}`);
354
+ }
355
+ }
356
+ // Validate operations exist in domain plugins
357
+ for (const domain of groupConfig.domains) {
358
+ const plugin = this.plugins[domain];
359
+ for (const operation of groupConfig.operations) {
360
+ const pluginMethod = plugin[operation];
361
+ if (!pluginMethod || typeof pluginMethod !== 'function') {
362
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Operation group '${groupName}' references unknown operation '${operation}' for domain '${domain}'`);
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ // ✅ Generate ETO-style methods dynamically
369
+ generateEtoStyleMethods() {
370
+ // Generate individual plugin methods (like EtoInternalService)
371
+ this.generatePluginMethods();
372
+ // Generate operation group methods based on group configuration
373
+ this.generateOperationGroupMethods();
374
+ }
375
+ // ✅ Generate individual plugin methods (domain.operation -> operationDomain)
376
+ generatePluginMethods() {
377
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
378
+ const operations = this.pluginRegistry.getOperations(domain);
379
+ for (const operation of operations) {
380
+ // ETO-style method naming: createCalendar, updateCalendar, etc.
381
+ const methodName = `${operation}${capitalize(domain)}`;
382
+ this[methodName] = async (input, sharedContext) => {
383
+ return await this.executeSingleOperation(domain, operation, input, sharedContext);
384
+ };
385
+ }
386
+ }
387
+ }
388
+ // ✅ Generate operation group methods based on group behavior
389
+ generateOperationGroupMethods() {
390
+ if (!this.operationGroups)
391
+ return;
392
+ for (const [groupName, groupConfig] of Object.entries(this.operationGroups)) {
393
+ for (const operation of groupConfig.operations) {
394
+ // ETO-style method naming based on group behavior
395
+ const methodName = this.getGroupMethodName(operation, groupName, groupConfig.options.behavior);
396
+ this[methodName] = async (input, sharedContext) => {
397
+ return await this.executeOperationGroup(groupName, operation, input, groupConfig, sharedContext);
398
+ };
399
+ }
400
+ }
401
+ }
402
+ // ✅ Determine method name based on group behavior
403
+ getGroupMethodName(operation, groupName, behavior) {
404
+ switch (behavior) {
405
+ case 'execute_all':
406
+ // createBookingAll, updateBookingAll
407
+ return `${operation}${capitalize(groupName)}All`;
408
+ case 'route_by_domain':
409
+ // createBooking, updateBooking (routes to appropriate domain)
410
+ return `${operation}${capitalize(groupName)}`;
411
+ case 'first_success':
412
+ // createBookingFirstSuccess, updateBookingFirstSuccess
413
+ return `${operation}${capitalize(groupName)}FirstSuccess`;
414
+ default:
415
+ return `${operation}${capitalize(groupName)}`;
416
+ }
417
+ }
418
+ // ✅ Execute single plugin operation (like EtoInternalService method)
419
+ async executeSingleOperation(domain, operation, input, sharedContext) {
420
+ const plugin = this.plugins[domain];
421
+ if (!plugin) {
422
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Plugin not found for domain: ${domain}`);
423
+ }
424
+ const pluginMethod = plugin[operation];
425
+ if (!pluginMethod || typeof pluginMethod !== 'function') {
426
+ throw new utils_1.EtoError(utils_1.EtoError.Types.NOT_FOUND, `Operation ${operation} not supported by plugin for domain ${domain}`);
427
+ }
428
+ try {
429
+ // ✅ Call plugin method directly (like EtoInternalService)
430
+ const result = await pluginMethod.call(plugin, {}, // config
431
+ input, { container: this.getContainerFromContext(sharedContext) });
432
+ if (this.isStepDefinition(result)) {
433
+ return await result.run(input, { container: this.getContainerFromContext(sharedContext) });
434
+ }
435
+ return result;
436
+ }
437
+ catch (error) {
438
+ throw new utils_1.EtoError(utils_1.EtoError.Types.UNKNOWN_ERROR, `Plugin operation ${domain}.${operation} failed: ${error instanceof Error ? error.message : String(error)}`);
439
+ }
440
+ }
441
+ // ✅ Execute operation group based on behavior
442
+ async executeOperationGroup(groupName, operation, input, groupConfig, sharedContext) {
443
+ const { behavior } = groupConfig.options;
444
+ switch (behavior) {
445
+ case 'execute_all':
446
+ return await this.executeAllDomains(groupConfig.domains, operation, input, sharedContext);
447
+ case 'route_by_domain':
448
+ return await this.routeByDomain(groupConfig.domains, operation, input, groupConfig.options, sharedContext);
449
+ case 'first_success':
450
+ return await this.executeFirstSuccess(groupConfig.domains, operation, input, sharedContext);
451
+ default:
452
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Unsupported behavior: ${behavior}`);
453
+ }
454
+ }
455
+ // ✅ Extract container from ETO shared context
456
+ getContainerFromContext(sharedContext) {
457
+ // In ETO, container is available in shared context
458
+ return sharedContext?.__container__ || {};
459
+ }
460
+ // ✅ Execute all domains in parallel with type safety
461
+ async executeAllDomains(domains, operation, input) {
462
+ const results = {};
463
+ const errors = {};
464
+ const startTime = Date.now();
465
+ // Execute all domains in parallel
466
+ const domainPromises = domains.map(async (domain) => {
467
+ try {
468
+ const result = await this.executeDomainOperation(domain, operation, input);
469
+ results[domain] = result;
470
+ }
471
+ catch (error) {
472
+ errors[domain] = error instanceof Error ? error : new Error(String(error));
473
+ }
474
+ });
475
+ await Promise.all(domainPromises);
476
+ return {
477
+ groupName: '', // Will be set by caller
478
+ executedDomains: domains,
479
+ results,
480
+ metadata: {
481
+ behavior: 'execute_all',
482
+ executionTime: Date.now() - startTime,
483
+ errors: Object.keys(errors).length > 0 ? errors : undefined
484
+ }
485
+ };
486
+ }
487
+ // ✅ Route to specific domain based on input or group configuration
488
+ async routeByDomain(groupDomains, operation, input, options) {
489
+ const startTime = Date.now();
490
+ // ✅ Domain precedence: options.connectorId > first group domain
491
+ const targetDomain = options.connectorId || groupDomains[0];
492
+ // ✅ Validate domain is supported by this group
493
+ if (!groupDomains.includes(targetDomain)) {
494
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Domain '${targetDomain}' not supported by this operation group. ` +
495
+ `Supported domains: ${groupDomains.join(', ')}`);
496
+ }
497
+ try {
498
+ const result = await this.executeDomainOperation(targetDomain, operation, input);
499
+ return {
500
+ groupName: '', // Will be set by caller
501
+ executedDomains: [targetDomain],
502
+ results: { [targetDomain]: result },
503
+ metadata: {
504
+ behavior: 'route_by_domain',
505
+ connectorId: options.connectorId,
506
+ executionTime: Date.now() - startTime,
507
+ domainSource: options.connectorId ? 'options' : 'group_default'
508
+ }
509
+ };
510
+ }
511
+ catch (error) {
512
+ return {
513
+ groupName: '',
514
+ executedDomains: [targetDomain],
515
+ results: {},
516
+ metadata: {
517
+ behavior: 'route_by_domain',
518
+ connectorId: options.connectorId,
519
+ executionTime: Date.now() - startTime,
520
+ domainSource: options.connectorId ? 'options' : 'group_default',
521
+ errors: { [targetDomain]: error instanceof Error ? error : new Error(String(error)) }
522
+ }
523
+ };
524
+ }
525
+ }
526
+ // ✅ Execute domains until first success
527
+ async executeFirstSuccess(domains, operation, input) {
528
+ const startTime = Date.now();
529
+ const errors = {};
530
+ for (const domain of domains) {
531
+ try {
532
+ const result = await this.executeDomainOperation(domain, operation, input);
533
+ return {
534
+ groupName: '',
535
+ executedDomains: [domain],
536
+ results: { [domain]: result },
537
+ metadata: {
538
+ behavior: 'first_success',
539
+ executionTime: Date.now() - startTime
540
+ }
541
+ };
542
+ }
543
+ catch (error) {
544
+ errors[domain] = error instanceof Error ? error : new Error(String(error));
545
+ continue; // Try next domain
546
+ }
547
+ }
548
+ // All domains failed
549
+ throw new utils_1.EtoError(utils_1.EtoError.Types.UNKNOWN_ERROR, `All domains failed for operation ${operation}. Errors: ${JSON.stringify(errors)}`);
550
+ }
551
+ // ===== TYPE GUARDS =====
552
+ isStepDefinition(value) {
553
+ return value !== null &&
554
+ typeof value === 'object' &&
555
+ 'run' in value &&
556
+ typeof value.run === 'function' &&
557
+ 'stepId' in value &&
558
+ typeof value.stepId === 'string';
559
+ }
560
+ // ===== HEALTH MONITORING =====
561
+ async getSystemHealth() {
562
+ const pluginHealths = {};
563
+ let healthyCount = 0;
564
+ let totalCount = 0;
565
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
566
+ totalCount++;
567
+ try {
568
+ pluginHealths[domain] = await plugin.healthCheck();
569
+ if (pluginHealths[domain].status === 'healthy') {
570
+ healthyCount++;
571
+ }
572
+ }
573
+ catch (error) {
574
+ pluginHealths[domain] = {
575
+ status: 'unhealthy',
576
+ message: error instanceof Error ? error.message : 'Unknown error',
577
+ lastChecked: new Date()
578
+ };
579
+ }
580
+ }
581
+ const healthRatio = totalCount > 0 ? healthyCount / totalCount : 1;
582
+ const overall = healthRatio === 1 ? 'healthy' : healthRatio >= 0.5 ? 'degraded' : 'unhealthy';
583
+ return { overall, plugins: pluginHealths };
584
+ }
585
+ // ===== PUBLIC API =====
586
+ getAvailableDomains() {
587
+ return Object.keys(this.plugins);
588
+ }
589
+ getAvailableOperations(domain) {
590
+ return this.pluginRegistry.getOperations(domain);
591
+ }
592
+ getOperationGroups() {
593
+ return this.operationGroups;
594
+ }
595
+ // ✅ Validate that operation groups reference valid plugins and operations
596
+ validatePluginConfiguration() {
597
+ if (!this.operationGroups)
598
+ return;
599
+ for (const [groupName, groupConfig] of Object.entries(this.operationGroups)) {
600
+ // Validate domains exist in plugins
601
+ for (const domain of groupConfig.domains) {
602
+ if (!this.plugins[domain]) {
603
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Operation group '${groupName}' references unknown domain '${domain}'. Available domains: ${Object.keys(this.plugins).join(', ')}`);
604
+ }
605
+ }
606
+ // Validate operations exist in domain plugins
607
+ for (const domain of groupConfig.domains) {
608
+ const plugin = this.plugins[domain];
609
+ for (const operation of groupConfig.operations) {
610
+ const pluginMethod = plugin[operation];
611
+ if (!pluginMethod || typeof pluginMethod !== 'function') {
612
+ throw new utils_1.EtoError(utils_1.EtoError.Types.INVALID_DATA, `Operation group '${groupName}' references unknown operation '${operation}' for domain '${domain}'`);
613
+ }
614
+ }
615
+ }
616
+ }
617
+ }
618
+ // ===== TYPE GUARDS =====
619
+ isStepDefinition(value) {
620
+ return value !== null &&
621
+ typeof value === 'object' &&
622
+ 'run' in value &&
623
+ typeof value.run === 'function' &&
624
+ 'stepId' in value &&
625
+ typeof value.stepId === 'string';
626
+ }
627
+ // ===== HEALTH MONITORING =====
628
+ async getSystemHealth() {
629
+ const pluginHealths = {};
630
+ let healthyCount = 0;
631
+ let totalCount = 0;
632
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
633
+ totalCount++;
634
+ try {
635
+ pluginHealths[domain] = await plugin.healthCheck();
636
+ if (pluginHealths[domain].status === 'healthy') {
637
+ healthyCount++;
638
+ }
639
+ }
640
+ catch (error) {
641
+ pluginHealths[domain] = {
642
+ status: 'unhealthy',
643
+ message: error instanceof Error ? error.message : 'Unknown error',
644
+ lastChecked: new Date()
645
+ };
646
+ }
647
+ }
648
+ const healthRatio = totalCount > 0 ? healthyCount / totalCount : 1;
649
+ const overall = healthRatio === 1 ? 'healthy' : healthRatio >= 0.5 ? 'degraded' : 'unhealthy';
650
+ return { overall, plugins: pluginHealths };
651
+ }
652
+ // ===== PUBLIC API =====
653
+ getAvailableDomains() {
654
+ return Object.keys(this.plugins);
655
+ }
656
+ getAvailableOperations(domain) {
657
+ return this.pluginRegistry.getOperations(domain);
658
+ }
659
+ getOperationGroups() {
660
+ return this.operationGroups;
661
+ }
662
+ getPluginRegistry() {
663
+ return this.pluginRegistry;
664
+ }
665
+ // ✅ Get list of generated dynamic methods
666
+ getGeneratedMethods() {
667
+ const domainMethods = [];
668
+ const groupMethods = [];
669
+ // Domain methods
670
+ for (const [domain, plugin] of Object.entries(this.plugins)) {
671
+ const operations = this.pluginRegistry.getOperations(domain);
672
+ for (const operation of operations) {
673
+ domainMethods.push(`execute${capitalize(domain)}${capitalize(operation)}`);
674
+ }
675
+ }
676
+ // Group methods
677
+ if (this.operationGroups) {
678
+ for (const [groupName, groupConfig] of Object.entries(this.operationGroups)) {
679
+ for (const operation of groupConfig.operations) {
680
+ groupMethods.push(`execute${capitalize(groupName)}${capitalize(operation)}`);
681
+ }
682
+ }
683
+ }
684
+ return { domainMethods, groupMethods };
685
+ }
686
+ }
687
+ exports.UniversalConnectorEngine = ConnectorEngineImpl;
688
+ return ConnectorEngineImpl;
689
+ return ConnectorEngineImpl;
690
+ // ===== UTILITY FUNCTIONS =====
691
+ function capitalize(str) {
692
+ return str.charAt(0).toUpperCase() + str.slice(1);
693
+ }
694
+ //# sourceMappingURL=connector-engine.js.map