@friggframework/core 2.0.0--canary.396.accf516.0 → 2.0.0--canary.396.469364a.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.
@@ -1,5 +1,9 @@
1
1
  const { Router } = require('express');
2
2
  const { Worker } = require('@friggframework/core');
3
+ const { loadAppDefinition } = require('./app-definition-loader');
4
+ const { IntegrationRepository } = require('../integrations/integration-repository');
5
+ const { ModuleService } = require('../module-plugin/module-service');
6
+ const { GetIntegrationInstance } = require('../integrations/use-cases/get-integration-instance');
3
7
 
4
8
  const loadRouterFromObject = (IntegrationClass, routerObject) => {
5
9
  const router = Router();
@@ -23,29 +27,37 @@ const loadRouterFromObject = (IntegrationClass, routerObject) => {
23
27
  };
24
28
 
25
29
  //todo: this should be in a use case class
26
- const createQueueWorker = (integrationClass, integrationFactory) => {
30
+ const createQueueWorker = (integrationClass) => {
27
31
  class QueueWorker extends Worker {
28
32
  async _run(params, context) {
29
33
  try {
30
- let instance;
34
+ let integrationInstance;
31
35
  if (!params.integrationId) {
32
- instance = new integrationClass();
33
- await instance.loadModules();
34
- // await instance.loadUserActions();
35
- await instance.registerEventHandlers();
36
+ integrationInstance = new integrationClass();
37
+ await integrationInstance.loadModules();
38
+ await integrationInstance.registerEventHandlers();
36
39
  console.log(
37
40
  `${params.event} for ${integrationClass.Definition.name} integration with no integrationId`
38
41
  );
39
42
  } else {
40
- instance =
41
- await integrationFactory.getInstanceFromIntegrationId({
42
- integrationId: params.integrationId,
43
- });
43
+ const { integrations: integrationClasses } = loadAppDefinition();
44
+ const integrationRepository = new IntegrationRepository();
45
+ const moduleService = new ModuleService();
46
+
47
+ const getIntegrationInstance = new GetIntegrationInstance({
48
+ integrationRepository,
49
+ integrationClasses,
50
+ moduleService,
51
+ });
52
+
53
+ // todo: are we going to have the userId available here?
54
+ integrationInstance = await getIntegrationInstance.execute(params.integrationId, params.userId);
44
55
  console.log(
45
- `${params.event} for ${instance.record.config.type} of integrationId: ${params.integrationId}`
56
+ `${params.event} for ${integrationInstance.record.config.type} of integrationId: ${params.integrationId}`
46
57
  );
47
58
  }
48
- const res = await instance.send(params.event, {
59
+
60
+ const res = await integrationInstance.send(params.event, {
49
61
  data: params.data,
50
62
  context,
51
63
  });
@@ -4,11 +4,9 @@ const {
4
4
  loadAppDefinition,
5
5
  } = require('../app-definition-loader');
6
6
  const { UserRepository } = require('../../user/user-repository');
7
- const { IntegrationFactory } = require('../../integrations/integration-factory');
8
7
  const { GetUserFromBearerToken } = require('../../user/use-cases/get-user-from-bearer-token');
9
8
 
10
- const { integrations, userConfig } = loadAppDefinition();
11
- const integrationFactory = new IntegrationFactory(integrations);
9
+ const { userConfig } = loadAppDefinition();
12
10
  const userRepository = new UserRepository({ userConfig });
13
11
  const getUserFromBearerToken = new GetUserFromBearerToken({
14
12
  userRepository,
@@ -16,10 +14,6 @@ const getUserFromBearerToken = new GetUserFromBearerToken({
16
14
  });
17
15
 
18
16
  const router = createIntegrationRouter({
19
- factory: {
20
- moduleFactory: integrationFactory.moduleFactory,
21
- integrationFactory,
22
- },
23
17
  getUserFromBearerToken,
24
18
  });
25
19
 
@@ -1,17 +1,15 @@
1
1
  const { createAppHandler } = require('./../app-handler-helpers');
2
2
  const {
3
3
  loadAppDefinition,
4
- loadRouterFromObject,
5
4
  } = require('../app-definition-loader');
6
5
  const { Router } = require('express');
7
- const { IntegrationFactory } = require('../../integrations/integration-factory');
6
+ const { loadRouterFromObject } = require('../backend-utils');
8
7
 
9
8
  const handlers = {};
10
- const { integrations } = loadAppDefinition();
11
- const integrationFactory = new IntegrationFactory(integrations);
9
+ const { integrations: integrationClasses } = loadAppDefinition();
12
10
 
13
11
  //todo: this should be in a use case class
14
- for (const IntegrationClass of integrationFactory.integrationClasses) {
12
+ for (const IntegrationClass of integrationClasses) {
15
13
  const router = Router();
16
14
  const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
17
15
 
@@ -1,13 +1,12 @@
1
1
  const { createHandler } = require('@friggframework/core');
2
- const { loadAppDefinition, createQueueWorker } = require('../app-definition-loader');
3
- const { IntegrationFactory } = require('../../integrations/integration-factory');
2
+ const { loadAppDefinition } = require('../app-definition-loader');
3
+ const { createQueueWorker } = require('../backend-utils');
4
4
 
5
5
  const handlers = {};
6
- const { integrations } = loadAppDefinition();
7
- const integrationFactory = new IntegrationFactory(integrations);
6
+ const { integrations: integrationClasses } = loadAppDefinition();
8
7
 
9
- integrationFactory.integrationClasses.forEach((IntegrationClass) => {
10
- const defaultQueueWorker = createQueueWorker(IntegrationClass, integrationFactory);
8
+ integrationClasses.forEach((IntegrationClass) => {
9
+ const defaultQueueWorker = createQueueWorker(IntegrationClass);
11
10
 
12
11
  handlers[`${IntegrationClass.Definition.name}`] = {
13
12
  queueWorker: createHandler({
package/index.js CHANGED
@@ -38,7 +38,6 @@ const {
38
38
  IntegrationModel,
39
39
  Options,
40
40
  IntegrationMapping,
41
- IntegrationFactory,
42
41
  createIntegrationRouter,
43
42
  checkRequiredParams,
44
43
  } = require('./integrations/index');
@@ -105,7 +104,6 @@ module.exports = {
105
104
  IntegrationModel,
106
105
  Options,
107
106
  IntegrationMapping,
108
- IntegrationFactory,
109
107
  checkRequiredParams,
110
108
  createIntegrationRouter,
111
109
 
@@ -2,7 +2,6 @@ const { IntegrationBase } = require('./integration-base');
2
2
  const { IntegrationModel } = require('./integration-model');
3
3
  const { Options } = require('./options');
4
4
  const { IntegrationMapping } = require('./integration-mapping');
5
- const { IntegrationFactory } = require('./integration-factory');
6
5
  const { createIntegrationRouter, checkRequiredParams } = require('./integration-router');
7
6
 
8
7
  module.exports = {
@@ -10,7 +9,6 @@ module.exports = {
10
9
  IntegrationModel,
11
10
  Options,
12
11
  IntegrationMapping,
13
- IntegrationFactory,
14
12
  createIntegrationRouter,
15
13
  checkRequiredParams,
16
14
  };
@@ -23,7 +23,7 @@ class IntegrationRepository {
23
23
  return {
24
24
  id: integrationRecord._id,
25
25
  entitiesIds: integrationRecord.entities.map(e => e._id),
26
- userId: integrationRecord.user,
26
+ userId: integrationRecord.user.toString(),
27
27
  config: integrationRecord.config,
28
28
  version: integrationRecord.version,
29
29
  status: integrationRecord.status,
@@ -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,53 +11,36 @@ 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');
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
18
 
19
- // todo: dont send moduleFactory and integrationFactory as a factory object, instead send them as separate params.
20
- // todo: this could be a use case class
21
19
  /**
22
20
  * Creates an Express router with integration and entity routes configured
23
21
  * @param {Object} params - Configuration parameters for the router
24
22
  * @param {express.Router} [params.router] - Optional Express router instance, creates new one if not provided
25
- * @param {Object} params.factory - Factory object containing moduleFactory and integrationFactory
26
- * @param {Object} params.factory.moduleFactory - Factory for creating and managing API modules
27
- * @param {Object} params.factory.integrationFactory - Factory for creating and managing integrations
28
23
  * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} params.getUserFromBearerToken - Use case for retrieving a user from a bearer token
29
24
  * @returns {express.Router} Configured Express router with integration and entity routes
30
25
  */
31
26
  function createIntegrationRouter(params) {
32
- const { integrations } = loadAppDefinition();
27
+ const { integrations: integrationClasses } = loadAppDefinition();
33
28
  const moduleRepository = new ModuleRepository();
34
29
  const integrationRepository = new IntegrationRepository();
35
30
  const credentialRepository = new CredentialRepository();
36
31
 
37
- // todo: move this into a utils file
38
- const getModules = () => {
39
- return [
40
- ...new Set(
41
- integrations
42
- .map((integration) =>
43
- Object.values(integration.Definition.modules).map(
44
- (module) => module.definition
45
- )
46
- )
47
- .flat()
48
- ),
49
- ];
50
- };
51
32
  const moduleService = new ModuleService({
52
33
  moduleRepository,
53
- moduleDefinitions: getModules(),
34
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
54
35
  });
55
36
  const deleteIntegrationForUser = new DeleteIntegrationForUser({
56
37
  integrationRepository,
38
+ integrationClasses,
57
39
  });
58
40
 
59
41
  const getIntegrationsForUser = new GetIntegrationsForUser({
60
42
  integrationRepository,
61
- integrationClasses: integrations,
43
+ integrationClasses,
62
44
  moduleService,
63
45
  moduleRepository,
64
46
  });
@@ -67,25 +49,38 @@ function createIntegrationRouter(params) {
67
49
  });
68
50
  const createIntegration = new CreateIntegration({
69
51
  integrationRepository,
70
- integrationClasses: integrations,
52
+ integrationClasses,
71
53
  moduleService,
72
54
  });
73
55
 
74
- const getEntitiesForUserUseCase = new GetEntitiesForUser({
56
+ const getEntitiesForUser = new GetEntitiesForUser({
75
57
  moduleRepository,
76
- moduleDefinitions: getModules(),
58
+ moduleDefinitions: getModulesDefinitionFromIntegrationClasses(integrationClasses),
59
+ });
60
+
61
+ const getIntegrationInstance = new GetIntegrationInstance({
62
+ integrationRepository,
63
+ integrationClasses,
64
+ moduleService,
65
+ });
66
+
67
+ const updateIntegration = new UpdateIntegration({
68
+ integrationRepository,
69
+ integrationClasses,
70
+ moduleService,
77
71
  });
78
72
 
79
73
  const router = get(params, 'router', express());
80
74
  const factory = get(params, 'factory');
81
75
  const getUserFromBearerToken = get(params, 'getUserFromBearerToken');
82
76
 
83
- // todo: moduleFactory in factory is not used here anymore, remove it
84
- setIntegrationRoutes(router, factory, getUserFromBearerToken, integrations, {
77
+ setIntegrationRoutes(router, getUserFromBearerToken, {
85
78
  createIntegration,
86
79
  deleteIntegrationForUser,
87
80
  getIntegrationsForUser,
88
- getEntitiesForUserUseCase,
81
+ getEntitiesForUser,
82
+ getIntegrationInstance,
83
+ updateIntegration,
89
84
  });
90
85
  setEntityRoutes(router, factory, getUserFromBearerToken, {
91
86
  getCredentialForUser,
@@ -118,18 +113,17 @@ function checkRequiredParams(params, requiredKeys) {
118
113
  /**
119
114
  * Sets up integration-related routes on the provided Express router
120
115
  * @param {express.Router} router - Express router instance to add routes to
121
- * @param {Object} factory - Factory object containing moduleFactory and integrationFactory
122
- * @param {Object} factory.moduleFactory - Factory for creating and managing API modules
123
- * @param {Object} factory.integrationFactory - Factory for creating and managing integrations
124
116
  * @param {import('../user/use-cases/get-user-from-bearer-token').GetUserFromBearerToken} getUserFromBearerToken - Use case for retrieving a user from a bearer token
117
+ * @param {Object} useCases - use cases for integration management
125
118
  */
126
- function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrationClasses, useCases) {
127
- const { integrationFactory } = factory;
119
+ function setIntegrationRoutes(router, getUserFromBearerToken, useCases) {
128
120
  const {
129
121
  createIntegration,
130
122
  deleteIntegrationForUser,
131
123
  getIntegrationsForUser,
132
- getEntitiesForUserUseCase,
124
+ getEntitiesForUser,
125
+ getIntegrationInstance,
126
+ updateIntegration,
133
127
  } = useCases;
134
128
  router.route('/api/integrations').get(
135
129
  catchAsyncError(async (req, res) => {
@@ -143,7 +137,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
143
137
  options: integrations.map((integration) =>
144
138
  integration.options
145
139
  ),
146
- authorized: await getEntitiesForUserUseCase.execute(userId),
140
+ authorized: await getEntitiesForUser.execute(userId),
147
141
  },
148
142
  integrations: integrations,
149
143
  }
@@ -162,27 +156,16 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
162
156
  'entities',
163
157
  'config',
164
158
  ]);
165
- // throw if not value
159
+
166
160
  get(params.config, 'type');
167
161
 
168
- // create integration
169
162
  const integration = await createIntegration.execute(
170
163
  params.entities,
171
164
  userId,
172
165
  params.config
173
166
  );
174
167
 
175
- // post integration initialization
176
- debug(
177
- `Calling onCreate on the ${integration?.constructor?.Config?.name} Integration with no arguments`
178
- );
179
- await integration.send('ON_CREATE', {});
180
-
181
- res.status(201).json(
182
- await integrationFactory.getFormattedIntegration(
183
- integration.record
184
- )
185
- );
168
+ res.status(201).json(integration);
186
169
  })
187
170
  );
188
171
 
@@ -194,23 +177,8 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
194
177
  const userId = user.getId();
195
178
  const params = checkRequiredParams(req.body, ['config']);
196
179
 
197
- const integration =
198
- await integrationFactory.getInstanceFromIntegrationId({
199
- integrationId: req.params.integrationId,
200
- userId,
201
- });
202
-
203
- debug(
204
- `Calling onUpdate on the ${integration?.constructor?.Config?.name} Integration arguments: `,
205
- params
206
- );
207
- await integration.send('ON_UPDATE', params);
208
-
209
- res.json(
210
- await integrationFactory.getFormattedIntegration(
211
- integration.record
212
- )
213
- );
180
+ const integration = await updateIntegration.execute(req.params.integrationId, userId, params.config);
181
+ res.json(integration);
214
182
  })
215
183
  );
216
184
 
@@ -220,18 +188,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
220
188
  req.headers.authorization
221
189
  );
222
190
  const params = checkRequiredParams(req.params, ['integrationId']);
223
- const integration =
224
- await integrationFactory.getInstanceFromIntegrationId({
225
- userId: user.getId(),
226
- integrationId: params.integrationId,
227
- });
228
-
229
- debug(
230
- `Calling onUpdate on the ${integration?.constructor?.Definition?.name} Integration with no arguments`
231
- );
232
- await integration.send('ON_DELETE');
233
191
  await deleteIntegrationForUser.execute(params.integrationId, user.getId());
234
-
235
192
  res.status(204).json({});
236
193
  })
237
194
  );
@@ -242,11 +199,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
242
199
  req.headers.authorization
243
200
  );
244
201
  const params = checkRequiredParams(req.params, ['integrationId']);
245
- const integration =
246
- await integrationFactory.getInstanceFromIntegrationId({
247
- integrationId: params.integrationId,
248
- userId: user.getId(),
249
- });
202
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
250
203
  res.json(await integration.send('GET_CONFIG_OPTIONS'));
251
204
  })
252
205
  );
@@ -261,13 +214,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
261
214
  const params = checkRequiredParams(req.params, [
262
215
  'integrationId',
263
216
  ]);
264
- const integration =
265
- await integrationFactory.getInstanceFromIntegrationId(
266
- {
267
- integrationId: params.integrationId,
268
- userId: user.getId(),
269
- }
270
- );
217
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
271
218
 
272
219
  res.json(
273
220
  await integration.send('REFRESH_CONFIG_OPTIONS', req.body)
@@ -280,11 +227,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
280
227
  req.headers.authorization
281
228
  );
282
229
  const params = checkRequiredParams(req.params, ['integrationId']);
283
- const integration =
284
- await integrationFactory.getInstanceFromIntegrationId({
285
- integrationId: params.integrationId,
286
- userId: user.getId(),
287
- });
230
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
288
231
  res.json(await integration.send('GET_USER_ACTIONS', req.body));
289
232
  })
290
233
  );
@@ -300,13 +243,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
300
243
  'integrationId',
301
244
  'actionId',
302
245
  ]);
303
- const integration =
304
- await integrationFactory.getInstanceFromIntegrationId(
305
- {
306
- integrationId: params.integrationId,
307
- userId: user.getId(),
308
- }
309
- );
246
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
310
247
 
311
248
  res.json(
312
249
  await integration.send('GET_USER_ACTION_OPTIONS', {
@@ -330,13 +267,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
330
267
  'integrationId',
331
268
  'actionId',
332
269
  ]);
333
- const integration =
334
- await integrationFactory.getInstanceFromIntegrationId(
335
- {
336
- integrationId: params.integrationId,
337
- userId: user.getId(),
338
- }
339
- );
270
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
340
271
 
341
272
  res.json(
342
273
  await integration.send('REFRESH_USER_ACTION_OPTIONS', {
@@ -356,12 +287,7 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
356
287
  'integrationId',
357
288
  'actionId',
358
289
  ]);
359
- const integration =
360
- await integrationFactory.getInstanceFromIntegrationId({
361
- integrationId: params.integrationId,
362
- userId: user.getId(),
363
- });
364
-
290
+ const integration = await getIntegrationInstance.execute(params.integrationId, user.getId());
365
291
  res.json(await integration.send(params.actionId, req.body));
366
292
  })
367
293
  );
@@ -395,13 +321,8 @@ function setIntegrationRoutes(router, factory, getUserFromBearerToken, integrati
395
321
  const user = await getUserFromBearerToken.execute(
396
322
  req.headers.authorization
397
323
  );
398
- const userId = user.getId();
399
324
  const params = checkRequiredParams(req.params, ['integrationId']);
400
- const instance =
401
- await integrationFactory.getInstanceFromIntegrationId({
402
- userId,
403
- integrationId: params.integrationId,
404
- });
325
+ const instance = await getIntegrationInstance.execute(params.integrationId, user.getId());
405
326
 
406
327
  if (!instance) {
407
328
  throw Boom.notFound();
@@ -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
  }
@@ -1,28 +1,52 @@
1
1
  const Boom = require('@hapi/boom');
2
2
 
3
3
  class DeleteIntegrationForUser {
4
- constructor({ integrationRepository }) {
4
+ constructor({ integrationRepository, integrationClasses }) {
5
5
 
6
6
  /**
7
7
  * @type {import('../integration-repository').IntegrationRepository}
8
8
  */
9
9
  this.integrationRepository = integrationRepository;
10
+ this.integrationClasses = integrationClasses;
10
11
  }
11
12
 
12
13
  async execute(integrationId, userId) {
13
- const integration = await this.integrationRepository.findIntegrationById(integrationId);
14
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
14
15
 
15
- if (!integration) {
16
+ if (!integrationRecord) {
16
17
  throw Boom.notFound(
17
18
  `Integration with id of ${integrationId} does not exist`
18
19
  );
19
20
  }
20
21
 
21
- if (integration.user.toString() !== userId.toString()) {
22
- throw Boom.forbidden('User does not have access to this integration');
22
+ const integrationClass = this.integrationClasses.find(
23
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
24
+ );
25
+
26
+ if (integrationRecord.userId !== userId) {
27
+ throw new Error(
28
+ `Integration ${integrationId} does not belong to User ${userId}`
29
+ );
23
30
  }
24
31
 
32
+ const integrationInstance = new Integration({
33
+ id: integrationRecord.id,
34
+ userId: integrationRecord.userId,
35
+ entities: integrationRecord.entitiesIds,
36
+ config: integrationRecord.config,
37
+ status: integrationRecord.status,
38
+ version: integrationRecord.version,
39
+ messages: integrationRecord.messages,
40
+ integrationClass: integrationClass,
41
+ modules: [],
42
+ });
43
+
44
+ // 6. Complete async initialization (load dynamic actions, register handlers)
45
+ await integrationInstance.initialize();
46
+ await integrationInstance.send('ON_DELETE');
47
+
25
48
  await this.integrationRepository.deleteIntegrationById(integrationId);
49
+
26
50
  }
27
51
  }
28
52
 
@@ -34,13 +34,13 @@ class GetIntegrationForUser {
34
34
  (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
35
35
  );
36
36
 
37
- const modules = {};
38
- for (const [key, entity] of Object.entries(entities)) {
37
+ const modules = [];
38
+ for (const entity of entities) {
39
39
  const moduleInstance = await this.moduleService.getModuleInstance(
40
- entity.id,
40
+ entity._id,
41
41
  integrationRecord.user
42
42
  );
43
- modules[key] = moduleInstance;
43
+ modules.push(moduleInstance);
44
44
  }
45
45
 
46
46
  const integrationInstance = new Integration({
@@ -0,0 +1,73 @@
1
+ const { Integration } = require('../integration');
2
+
3
+ class GetIntegrationInstance {
4
+
5
+ /**
6
+ * @class GetIntegrationInstance
7
+ * @description Use case for retrieving a single integration instance by ID and user.
8
+ * @param {Object} params
9
+ * @param {import('../integration-repository').IntegrationRepository} params.integrationRepository - Repository for integration data access
10
+ * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes
11
+ * @param {import('../module-plugin/module-service').ModuleService} params.moduleService - Service for module instantiation and management
12
+ */
13
+ constructor({
14
+ integrationRepository,
15
+ integrationClasses,
16
+ moduleService,
17
+ }) {
18
+ this.integrationRepository = integrationRepository;
19
+ this.integrationClasses = integrationClasses;
20
+ this.moduleService = moduleService;
21
+ }
22
+
23
+ async execute(integrationId, userId) {
24
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
25
+
26
+ if (!integrationRecord) {
27
+ throw new Error(`No integration found by the ID of ${integrationId}`);
28
+ }
29
+
30
+ const integrationClass = this.integrationClasses.find(
31
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
32
+ );
33
+
34
+ if (!integrationClass) {
35
+ throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
36
+ }
37
+
38
+ if (integrationRecord.userId !== userId) {
39
+ throw new Error(
40
+ `Integration ${integrationId} does not belong to User ${userId}`
41
+ );
42
+ }
43
+
44
+
45
+ const modules = [];
46
+ for (const entityId of integrationRecord.entitiesIds) {
47
+ const moduleInstance = await this.moduleService.getModuleInstance(
48
+ entityId,
49
+ integrationRecord.userId
50
+ );
51
+ modules.push(moduleInstance);
52
+ }
53
+
54
+ const integrationInstance = new Integration({
55
+ id: integrationRecord.id,
56
+ userId: integrationRecord.userId,
57
+ entities: integrationRecord.entitiesIds,
58
+ config: integrationRecord.config,
59
+ status: integrationRecord.status,
60
+ version: integrationRecord.version,
61
+ messages: integrationRecord.messages,
62
+ integrationClass: integrationClass,
63
+ modules
64
+ });
65
+
66
+
67
+ await integrationInstance.initialize();
68
+
69
+ return integrationInstance;
70
+ }
71
+ }
72
+
73
+ module.exports = { GetIntegrationInstance };
@@ -29,13 +29,13 @@ class GetIntegrationsForUser {
29
29
  (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
30
30
  );
31
31
 
32
- const modules = {};
33
- for (const [key, entity] of Object.entries(entities)) {
32
+ const modules = [];
33
+ for (const entity of entities) {
34
34
  const moduleInstance = await this.moduleService.getModuleInstance(
35
35
  entity.id,
36
36
  integrationRecord.userId
37
37
  );
38
- modules[key] = moduleInstance;
38
+ modules.push(moduleInstance);
39
39
  }
40
40
 
41
41
  const integrationInstance = new Integration({
@@ -2,8 +2,16 @@ const { Integration } = require('../integration');
2
2
  const { mapIntegrationClassToIntegrationDTO } = require('../utils/map-integration-dto');
3
3
 
4
4
 
5
- // todo: remove this use case
6
- class GetIntegration {
5
+ class UpdateIntegration {
6
+
7
+ /**
8
+ * @class UpdateIntegration
9
+ * @description Use case for updating a single integration by ID and user.
10
+ * @param {Object} params
11
+ * @param {import('../integration-repository').IntegrationRepository} params.integrationRepository - Repository for integration data access
12
+ * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes
13
+ * @param {import('../module-plugin/module-service').ModuleService} params.moduleService - Service for module instantiation and management
14
+ */
7
15
  constructor({
8
16
  integrationRepository,
9
17
  integrationClasses,
@@ -14,7 +22,7 @@ class GetIntegration {
14
22
  this.moduleService = moduleService;
15
23
  }
16
24
 
17
- async execute(integrationId, userId) {
25
+ async execute(integrationId, userId, config) {
18
26
  // 1. Get integration record from repository
19
27
  const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
20
28
 
@@ -22,42 +30,41 @@ class GetIntegration {
22
30
  throw new Error(`No integration found by the ID of ${integrationId}`);
23
31
  }
24
32
 
33
+ // 2. Get the correct Integration class by type
34
+ const integrationClass = this.integrationClasses.find(
35
+ (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
36
+ );
37
+
25
38
  if (!integrationClass) {
26
39
  throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
27
40
  }
28
41
 
29
- if (!integrationRecord.user.equals(userId)) {
42
+ if (integrationRecord.userId !== userId) {
30
43
  throw new Error(
31
44
  `Integration ${integrationId} does not belong to User ${userId}`
32
45
  );
33
46
  }
34
47
 
35
- // 2. Get the correct Integration class by type
36
- const integrationClass = this.integrationClasses.find(
37
- (integrationClass) => integrationClass.Definition.name === integrationRecord.config.type
38
- );
39
-
40
48
 
41
49
  // 3. Load modules based on entity references
42
- const modules = {};
43
- for (const [key, entity] of Object.entries(integrationRecord.entities)) {
50
+ const modules = [];
51
+ for (const entityId of integrationRecord.entitiesIds) {
44
52
  const moduleInstance = await this.moduleService.getModuleInstance(
45
- entity._id,
46
- integrationRecord.user
53
+ entityId,
54
+ integrationRecord.userId
47
55
  );
48
- modules[key] = moduleInstance;
56
+ modules.push(moduleInstance);
49
57
  }
50
58
 
51
59
  // 4. Create the Integration domain entity with modules
52
60
  const integrationInstance = new Integration({
53
- id: integrationRecord._id.toString(),
54
- userId: integrationRecord.user,
55
- entities: integrationRecord.entities,
61
+ id: integrationRecord.id,
62
+ userId: integrationRecord.userId,
63
+ entities: integrationRecord.entitiesIds,
56
64
  config: integrationRecord.config,
57
65
  status: integrationRecord.status,
58
66
  version: integrationRecord.version,
59
67
  messages: integrationRecord.messages,
60
- entityReference: integrationRecord.entityReference,
61
68
  integrationClass: integrationClass,
62
69
  modules
63
70
  });
@@ -65,9 +72,10 @@ class GetIntegration {
65
72
 
66
73
  // 6. Complete async initialization (load dynamic actions, register handlers)
67
74
  await integrationInstance.initialize();
75
+ await integrationInstance.send('ON_UPDATE', { config });
68
76
 
69
77
  return mapIntegrationClassToIntegrationDTO(integrationInstance);
70
78
  }
71
79
  }
72
80
 
73
- module.exports = { GetIntegration };
81
+ module.exports = { UpdateIntegration };
@@ -19,4 +19,19 @@ function mapIntegrationClassToIntegrationDTO(integration) {
19
19
  };
20
20
  }
21
21
 
22
- module.exports = { mapIntegrationClassToIntegrationDTO };
22
+
23
+ const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
24
+ return [
25
+ ...new Set(
26
+ integrationClasses
27
+ .map((integration) =>
28
+ Object.values(integration.Definition.modules).map(
29
+ (module) => module.definition
30
+ )
31
+ )
32
+ .flat()
33
+ ),
34
+ ];
35
+ };
36
+
37
+ module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };
@@ -22,7 +22,8 @@ class ModuleRepository {
22
22
  async findEntitiesByIds(entitiesIds) {
23
23
  const entitiesRecords = await Entity.find({ _id: { $in: entitiesIds } }, '', { lean: true }).populate('credential');
24
24
 
25
- if (entitiesRecords.length !== entitiesIds.length) {
25
+ // todo: this is a workaround needed while we create an integration with the same entity twice
26
+ if (entitiesRecords.length !== entitiesIds.length && entitiesIds[0] !== entitiesIds[1]) {
26
27
  throw new Error(`Some entities not found`);
27
28
  }
28
29
 
@@ -45,6 +46,25 @@ class ModuleRepository {
45
46
  { lean: true }
46
47
  );
47
48
  }
49
+
50
+ async findEntitiesByIds(entityIds) {
51
+ const entities = await Entity.find(
52
+ { _id: { $in: entityIds } },
53
+ '',
54
+ { lean: true }
55
+ );
56
+
57
+ return entities.map(e => ({
58
+ id: e._id.toString(),
59
+ accountId: e.accountId,
60
+ credentialId: e.credential.toString(),
61
+ userId: e.user.toString(),
62
+ name: e.name,
63
+ externalId: e.externalId,
64
+ type: e.__t,
65
+ moduleName: e.moduleName,
66
+ }));
67
+ }
48
68
  }
49
69
 
50
70
  module.exports = { ModuleRepository };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.396.accf516.0",
4
+ "version": "2.0.0--canary.396.469364a.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "aws-sdk": "^2.1200.0",
@@ -22,9 +22,9 @@
22
22
  "uuid": "^9.0.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@friggframework/eslint-config": "2.0.0--canary.396.accf516.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.396.accf516.0",
27
- "@friggframework/test": "2.0.0--canary.396.accf516.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.396.469364a.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.396.469364a.0",
27
+ "@friggframework/test": "2.0.0--canary.396.469364a.0",
28
28
  "@types/lodash": "4.17.15",
29
29
  "@typescript-eslint/eslint-plugin": "^8.0.0",
30
30
  "chai": "^4.3.6",
@@ -53,5 +53,5 @@
53
53
  },
54
54
  "homepage": "https://github.com/friggframework/frigg#readme",
55
55
  "description": "",
56
- "gitHead": "accf516c6aef6b581e2fe68268c28676275efa0d"
56
+ "gitHead": "469364a7e870439540866a1a8f35107e9e2aef18"
57
57
  }
@@ -1,195 +0,0 @@
1
- const { ModuleFactory, Entity } = require('../module-plugin');
2
- const { IntegrationModel } = require('./integration-model');
3
-
4
- class IntegrationFactory {
5
- constructor(integrationClasses = []) {
6
- this.integrationClasses = integrationClasses;
7
- this.moduleFactory = new ModuleFactory(...this.getModules());
8
- this.integrationTypes = this.integrationClasses.map(
9
- (IntegrationClass) => IntegrationClass.getName()
10
- );
11
- this.getIntegrationDefinitions = this.integrationClasses.map(
12
- (IntegrationClass) => IntegrationClass.Definition
13
- );
14
- }
15
-
16
- async getIntegrationOptions() {
17
- const options = this.integrationClasses.map(
18
- (IntegrationClass) => IntegrationClass
19
- );
20
- return {
21
- entities: {
22
- options: options.map((IntegrationClass) =>
23
- IntegrationClass.getOptionDetails()
24
- ),
25
- authorized: [],
26
- },
27
- integrations: [],
28
- };
29
- }
30
-
31
- getModules() {
32
- return [
33
- ...new Set(
34
- this.integrationClasses
35
- .map((integration) =>
36
- Object.values(integration.Definition.modules).map(
37
- (module) => module.definition
38
- )
39
- )
40
- .flat()
41
- ),
42
- ];
43
- }
44
-
45
- getIntegrationClassByType(type) {
46
- const integrationClassIndex = this.integrationTypes.indexOf(type);
47
- return this.integrationClasses[integrationClassIndex];
48
- }
49
- getModuleTypesAndKeys(integrationClass) {
50
- const moduleTypesAndKeys = {};
51
- const moduleTypeCount = {};
52
-
53
- if (integrationClass && integrationClass.Definition.modules) {
54
- for (const [key, moduleClass] of Object.entries(
55
- integrationClass.Definition.modules
56
- )) {
57
- if (
58
- moduleClass &&
59
- typeof moduleClass.definition.getName === 'function'
60
- ) {
61
- const moduleType = moduleClass.definition.getName();
62
-
63
- // Check if this module type has already been seen
64
- if (moduleType in moduleTypesAndKeys) {
65
- throw new Error(
66
- `Duplicate module type "${moduleType}" found in integration class definition.`
67
- );
68
- }
69
-
70
- // Well how baout now
71
-
72
- moduleTypesAndKeys[moduleType] = key;
73
- moduleTypeCount[moduleType] =
74
- (moduleTypeCount[moduleType] || 0) + 1;
75
- }
76
- }
77
- }
78
-
79
- // Check for any module types with count > 1
80
- for (const [moduleType, count] of Object.entries(moduleTypeCount)) {
81
- if (count > 1) {
82
- throw new Error(
83
- `Multiple instances of module type "${moduleType}" found in integration class definition.`
84
- );
85
- }
86
- }
87
-
88
- return moduleTypesAndKeys;
89
- }
90
-
91
- async getInstanceFromIntegrationId({ integrationId, userId }) {
92
- const integrationRecord = await IntegrationHelper.getIntegrationById(
93
- integrationId
94
- );
95
- if (!integrationRecord) {
96
- throw new Error(
97
- `No integration found by the ID of ${integrationId}`
98
- );
99
- }
100
-
101
- if (!userId) {
102
- userId = integrationRecord.user._id.toString();
103
- } else if (userId.toString() !== integrationRecord.user.toString()) {
104
- throw new Error(
105
- `Integration ${integrationId
106
- } does not belong to User ${userId}, ${integrationRecord.user.toString()}`
107
- );
108
- }
109
-
110
- // getIntegrationClassByType is only used here
111
- const integrationClass = this.getIntegrationClassByType(
112
- integrationRecord.config.type
113
- );
114
-
115
- // here we should instantiate an Integration Domain class along with the integration record and it should happen in a use case class
116
- // Actually, this integrationClass is a subclass of IntegrationBase.
117
- const instance = new integrationClass({
118
- userId,
119
- integrationId,
120
- });
121
-
122
- if (
123
- integrationRecord.entityReference &&
124
- Object.keys(integrationRecord.entityReference) > 0
125
- ) {
126
- // Use the specified entityReference to find the modules and load them according to their key
127
- // entityReference will be a map of entityIds with their corresponding desired key
128
- for (const [entityId, key] of Object.entries(
129
- integrationRecord.entityReference
130
- )) {
131
- const moduleInstance =
132
- await this.moduleFactory.getModuleInstanceFromEntityId(
133
- entityId,
134
- integrationRecord.user
135
- );
136
- instance[key] = moduleInstance;
137
- }
138
- }
139
-
140
- instance.record = integrationRecord;
141
-
142
- try {
143
- const additionalUserActions =
144
- await instance.loadDynamicUserActions();
145
- instance.events = { ...instance.events, ...additionalUserActions };
146
- } catch (e) {
147
- instance.record.status = 'ERROR';
148
- instance.record.messages.errors.push(e);
149
- await instance.record.save();
150
- }
151
- // Register all of the event handlers
152
-
153
- await instance.registerEventHandlers();
154
- return instance;
155
- }
156
-
157
- async createIntegration(entities, userId, config) {
158
- const integrationRecord = await IntegrationModel.create({
159
- entities: entities,
160
- user: userId,
161
- config,
162
- version: '0.0.0',
163
- });
164
- return await this.getInstanceFromIntegrationId({
165
- integrationId: integrationRecord.id,
166
- userId,
167
- });
168
- }
169
-
170
- async getFormattedIntegration(integrationRecord) {
171
- const integrationObj = {
172
- id: integrationRecord.id,
173
- status: integrationRecord.status,
174
- config: integrationRecord.config,
175
- entities: [],
176
- version: integrationRecord.version,
177
- messages: integrationRecord.messages,
178
- };
179
- for (const entityId of integrationRecord.entities) {
180
- // Only return non-internal fields. Leverages "select" and "options" to non-excepted fields and a pure object.
181
- const entity = await Entity.findById(
182
- entityId,
183
- '-createdAt -updatedAt -user -credentials -credential -_id -__t -__v',
184
- { lean: true }
185
- );
186
- integrationObj.entities.push({
187
- id: entityId,
188
- ...entity,
189
- });
190
- }
191
- return integrationObj;
192
- }
193
- }
194
-
195
- module.exports = { IntegrationFactory };