@friggframework/core 2.0.0--canary.396.0dd37aa.0 → 2.0.0--canary.397.216d54b.0

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.
@@ -2,7 +2,6 @@ const express = require('express');
2
2
  const { get } = require('../assertions');
3
3
  const Boom = require('@hapi/boom');
4
4
  const catchAsyncError = require('express-async-handler');
5
- const { debug } = require('../logs');
6
5
  const { IntegrationRepository } = require('./integration-repository');
7
6
  const { DeleteIntegrationForUser } = require('./use-cases/delete-integration-for-user');
8
7
  const { GetIntegrationsForUser } = require('./use-cases/get-integrations-for-user');
@@ -12,54 +11,42 @@ const { CreateIntegration } = require('./use-cases/create-integration');
12
11
  const { ModuleService } = require('../module-plugin/module-service');
13
12
  const { ModuleRepository } = require('../module-plugin/module-repository');
14
13
  const { GetEntitiesForUser } = require('../module-plugin/use-cases/get-entities-for-user');
15
- const {
16
- loadAppDefinition,
17
- } = require('../handlers/app-definition-loader');
18
- const { GetIntegration } = require('./use-cases/get-integration');
14
+ const { loadAppDefinition } = require('../handlers/app-definition-loader');
15
+ const { GetIntegrationInstance } = require('./use-cases/get-integration-instance');
16
+ const { UpdateIntegration } = require('./use-cases/update-integration');
17
+ const { getModulesDefinitionFromIntegrationClasses } = require('./utils/map-integration-dto');
18
+ const { GetModuleInstanceFromType } = require('../module-plugin/use-cases/get-module-instance-from-type');
19
+ const { GetEntityOptionsByType } = require('../module-plugin/use-cases/get-entity-options-by-type');
20
+ const { TestModuleAuth } = require('../module-plugin/use-cases/test-module-auth');
21
+ const { GetModule } = require('../module-plugin/use-cases/get-module');
22
+ const { GetEntityOptionsById } = require('../module-plugin/use-cases/get-entity-options-by-id');
23
+ const { RefreshEntityOptions } = require('../module-plugin/use-cases/refresh-entity-options');
19
24
 
20
- // todo: dont send moduleFactory and integrationFactory as a factory object, instead send them as separate params.
21
- // todo: this could be a use case class
22
25
  /**
23
26
  * Creates an Express router with integration and entity routes configured
24
27
  * @param {Object} params - Configuration parameters for the router
25
28
  * @param {express.Router} [params.router] - Optional Express router instance, creates new one if not provided
26
- * @param {Object} params.factory - Factory object containing moduleFactory and integrationFactory
27
- * @param {Object} params.factory.moduleFactory - Factory for creating and managing API modules
28
- * @param {Object} params.factory.integrationFactory - Factory for creating and managing integrations
29
29
  * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} params.getUserFromBearerToken - Use case for retrieving a user from a bearer token
30
30
  * @returns {express.Router} Configured Express router with integration and entity routes
31
31
  */
32
32
  function createIntegrationRouter(params) {
33
- const { integrations } = loadAppDefinition();
33
+ const { integrations: integrationClasses } = loadAppDefinition();
34
34
  const moduleRepository = new ModuleRepository();
35
35
  const integrationRepository = new IntegrationRepository();
36
36
  const credentialRepository = new CredentialRepository();
37
37
 
38
- // todo: move this into a utils file
39
- const getModules = () => {
40
- return [
41
- ...new Set(
42
- integrations
43
- .map((integration) =>
44
- Object.values(integration.Definition.modules).map(
45
- (module) => module.definition
46
- )
47
- )
48
- .flat()
49
- ),
50
- ];
51
- };
52
38
  const moduleService = new ModuleService({
53
39
  moduleRepository,
54
- moduleDefinitions: getModules(),
40
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
55
41
  });
56
42
  const deleteIntegrationForUser = new DeleteIntegrationForUser({
57
43
  integrationRepository,
44
+ integrationClasses,
58
45
  });
59
46
 
60
47
  const getIntegrationsForUser = new GetIntegrationsForUser({
61
48
  integrationRepository,
62
- integrationClasses: integrations,
49
+ integrationClasses,
63
50
  moduleService,
64
51
  moduleRepository,
65
52
  });
@@ -68,35 +55,74 @@ function createIntegrationRouter(params) {
68
55
  });
69
56
  const createIntegration = new CreateIntegration({
70
57
  integrationRepository,
71
- integrationClasses: integrations,
58
+ integrationClasses,
72
59
  moduleService,
73
60
  });
74
61
 
75
- const getEntitiesForUserUseCase = new GetEntitiesForUser({
62
+ const getEntitiesForUser = new GetEntitiesForUser({
76
63
  moduleRepository,
77
- moduleDefinitions: getModules(),
64
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
78
65
  });
79
66
 
80
- const getIntegration = new GetIntegration({
67
+ const getIntegrationInstance = new GetIntegrationInstance({
81
68
  integrationRepository,
82
- integrationClasses: integrations,
69
+ integrationClasses,
83
70
  moduleService,
84
71
  });
85
72
 
73
+ const updateIntegration = new UpdateIntegration({
74
+ integrationRepository,
75
+ integrationClasses,
76
+ moduleService,
77
+ });
78
+
79
+ const getModuleInstanceFromType = new GetModuleInstanceFromType({
80
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
81
+ });
82
+
83
+ const getEntityOptionsByType = new GetEntityOptionsByType({
84
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
85
+ });
86
+
87
+ const testModuleAuth = new TestModuleAuth({
88
+ moduleRepository,
89
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
90
+ });
91
+
92
+ const getModule = new GetModule({
93
+ moduleRepository,
94
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
95
+ });
96
+
97
+ const getEntityOptionsById = new GetEntityOptionsById({
98
+ moduleRepository,
99
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
100
+ });
101
+
102
+ const refreshEntityOptions = new RefreshEntityOptions({
103
+ moduleRepository,
104
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
105
+ });
106
+
86
107
  const router = get(params, 'router', express());
87
- const factory = get(params, 'factory');
88
108
  const getUserFromBearerToken = get(params, 'getUserFromBearerToken');
89
109
 
90
- // todo: moduleFactory in factory is not used here anymore, remove it
91
- setIntegrationRoutes(router, factory, getUserFromBearerToken, integrations, {
110
+ setIntegrationRoutes(router, getUserFromBearerToken, {
92
111
  createIntegration,
93
112
  deleteIntegrationForUser,
94
113
  getIntegrationsForUser,
95
- getEntitiesForUserUseCase,
96
- getIntegration,
114
+ getEntitiesForUser,
115
+ getIntegrationInstance,
116
+ updateIntegration,
97
117
  });
98
- setEntityRoutes(router, factory, getUserFromBearerToken, {
118
+ setEntityRoutes(router, getUserFromBearerToken, {
99
119
  getCredentialForUser,
120
+ getModuleInstanceFromType,
121
+ getEntityOptionsByType,
122
+ testModuleAuth,
123
+ getModule,
124
+ getEntityOptionsById,
125
+ refreshEntityOptions,
100
126
  });
101
127
  return router;
102
128
  }
@@ -126,19 +152,17 @@ function checkRequiredParams(params, requiredKeys) {
126
152
  /**
127
153
  * Sets up integration-related routes on the provided Express router
128
154
  * @param {express.Router} router - Express router instance to add routes to
129
- * @param {Object} factory - Factory object containing moduleFactory and integrationFactory
130
- * @param {Object} factory.moduleFactory - Factory for creating and managing API modules
131
- * @param {Object} factory.integrationFactory - Factory for creating and managing integrations
132
155
  * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
156
+ * @param {Object} useCases - use cases for integration management
133
157
  */
134
- function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrationClasses, useCases) {
135
- const { integrationFactory } = factory;
158
+ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
136
159
  const {
137
160
  createIntegration,
138
161
  deleteIntegrationForUser,
139
162
  getIntegrationsForUser,
140
- getEntitiesForUserUseCase,
141
- getIntegration,
163
+ getEntitiesForUser,
164
+ getIntegrationInstance,
165
+ updateIntegration,
142
166
  } = useCases;
143
167
  router.route('/api/integrations').get(
144
168
  catchAsyncError(async (req, res) => {
@@ -152,7 +176,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
152
176
  options: integrations.map((integration) =>
153
177
  integration.options
154
178
  ),
155
- authorized: await getEntitiesForUserUseCase.execute(userId),
179
+ authorized: await getEntitiesForUser.execute(userId),
156
180
  },
157
181
  integrations: integrations,
158
182
  }
@@ -171,27 +195,16 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
171
195
  'entities',
172
196
  'config',
173
197
  ]);
174
- // throw if not value
198
+
175
199
  get(params.config, 'type');
176
200
 
177
- // create integration
178
201
  const integration = await createIntegration.execute(
179
202
  params.entities,
180
203
  userId,
181
204
  params.config
182
205
  );
183
206
 
184
- // post integration initialization
185
- debug(
186
- `Calling onCreate on the ${integration?.constructor?.Config?.name} Integration with no arguments`
187
- );
188
- await integration.send('ON_CREATE', {});
189
-
190
- res.status(201).json(
191
- await integrationFactory.getFormattedIntegration(
192
- integration.record
193
- )
194
- );
207
+ res.status(201).json(integration);
195
208
  })
196
209
  );
197
210
 
@@ -203,23 +216,8 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
203
216
  const userId = user.getId();
204
217
  const params = checkRequiredParams(req.body, ['config']);
205
218
 
206
- const integration =
207
- await integrationFactory.getInstanceFromIntegrationId({
208
- integrationId: req.params.integrationId,
209
- userId,
210
- });
211
-
212
- debug(
213
- `Calling onUpdate on the ${integration?.constructor?.Config?.name} Integration arguments: `,
214
- params
215
- );
216
- await integration.send('ON_UPDATE', params);
217
-
218
- res.json(
219
- await integrationFactory.getFormattedIntegration(
220
- integration.record
221
- )
222
- );
219
+ const integration = await updateIntegration.execute(req.params.integrationId, userId, params.config);
220
+ res.json(integration);
223
221
  })
224
222
  );
225
223
 
@@ -229,18 +227,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
229
227
  req.headers.authorization
230
228
  );
231
229
  const params = checkRequiredParams(req.params, ['integrationId']);
232
- const integration =
233
- await integrationFactory.getInstanceFromIntegrationId({
234
- userId: user.getId(),
235
- integrationId: params.integrationId,
236
- });
237
-
238
- debug(
239
- `Calling onUpdate on the ${integration?.constructor?.Definition?.name} Integration with no arguments`
240
- );
241
- await integration.send('ON_DELETE');
242
230
  await deleteIntegrationForUser.execute(params.integrationId, user.getId());
243
-
244
231
  res.status(204).json({});
245
232
  })
246
233
  );
@@ -251,11 +238,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
251
238
  req.headers.authorization
252
239
  );
253
240
  const params = checkRequiredParams(req.params, ['integrationId']);
254
- const integration =
255
- await integrationFactory.getInstanceFromIntegrationId({
256
- integrationId: params.integrationId,
257
- userId: user.getId(),
258
- });
241
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
259
242
  res.json(await integration.send('GET_CONFIG_OPTIONS'));
260
243
  })
261
244
  );
@@ -270,13 +253,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
270
253
  const params = checkRequiredParams(req.params, [
271
254
  'integrationId',
272
255
  ]);
273
- const integration =
274
- await integrationFactory.getInstanceFromIntegrationId(
275
- {
276
- integrationId: params.integrationId,
277
- userId: user.getId(),
278
- }
279
- );
256
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
280
257
 
281
258
  res.json(
282
259
  await integration.send('REFRESH_CONFIG_OPTIONS', req.body)
@@ -289,7 +266,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
289
266
  req.headers.authorization
290
267
  );
291
268
  const params = checkRequiredParams(req.params, ['integrationId']);
292
- const integration = await getIntegration.execute(params.integrationId, user.getId());
269
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
293
270
  res.json(await integration.send('GET_USER_ACTIONS', req.body));
294
271
  })
295
272
  );
@@ -305,13 +282,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
305
282
  'integrationId',
306
283
  'actionId',
307
284
  ]);
308
- const integration =
309
- await integrationFactory.getInstanceFromIntegrationId(
310
- {
311
- integrationId: params.integrationId,
312
- userId: user.getId(),
313
- }
314
- );
285
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
315
286
 
316
287
  res.json(
317
288
  await integration.send('GET_USER_ACTION_OPTIONS', {
@@ -335,13 +306,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
335
306
  'integrationId',
336
307
  'actionId',
337
308
  ]);
338
- const integration =
339
- await integrationFactory.getInstanceFromIntegrationId(
340
- {
341
- integrationId: params.integrationId,
342
- userId: user.getId(),
343
- }
344
- );
309
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
345
310
 
346
311
  res.json(
347
312
  await integration.send('REFRESH_USER_ACTION_OPTIONS', {
@@ -361,12 +326,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
361
326
  'integrationId',
362
327
  'actionId',
363
328
  ]);
364
- const integration =
365
- await integrationFactory.getInstanceFromIntegrationId({
366
- integrationId: params.integrationId,
367
- userId: user.getId(),
368
- });
369
-
329
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
370
330
  res.json(await integration.send(params.actionId, req.body));
371
331
  })
372
332
  );
@@ -400,13 +360,8 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
400
360
  const user = await getUserFromBearerToken.execute(
401
361
  req.headers.authorization
402
362
  );
403
- const userId = user.getId();
404
363
  const params = checkRequiredParams(req.params, ['integrationId']);
405
- const instance =
406
- await integrationFactory.getInstanceFromIntegrationId({
407
- userId,
408
- integrationId: params.integrationId,
409
- });
364
+ const instance = await getIntegrationInstance.execute(params.integrationId, user.getId());
410
365
 
411
366
  if (!instance) {
412
367
  throw Boom.notFound();
@@ -432,25 +387,18 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
432
387
  /**
433
388
  * Sets up entity-related routes for the integration router
434
389
  * @param {Object} router - Express router instance
435
- * @param {Object} factory - Factory object containing moduleFactory
436
390
  * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
437
391
  */
438
- function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
439
- const { moduleFactory } = factory;
440
- const { getCredentialForUser } = useCases;
441
- const getModuleInstance = async (userId, entityType) => {
442
- if (!moduleFactory.checkIsValidType(entityType)) {
443
- throw Boom.badRequest(
444
- `Error: Invalid entity type of ${entityType}, options are ${moduleFactory.moduleTypes.join(
445
- ', '
446
- )}`
447
- );
448
- }
449
- return await moduleFactory.getInstanceFromTypeName(
450
- entityType,
451
- userId
452
- );
453
- };
392
+ function setEntityRoutes(router, getUserFromBearerToken, useCases) {
393
+ const {
394
+ getCredentialForUser,
395
+ getModuleInstanceFromType,
396
+ getEntityOptionsByType,
397
+ testModuleAuth,
398
+ getModule,
399
+ getEntityOptionsById,
400
+ refreshEntityOptions,
401
+ } = useCases;
454
402
 
455
403
  router.route('/api/authorize').get(
456
404
  catchAsyncError(async (req, res) => {
@@ -459,7 +407,7 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
459
407
  );
460
408
  const userId = user.getId();
461
409
  const params = checkRequiredParams(req.query, ['entityType']);
462
- const module = await getModuleInstance(userId, params.entityType);
410
+ const module = await getModuleInstanceFromType.execute(userId, params.entityType);
463
411
  const areRequirementsValid =
464
412
  module.validateAuthorizationRequirements();
465
413
  if (!areRequirementsValid) {
@@ -482,7 +430,7 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
482
430
  'entityType',
483
431
  'data',
484
432
  ]);
485
- const module = await getModuleInstance(userId, params.entityType);
433
+ const module = await getModuleInstanceFromType.execute(userId, params.entityType);
486
434
 
487
435
  res.json(
488
436
  await module.processAuthorizationCallback({
@@ -515,7 +463,7 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
515
463
  throw Boom.badRequest('Invalid credential ID');
516
464
  }
517
465
 
518
- const module = await getModuleInstance(userId, params.entityType);
466
+ const module = await getModuleInstanceFromType.execute(userId, params.entityType);
519
467
  const entityDetails = await module.getEntityDetails(
520
468
  module.api,
521
469
  null,
@@ -544,9 +492,9 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
544
492
  }
545
493
 
546
494
  const params = checkRequiredParams(req.query, ['entityType']);
547
- const module = await getModuleInstance(userId, params.entityType);
495
+ const entityOptions = await getEntityOptionsByType.execute(userId, params.entityType);
548
496
 
549
- res.json(await module.getEntityOptions());
497
+ res.json(entityOptions);
550
498
  })
551
499
  );
552
500
 
@@ -557,17 +505,11 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
557
505
  );
558
506
  const userId = user.getId();
559
507
  const params = checkRequiredParams(req.params, ['entityId']);
560
- const module = await moduleFactory.getModuleInstanceFromEntityId(
508
+ const testAuthResponse = await testModuleAuth.execute(
561
509
  params.entityId,
562
510
  userId
563
511
  );
564
512
 
565
- if (!module) {
566
- throw Boom.notFound();
567
- }
568
-
569
- const testAuthResponse = await module.testAuth();
570
-
571
513
  if (!testAuthResponse) {
572
514
  res.status(400);
573
515
  res.json({
@@ -592,16 +534,12 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
592
534
  );
593
535
  const userId = user.getId();
594
536
  const params = checkRequiredParams(req.params, ['entityId']);
595
- const module = await moduleFactory.getModuleInstanceFromEntityId(
537
+ const module = await getModule.execute(
596
538
  params.entityId,
597
539
  userId
598
540
  );
599
541
 
600
- if (!module) {
601
- throw Boom.notFound();
602
- }
603
-
604
- res.json(module.entity);
542
+ res.json(module);
605
543
  })
606
544
  );
607
545
 
@@ -614,16 +552,10 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
614
552
  const params = checkRequiredParams(req.params, [
615
553
  'entityId',
616
554
  ]);
617
- const module = await moduleFactory.getModuleInstanceFromEntityId(
618
- params.entityId,
619
- userId
620
- );
621
555
 
622
- if (!module) {
623
- throw Boom.notFound();
624
- }
556
+ const entityOptions = await getEntityOptionsById.execute(params.entityId, userId);
625
557
 
626
- res.json(await module.getEntityOptions());
558
+ res.json(entityOptions);
627
559
  })
628
560
  );
629
561
 
@@ -636,16 +568,13 @@ function setEntityRoutes(router, factory, getUserFromBearerToken, useCases) {
636
568
  const params = checkRequiredParams(req.params, [
637
569
  'entityId',
638
570
  ]);
639
- const module = await moduleFactory.getModuleInstanceFromEntityId(
571
+ const updatedOptions = await refreshEntityOptions.execute(
640
572
  params.entityId,
641
- userId
573
+ userId,
574
+ req.body
642
575
  );
643
576
 
644
- if (!module) {
645
- throw Boom.notFound();
646
- }
647
-
648
- res.json(await module.refreshEntityOptions(req.body));
577
+ res.json(updatedOptions);
649
578
  })
650
579
  );
651
580
  }
@@ -1,5 +1,37 @@
1
1
  const { Options } = require('./options');
2
2
 
3
+ /**
4
+ * Integration (Domain Aggregate-Root)
5
+ * ----------------------------------
6
+ * This class represents a *configured* integration instance at runtime. It is
7
+ * deliberately split into **two layers**:
8
+ * 1. A *data snapshot* of the persisted record (id, userId, config, etc.).
9
+ * 2. A *behaviour* object: a concrete class supplied by the app developer
10
+ * that extends `IntegrationBase` and implements event handlers, user
11
+ * actions, custom routes, etc.
12
+ *
13
+ * The two layers are glued together via a **JavaScript `Proxy`**. When a
14
+ * property is requested on an `Integration` instance we:
15
+ * • Check if the property exists on the wrapper itself (data-layer).
16
+ * • Fallback to the behaviour instance (logic-layer).
17
+ * • If the value is a function we `.bind(this)` so that the function's
18
+ * `this` always points to the *wrapper* – giving it access to both data
19
+ * and behaviour transparently.
20
+ *
21
+ * This means you can treat a hydrated Integration as if it *were* the custom
22
+ * class:
23
+ *
24
+ * ```js
25
+ * const integration = await getIntegration.execute(id, userId);
26
+ * // `send` actually lives on IntegrationBase but is accessible here
27
+ * const actions = await integration.send('GET_USER_ACTIONS');
28
+ * ```
29
+ *
30
+ * A corollary benefit is that **circular references stay internal**: the heavy
31
+ * `Module → Api → delegate` graph is never exposed when we later serialise the
32
+ * object to JSON – we map it to a DTO first.
33
+ */
34
+
3
35
  /**
4
36
  * Integration Domain Entity
5
37
  * Represents a configured integration with its data and behavior
@@ -15,7 +47,6 @@ class Integration {
15
47
  status,
16
48
  version,
17
49
  messages,
18
- entityReference,
19
50
  integrationClass,
20
51
  modules = {}
21
52
  }) {
@@ -27,7 +58,6 @@ class Integration {
27
58
  this.status = status;
28
59
  this.version = version;
29
60
  this.messages = messages;
30
- this.entityReference = entityReference;
31
61
 
32
62
  // Integration behavior (strategy pattern)
33
63
  this.integrationClass = integrationClass;
@@ -38,7 +68,10 @@ class Integration {
38
68
  // Initialize basic behavior (sync parts only)
39
69
  this._initializeBasicBehavior();
40
70
 
41
- // Return a Proxy to handle dynamic method delegation
71
+ // --- Behaviour delegation via Proxy --------------------------------
72
+ // The Proxy merges the *data layer* (this wrapper) with the *behaviour
73
+ // layer* (custom IntegrationBase subclass). Consumers don't have to
74
+ // know (or care) where a method/property is defined.
42
75
  return new Proxy(this, {
43
76
  get(target, prop) {
44
77
  // First, check if property exists on Integration entity
@@ -76,6 +109,21 @@ class Integration {
76
109
  // Copy events
77
110
  this.events = this.behavior.events || {};
78
111
  this.defaultEvents = this.behavior.defaultEvents || {};
112
+
113
+
114
+ // Expose behaviour instance methods directly on the wrapper so that
115
+ // early-bound handlers (created before behaviour existed) can still
116
+ // access them without falling back through the Proxy. This prevents
117
+ // `undefined` errors for methods like `loadDynamicUserActions` that
118
+ // may be invoked inside default event-handlers.
119
+ const proto = Object.getPrototypeOf(this.behavior);
120
+ for (const key of Object.getOwnPropertyNames(proto)) {
121
+ if (key === 'constructor') continue;
122
+ if (typeof proto[key] === 'function' && this[key] === undefined) {
123
+ // Bind to behaviour so internal `this` remains correct.
124
+ this[key] = proto[key].bind(this.behavior);
125
+ }
126
+ }
79
127
  }
80
128
  }
81
129
 
@@ -176,7 +224,6 @@ class Integration {
176
224
  status: this.status,
177
225
  version: this.version,
178
226
  messages: this.messages,
179
- entityReference: this.entityReference,
180
227
  // Expose userActions if they were loaded/attached elsewhere
181
228
  userActions: this.userActions,
182
229
  };
@@ -17,29 +17,39 @@ class CreateIntegration {
17
17
  async execute(entities, userId, config) {
18
18
  const integrationRecord = await this.integrationRepository.createIntegration(entities, userId, config);
19
19
 
20
- const modules = {};
21
- for (const [key, entity] of Object.entries(integrationRecord.entities)) {
20
+
21
+ // 2. Get the correct Integration class by type
22
+ const integrationClass = this.integrationClasses.find(
23
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
24
+ );
25
+
26
+ if (!integrationClass) {
27
+ throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
28
+ }
29
+
30
+ const modules = [];
31
+ for (const entityId of integrationRecord.entitiesIds) {
22
32
  const moduleInstance = await this.moduleService.getModuleInstance(
23
- entity._id,
24
- integrationRecord.user
33
+ entityId,
34
+ integrationRecord.userId
25
35
  );
26
- modules[key] = moduleInstance;
36
+ modules.push(moduleInstance);
27
37
  }
28
38
 
29
39
  const integrationInstance = new Integration({
30
- id: integrationRecord._id.toString(),
31
- userId: integrationRecord.user,
32
- entities: integrationRecord.entities,
40
+ id: integrationRecord.id,
41
+ userId: integrationRecord.userId,
42
+ entities: integrationRecord.entitiesIds,
33
43
  config: integrationRecord.config,
34
44
  status: integrationRecord.status,
35
45
  version: integrationRecord.version,
36
46
  messages: integrationRecord.messages,
37
- entityReference: integrationRecord.entityReference,
38
- integrationClass: this.integrationClasses[integrationRecord.config.type], // todo: check if this is correct
47
+ integrationClass: integrationClass,
39
48
  modules
40
49
  });
41
50
 
42
51
  await integrationInstance.initialize();
52
+ await integrationInstance.send('ON_CREATE', {});
43
53
 
44
54
  return mapIntegrationClassToIntegrationDTO(integrationInstance);
45
55
  }