@friggframework/core 2.0.0--canary.397.3862908.0 → 2.0.0--canary.398.e2147f7.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.
Files changed (91) hide show
  1. package/handlers/app-handler-helpers.js +3 -0
  2. package/handlers/backend-utils.js +34 -35
  3. package/handlers/routers/auth.js +11 -14
  4. package/handlers/routers/integration-defined-routers.js +5 -8
  5. package/handlers/routers/middleware/loadUser.js +15 -0
  6. package/handlers/routers/middleware/requireLoggedInUser.js +12 -0
  7. package/handlers/routers/user.js +5 -25
  8. package/handlers/workers/integration-defined-workers.js +3 -6
  9. package/index.js +14 -1
  10. package/integrations/create-frigg-backend.js +31 -0
  11. package/integrations/index.js +5 -0
  12. package/integrations/integration-base.js +44 -42
  13. package/integrations/integration-factory.js +251 -0
  14. package/integrations/integration-router.js +171 -289
  15. package/integrations/integration-user.js +144 -0
  16. package/integrations/options.js +1 -1
  17. package/integrations/test/integration-base.test.js +6 -6
  18. package/module-plugin/auther.js +393 -0
  19. package/module-plugin/entity-manager.js +70 -0
  20. package/{modules → module-plugin}/index.js +6 -0
  21. package/module-plugin/module-factory.js +61 -0
  22. package/{modules → module-plugin}/test/mock-api/api.js +3 -8
  23. package/{modules → module-plugin}/test/mock-api/definition.js +8 -12
  24. package/package.json +5 -5
  25. package/types/integrations/index.d.ts +6 -2
  26. package/types/module-plugin/index.d.ts +21 -4
  27. package/credential/credential-repository.js +0 -42
  28. package/credential/use-cases/get-credential-for-user.js +0 -21
  29. package/credential/use-cases/update-authentication-status.js +0 -15
  30. package/handlers/app-definition-loader.js +0 -38
  31. package/integrations/integration-repository.js +0 -67
  32. package/integrations/integration.js +0 -233
  33. package/integrations/tests/doubles/dummy-integration-class.js +0 -11
  34. package/integrations/tests/doubles/test-integration-repository.js +0 -52
  35. package/integrations/tests/use-cases/create-integration.test.js +0 -27
  36. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -20
  37. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -29
  38. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -26
  39. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -30
  40. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -12
  41. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -15
  42. package/integrations/tests/use-cases/update-integration-status.test.js +0 -0
  43. package/integrations/tests/use-cases/update-integration.test.js +0 -24
  44. package/integrations/use-cases/create-integration.js +0 -57
  45. package/integrations/use-cases/delete-integration-for-user.js +0 -54
  46. package/integrations/use-cases/get-integration-for-user.js +0 -63
  47. package/integrations/use-cases/get-integration-instance.js +0 -73
  48. package/integrations/use-cases/get-integrations-for-user.js +0 -63
  49. package/integrations/use-cases/get-possible-integrations.js +0 -13
  50. package/integrations/use-cases/index.js +0 -11
  51. package/integrations/use-cases/update-integration-messages.js +0 -20
  52. package/integrations/use-cases/update-integration-status.js +0 -12
  53. package/integrations/use-cases/update-integration.js +0 -81
  54. package/integrations/utils/map-integration-dto.js +0 -36
  55. package/modules/module-factory.js +0 -54
  56. package/modules/module-repository.js +0 -72
  57. package/modules/module.js +0 -251
  58. package/modules/tests/doubles/test-module-factory.js +0 -16
  59. package/modules/tests/doubles/test-module-repository.js +0 -19
  60. package/modules/use-cases/get-entities-for-user.js +0 -32
  61. package/modules/use-cases/get-entity-options-by-id.js +0 -58
  62. package/modules/use-cases/get-entity-options-by-type.js +0 -34
  63. package/modules/use-cases/get-module-instance-from-type.js +0 -31
  64. package/modules/use-cases/get-module.js +0 -56
  65. package/modules/use-cases/refresh-entity-options.js +0 -58
  66. package/modules/use-cases/test-module-auth.js +0 -54
  67. package/modules/utils/map-module-dto.js +0 -18
  68. package/user/tests/doubles/test-user-repository.js +0 -72
  69. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  70. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  71. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  72. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  73. package/user/tests/use-cases/login-user.test.js +0 -140
  74. package/user/use-cases/create-individual-user.js +0 -61
  75. package/user/use-cases/create-organization-user.js +0 -47
  76. package/user/use-cases/create-token-for-user-id.js +0 -30
  77. package/user/use-cases/get-user-from-bearer-token.js +0 -77
  78. package/user/use-cases/login-user.js +0 -122
  79. package/user/user-repository.js +0 -62
  80. package/user/user.js +0 -77
  81. /package/{modules → module-plugin}/ModuleConstants.js +0 -0
  82. /package/{modules → module-plugin}/credential.js +0 -0
  83. /package/{modules → module-plugin}/entity.js +0 -0
  84. /package/{modules → module-plugin}/manager.js +0 -0
  85. /package/{modules → module-plugin}/requester/api-key.js +0 -0
  86. /package/{modules → module-plugin}/requester/basic.js +0 -0
  87. /package/{modules → module-plugin}/requester/oauth-2.js +0 -0
  88. /package/{modules → module-plugin}/requester/requester.js +0 -0
  89. /package/{modules → module-plugin}/requester/requester.test.js +0 -0
  90. /package/{modules → module-plugin}/test/auther.test.js +0 -0
  91. /package/{modules → module-plugin}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,61 @@
1
+ const { Entity } = require('./entity');
2
+ const { Auther } = require('./auther');
3
+
4
+ class ModuleFactory {
5
+ constructor(...params) {
6
+ this.moduleDefinitions = params;
7
+ this.moduleTypes = this.moduleDefinitions.map((def) => def.moduleName);
8
+ }
9
+
10
+ async getEntitiesForUser(userId) {
11
+ let results = [];
12
+ for (const moduleDefinition of this.moduleDefinitions) {
13
+ const moduleInstance = await Auther.getInstance({
14
+ userId,
15
+ definition: moduleDefinition,
16
+ });
17
+ const list = await moduleInstance.getEntitiesForUserId(userId);
18
+ results.push(...list);
19
+ }
20
+ return results;
21
+ }
22
+
23
+ checkIsValidType(entityType) {
24
+ return this.moduleTypes.includes(entityType);
25
+ }
26
+
27
+ getModuleDefinitionFromTypeName(typeName) {
28
+ return;
29
+ }
30
+
31
+ async getModuleInstanceFromEntityId(entityId, userId) {
32
+ const entity = await Entity.findById(entityId);
33
+ const moduleDefinition = this.moduleDefinitions.find(
34
+ (def) =>
35
+ entity.toJSON()['__t'] ===
36
+ Auther.getEntityModelFromDefinition(def).modelName
37
+ );
38
+ if (!moduleDefinition) {
39
+ throw new Error(
40
+ 'Module definition not found for entity type: ' + entity['__t']
41
+ );
42
+ }
43
+ return await Auther.getInstance({
44
+ userId,
45
+ entityId,
46
+ definition: moduleDefinition,
47
+ });
48
+ }
49
+
50
+ async getInstanceFromTypeName(typeName, userId) {
51
+ const moduleDefinition = this.moduleDefinitions.find(
52
+ (def) => def.getName() === typeName
53
+ );
54
+ return await Auther.getInstance({
55
+ userId,
56
+ definition: moduleDefinition,
57
+ });
58
+ }
59
+ }
60
+
61
+ module.exports = { ModuleFactory };
@@ -1,5 +1,5 @@
1
- const { get } = require('../../../assertions');
2
- const { OAuth2Requester } = require('../..');
1
+ const { get } = require('../../assertions');
2
+ const { OAuth2Requester } = require('../../module-plugin');
3
3
 
4
4
  class Api extends OAuth2Requester {
5
5
  constructor(params) {
@@ -23,12 +23,7 @@ class Api extends OAuth2Requester {
23
23
  return this.authorizationUri;
24
24
  }
25
25
 
26
- getAuthorizationRequirements() {
27
- return {
28
- url: this.getAuthUri(),
29
- type: 'oauth2',
30
- };
31
- }
26
+
32
27
  }
33
28
 
34
29
  module.exports = { Api };
@@ -1,23 +1,19 @@
1
1
  require('dotenv').config();
2
- const { Api } = require('./api');
3
- const { get } = require('../../../assertions');
4
- const config = { name: 'anapi' }
2
+ const {Api} = require('./api');
3
+ const {get} = require('../../assertions');
4
+ const config = {name: 'anapi'}
5
5
 
6
6
  const Definition = {
7
7
  API: Api,
8
- getAuthorizationRequirements: () => ({
9
- url: 'http://localhost:3000/redirect/anapi',
10
- type: 'oauth2',
11
- }),
12
- getName: function () { return config.name },
8
+ getName: function() {return config.name},
13
9
  moduleName: config.name,
14
10
  modelName: 'AnApi',
15
11
  requiredAuthMethods: {
16
- getToken: async function (api, params) {
12
+ getToken: async function(api, params){
17
13
  const code = get(params.data, 'code');
18
14
  return api.getTokenFromCode(code);
19
15
  },
20
- getEntityDetails: async function (api, callbackParams, tokenResponse, userId) {
16
+ getEntityDetails: async function(api, callbackParams, tokenResponse, userId) {
21
17
  const userDetails = await api.getUserDetails();
22
18
  return {
23
19
  identifiers: { externalId: userDetails.portalId, user: userId },
@@ -30,14 +26,14 @@ const Definition = {
30
26
  ],
31
27
  entity: [],
32
28
  },
33
- getCredentialDetails: async function (api, userId) {
29
+ getCredentialDetails: async function(api, userId) {
34
30
  const userDetails = await api.getUserDetails();
35
31
  return {
36
32
  identifiers: { externalId: userDetails.portalId, user: userId },
37
33
  details: {}
38
34
  };
39
35
  },
40
- testAuthRequest: async function (api) {
36
+ testAuthRequest: async function(api){
41
37
  return api.getUserDetails()
42
38
  },
43
39
  },
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.397.3862908.0",
4
+ "version": "2.0.0--canary.398.e2147f7.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.397.3862908.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.397.3862908.0",
27
- "@friggframework/test": "2.0.0--canary.397.3862908.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.398.e2147f7.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.398.e2147f7.0",
27
+ "@friggframework/test": "2.0.0--canary.398.e2147f7.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": "3862908989603188a6c3a404d456f4349c84e54b"
56
+ "gitHead": "e2147f7f03122a5daa917429ba610c1a7c6ef819"
57
57
  }
@@ -1,6 +1,7 @@
1
1
  declare module "@friggframework/integrations" {
2
2
  import { Delegate, IFriggDelegate } from "@friggframework/core";
3
3
  import { Model } from "mongoose";
4
+ import { EntityManager } from "@friggframework/module-plugin";
4
5
 
5
6
  export class Integration extends Model {
6
7
  entities: any[];
@@ -18,7 +19,8 @@ declare module "@friggframework/integrations" {
18
19
 
19
20
  export class IntegrationManager
20
21
  extends Delegate
21
- implements IFriggIntegrationManager {
22
+ implements IFriggIntegrationManager
23
+ {
22
24
  integration: Integration;
23
25
  primaryInstance: any;
24
26
  targetInstance: any;
@@ -54,6 +56,7 @@ declare module "@friggframework/integrations" {
54
56
  entities: { id: string; user: any },
55
57
  userId: string,
56
58
  config: any,
59
+ EntityManager: EntityManager
57
60
  ): Promise<any>;
58
61
 
59
62
  static getFormattedIntegration(
@@ -113,7 +116,8 @@ declare module "@friggframework/integrations" {
113
116
  }
114
117
 
115
118
  export class IntegrationConfigManager
116
- implements IFriggIntegrationConfigManager {
119
+ implements IFriggIntegrationConfigManager
120
+ {
117
121
  options: IntegrationOptions[];
118
122
  primary: any;
119
123
 
@@ -9,7 +9,21 @@ declare module "@friggframework/module-plugin" {
9
9
  externalId: string;
10
10
  }
11
11
 
12
- interface IFriggEntityManager { }
12
+ export class EntityManager implements IFriggEntityManager {
13
+ static primaryEntityClass: any;
14
+ static entityManagerClasses: any[];
15
+ static entityTypes: string[];
16
+ static getEntitiesForUser(userId: string): Promise<any[]>;
17
+ static checkIsValidType(entityType: string): boolean;
18
+ static getEntityManagerClass(entityType?: string): any;
19
+
20
+ static getEntityManagerInstanceFromEntityId(
21
+ entityId: string,
22
+ userId: string
23
+ ): Promise<any>;
24
+ }
25
+
26
+ interface IFriggEntityManager {}
13
27
 
14
28
  export class Entity extends Model {
15
29
  credentialId: string;
@@ -124,7 +138,8 @@ declare module "@friggframework/module-plugin" {
124
138
 
125
139
  export class ApiKeyRequester
126
140
  extends Requester
127
- implements IFriggApiKeyRequester {
141
+ implements IFriggApiKeyRequester
142
+ {
128
143
  API_KEY_NAME: string;
129
144
  API_KEY_VALUE: any;
130
145
 
@@ -145,7 +160,8 @@ declare module "@friggframework/module-plugin" {
145
160
 
146
161
  export class BasicAuthRequester
147
162
  extends Requester
148
- implements IFriggBasicAuthRequester {
163
+ implements IFriggBasicAuthRequester
164
+ {
149
165
  password: string;
150
166
  username: string;
151
167
 
@@ -173,7 +189,8 @@ declare module "@friggframework/module-plugin" {
173
189
 
174
190
  export class OAuth2Requester
175
191
  extends Requester
176
- implements IFriggOAuth2Requester {
192
+ implements IFriggOAuth2Requester
193
+ {
177
194
  DLGT_TOKEN_DEAUTHORIZED: string;
178
195
  DLGT_TOKEN_UPDATE: string;
179
196
  accessTokenExpire: any;
@@ -1,42 +0,0 @@
1
- const { Credential } = require('../modules');
2
-
3
- class CredentialRepository {
4
- async findCredentialById(id) {
5
- return Credential.findById(id);
6
- }
7
-
8
- async updateAuthenticationStatus(credentialId, authIsValid) {
9
- return Credential.updateOne({ _id: credentialId }, { $set: { auth_is_valid: authIsValid } });
10
- }
11
-
12
- /**
13
- * Permanently remove a credential document.
14
- * @param {string} credentialId
15
- * @returns {Promise<import('mongoose').DeleteResult>}
16
- */
17
- async deleteCredentialById(credentialId) {
18
- return Credential.deleteOne({ _id: credentialId });
19
- }
20
-
21
- /**
22
- * Create a new credential or update an existing one matching the identifiers.
23
- * `credentialDetails` format: { identifiers: { ... }, details: { ... } }
24
- * Identifiers are used in the query filter; details are merged into the document.
25
- * @param {{identifiers: Object, details: Object}} credentialDetails
26
- * @returns {Promise<Object>} The persisted credential (lean object)
27
- */
28
- async upsertCredential(credentialDetails) {
29
- const { identifiers, details } = credentialDetails;
30
- if (!identifiers) throw new Error('identifiers required to upsert credential');
31
-
32
- const query = { ...identifiers };
33
-
34
- const update = { $set: { ...details } };
35
-
36
- const options = { upsert: true, new: true, setDefaultsOnInsert: true, lean: true };
37
-
38
- return Credential.findOneAndUpdate(query, update, options);
39
- }
40
- }
41
-
42
- module.exports = { CredentialRepository };
@@ -1,21 +0,0 @@
1
- class GetCredentialForUser {
2
- constructor({ credentialRepository }) {
3
- this.credentialRepository = credentialRepository;
4
- }
5
-
6
- async execute(credentialId, userId) {
7
- const credential = await this.credentialRepository.findCredentialById(credentialId);
8
-
9
- if (!credential) {
10
- throw new Error(`Credential with id ${credentialId} not found`);
11
- }
12
-
13
- if (credential.user.toString() !== userId.toString()) {
14
- throw new Error(`Credential ${credentialId} does not belong to user ${userId}`);
15
- }
16
-
17
- return credential;
18
- }
19
- }
20
-
21
- module.exports = { GetCredentialForUser };
@@ -1,15 +0,0 @@
1
- class UpdateAuthenticationStatus {
2
- constructor({ credentialRepository }) {
3
- this.credentialRepository = credentialRepository;
4
- }
5
-
6
- /**
7
- * @param {string} credentialId
8
- * @param {boolean} authIsValid
9
- */
10
- async execute(credentialId, authIsValid) {
11
- await this.credentialRepository.updateAuthenticationStatus(credentialId, authIsValid);
12
- }
13
- }
14
-
15
- module.exports = { UpdateAuthenticationStatus };
@@ -1,38 +0,0 @@
1
- const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
2
- const path = require('node:path');
3
- const fs = require('fs-extra');
4
-
5
- /**
6
- * Loads the App definition from the nearest backend package
7
- * @function loadAppDefinition
8
- * @description Searches for the nearest backend package.json, loads the corresponding index.js file,
9
- * and extracts the application definition containing integrations and user configuration.
10
- * @returns {{integrations: Array<object>, userConfig: object | null}} An object containing the application definition.
11
- * @throws {Error} Throws error if backend package.json cannot be found.
12
- * @throws {Error} Throws error if index.js file cannot be found in the backend directory.
13
- * @example
14
- * const { integrations, userConfig } = loadAppDefinition();
15
- * console.log(`Found ${integrations.length} integrations`);
16
- */
17
- function loadAppDefinition() {
18
- const backendPath = findNearestBackendPackageJson();
19
- if (!backendPath) {
20
- throw new Error('Could not find backend package.json');
21
- }
22
-
23
- const backendDir = path.dirname(backendPath);
24
- const backendFilePath = path.join(backendDir, 'index.js');
25
- if (!fs.existsSync(backendFilePath)) {
26
- throw new Error('Could not find index.js');
27
- }
28
-
29
- const backendJsFile = require(backendFilePath);
30
- const appDefinition = backendJsFile.Definition;
31
-
32
- const { integrations = [], user: userConfig = null } = appDefinition;
33
- return { integrations, userConfig };
34
- }
35
-
36
- module.exports = {
37
- loadAppDefinition,
38
- };
@@ -1,67 +0,0 @@
1
- const { IntegrationModel } = require('./integration-model');
2
-
3
- class IntegrationRepository {
4
- async findIntegrationsByUserId(userId) {
5
- const integrationRecords = await IntegrationModel.find({ user: userId }, '', { lean: true }).populate('entities');
6
- return integrationRecords.map(integrationRecord => ({
7
- id: integrationRecord._id,
8
- entitiesIds: integrationRecord.entities.map(e => e._id),
9
- userId: integrationRecord.user.toString(),
10
- config: integrationRecord.config,
11
- version: integrationRecord.version,
12
- status: integrationRecord.status,
13
- messages: integrationRecord.messages,
14
- }));
15
- }
16
-
17
- async deleteIntegrationById(integrationId) {
18
- return IntegrationModel.deleteOne({ _id: integrationId });
19
- }
20
-
21
- async findIntegrationById(id) {
22
- const integrationRecord = await IntegrationModel.findById(id, '', { lean: true }).populate('entities');
23
- return {
24
- id: integrationRecord._id,
25
- entitiesIds: integrationRecord.entities.map(e => e._id),
26
- userId: integrationRecord.user.toString(),
27
- config: integrationRecord.config,
28
- version: integrationRecord.version,
29
- status: integrationRecord.status,
30
- messages: integrationRecord.messages,
31
- }
32
- }
33
-
34
- async updateIntegrationStatus(integrationId, status) {
35
- const integrationRecord = await IntegrationModel.updateOne({ _id: integrationId }, { status });
36
- return integrationRecord.acknowledged;
37
- }
38
-
39
- async updateIntegrationMessages(integrationId, messageType, messageTitle, messageBody, messageTimestamp) {
40
- const integrationRecord = await IntegrationModel.updateOne(
41
- { _id: integrationId },
42
- { $push: { [`messages.${messageType}`]: { title: messageTitle, message: messageBody, timestamp: messageTimestamp } } }
43
- );
44
- return integrationRecord.acknowledged;
45
- }
46
-
47
- async createIntegration(entities, userId, config) {
48
- const integrationRecord = await IntegrationModel.create({
49
- entities: entities,
50
- user: userId,
51
- config,
52
- version: '0.0.0',
53
- });
54
-
55
- return {
56
- id: integrationRecord._id,
57
- entitiesIds: integrationRecord.entities.map(e => e._id),
58
- userId: integrationRecord.user.toString(),
59
- config: integrationRecord.config,
60
- version: integrationRecord.version,
61
- status: integrationRecord.status,
62
- messages: integrationRecord.messages,
63
- };
64
- }
65
- }
66
-
67
- module.exports = { IntegrationRepository };
@@ -1,233 +0,0 @@
1
- const { Options } = require('./options');
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
-
35
- /**
36
- * Integration Domain Entity
37
- * Represents a configured integration with its data and behavior
38
- * Uses the strategy pattern to delegate behavior to the integration class
39
- * This is the main class that is used to interact with integrations
40
- */
41
- class Integration {
42
- constructor({
43
- id,
44
- userId,
45
- entities,
46
- config,
47
- status,
48
- version,
49
- messages,
50
- integrationClass,
51
- modules = {}
52
- }) {
53
- // Data from record
54
- this.id = id;
55
- this.userId = userId;
56
- this.entities = entities;
57
- this.config = config;
58
- this.status = status;
59
- this.version = version;
60
- this.messages = messages;
61
-
62
- // Integration behavior (strategy pattern)
63
- this.integrationClass = integrationClass;
64
-
65
- // Preserve the provided Module instances (array). We'll attach them
66
- // as keyed modules after the behaviour instance is created so that
67
- // `this.hubspot`, `this.salesforce`, etc. reference the fully-
68
- // initialised objects with credentials.
69
- this._moduleInstances = Array.isArray(modules) ? modules : [];
70
-
71
- // Normalised map of modules keyed by module name (filled later)
72
- this.modules = {};
73
-
74
- // Initialize basic behavior (sync parts only)
75
- this._initializeBasicBehavior();
76
-
77
- // --- Behaviour delegation via Proxy --------------------------------
78
- // The Proxy merges the *data layer* (this wrapper) with the *behaviour
79
- // layer* (custom IntegrationBase subclass). Consumers don't have to
80
- // know (or care) where a method/property is defined.
81
- return new Proxy(this, {
82
- get(target, prop) {
83
- // First, check if property exists on Integration entity
84
- if (prop in target) {
85
- return target[prop];
86
- }
87
-
88
- // Then, check if it exists on the behavior instance
89
- if (target.behavior && prop in target.behavior) {
90
- const value = target.behavior[prop];
91
-
92
- // If it's a function, bind the context to the Integration entity
93
- if (typeof value === 'function') {
94
- return value.bind(target);
95
- }
96
-
97
- return value;
98
- }
99
-
100
- // Return undefined for non-existent properties
101
- return undefined;
102
- }
103
- });
104
- }
105
-
106
- _initializeBasicBehavior() {
107
- // Initialize basic behavior (sync parts only)
108
- if (this.integrationClass) {
109
- // Create instance for behavior delegation
110
- this.behavior = new this.integrationClass({
111
- userId: this.userId,
112
- integrationId: this.id
113
- });
114
-
115
- // Copy events
116
- this.events = this.behavior.events || {};
117
- this.defaultEvents = this.behavior.defaultEvents || {};
118
-
119
- // -----------------------------------------------------------------
120
- // Inject the real Module instances (with credentials) so that any
121
- // behaviour code accessing `this.<moduleName>.api` hits the
122
- // correctly authenticated requester created by ModuleFactory.
123
- // -----------------------------------------------------------------
124
- for (const mod of this._moduleInstances) {
125
- const key = typeof mod.getName === 'function' ? mod.getName() : mod.name;
126
- if (!key) continue;
127
- this.setModule(key, mod);
128
- }
129
-
130
- // `undefined` errors for methods like `loadDynamicUserActions` that
131
- // may be invoked inside default event-handlers.
132
- let proto = Object.getPrototypeOf(this.behavior);
133
- while (proto && proto !== Object.prototype) {
134
- for (const key of Object.getOwnPropertyNames(proto)) {
135
- if (key === 'constructor') continue;
136
- if (typeof proto[key] === 'function' && this[key] === undefined) {
137
- this[key] = proto[key].bind(this.behavior);
138
- }
139
- }
140
- proto = Object.getPrototypeOf(proto);
141
- }
142
- }
143
- }
144
-
145
- async initialize() {
146
- // Complete async initialization
147
- if (this.behavior) {
148
- // Load dynamic user actions
149
- try {
150
- const additionalUserActions = await this.loadDynamicUserActions();
151
- this.events = { ...this.events, ...additionalUserActions };
152
- } catch (e) {
153
- this.addError(e);
154
- }
155
-
156
- // Register event handlers
157
- await this.registerEventHandlers();
158
- }
159
- }
160
-
161
- // Core methods that should always be on Integration entity
162
- // These override any behavior methods with the same name
163
-
164
- getConfig() {
165
- return this.config;
166
- }
167
-
168
- // Module access helpers
169
- getModule(key) {
170
- return this.modules[key];
171
- }
172
-
173
- setModule(key, module) {
174
- this.modules[key] = module;
175
- // Also set on behavior for backward compatibility
176
- if (this.behavior) {
177
- this.behavior[key] = module;
178
- }
179
- }
180
-
181
- // State management
182
- addError(error) {
183
- if (!this.messages.errors) {
184
- this.messages.errors = [];
185
- }
186
- this.messages.errors.push(error);
187
- this.status = 'ERROR';
188
- }
189
-
190
- addWarning(warning) {
191
- if (!this.messages.warnings) {
192
- this.messages.warnings = [];
193
- }
194
- this.messages.warnings.push(warning);
195
- }
196
-
197
- // Domain methods
198
- isActive() {
199
- return this.status === 'ENABLED' || this.status === 'ACTIVE';
200
- }
201
-
202
- needsConfiguration() {
203
- return this.status === 'NEEDS_CONFIG';
204
- }
205
-
206
- hasErrors() {
207
- return this.status === 'ERROR';
208
- }
209
-
210
- belongsToUser(userId) {
211
- return this.userId.toString() === userId.toString();
212
- }
213
-
214
- // Get the underlying behavior instance (useful for debugging or special cases)
215
- getBehavior() {
216
- return this.behavior;
217
- }
218
-
219
- // Check if a method exists (either on entity or behavior)
220
- hasMethod(methodName) {
221
- return methodName in this || (this.behavior && methodName in this.behavior);
222
- }
223
-
224
- getOptionDetails() {
225
- const options = new Options({
226
- module: Object.values(this.integrationClass.Definition.modules)[0], // This is a placeholder until we revamp the frontend
227
- ...this.integrationClass.Definition,
228
- });
229
- return options.get();
230
- }
231
- }
232
-
233
- module.exports = { Integration };
@@ -1,11 +0,0 @@
1
- const { IntegrationBase } = require('../../integration-base');
2
-
3
- class DummyIntegration extends IntegrationBase {
4
- static Definition = {
5
- name: 'dummy',
6
- version: '1.0.0',
7
- modules: {},
8
- };
9
- }
10
-
11
- module.exports = { DummyIntegration };