@friggframework/core 2.0.0-next.41 → 2.0.0-next.43

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 (197) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +931 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +160 -0
  7. package/application/commands/integration-commands.test.js +123 -0
  8. package/application/commands/user-commands.js +213 -0
  9. package/application/index.js +69 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/create-handler.js +0 -6
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +301 -0
  15. package/credential/repositories/credential-repository-postgres.js +307 -0
  16. package/credential/repositories/credential-repository.js +307 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/config.js +117 -0
  20. package/database/encryption/README.md +683 -0
  21. package/database/encryption/encryption-integration.test.js +553 -0
  22. package/database/encryption/encryption-schema-registry.js +141 -0
  23. package/database/encryption/encryption-schema-registry.test.js +392 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/field-encryption-service.test.js +525 -0
  26. package/database/encryption/logger.js +79 -0
  27. package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
  28. package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
  29. package/database/encryption/postgres-relation-decryption.test.js +245 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/encryption/prisma-encryption-extension.test.js +439 -0
  32. package/database/index.js +25 -12
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +162 -0
  35. package/database/repositories/health-check-repository-factory.js +38 -0
  36. package/database/repositories/health-check-repository-interface.js +86 -0
  37. package/database/repositories/health-check-repository-mongodb.js +72 -0
  38. package/database/repositories/health-check-repository-postgres.js +75 -0
  39. package/database/repositories/health-check-repository.js +108 -0
  40. package/database/use-cases/check-database-health-use-case.js +34 -0
  41. package/database/use-cases/check-encryption-health-use-case.js +82 -0
  42. package/database/use-cases/test-encryption-use-case.js +252 -0
  43. package/encrypt/Cryptor.js +20 -152
  44. package/encrypt/index.js +1 -2
  45. package/encrypt/test-encrypt.js +0 -2
  46. package/handlers/app-definition-loader.js +38 -0
  47. package/handlers/app-handler-helpers.js +0 -3
  48. package/handlers/auth-flow.integration.test.js +147 -0
  49. package/handlers/backend-utils.js +25 -45
  50. package/handlers/integration-event-dispatcher.js +54 -0
  51. package/handlers/integration-event-dispatcher.test.js +141 -0
  52. package/handlers/routers/HEALTHCHECK.md +103 -1
  53. package/handlers/routers/auth.js +3 -14
  54. package/handlers/routers/health.js +63 -424
  55. package/handlers/routers/health.test.js +7 -0
  56. package/handlers/routers/integration-defined-routers.js +8 -5
  57. package/handlers/routers/user.js +27 -5
  58. package/handlers/routers/websocket.js +5 -3
  59. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  60. package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
  61. package/handlers/workers/integration-defined-workers.js +6 -3
  62. package/index.js +45 -22
  63. package/integrations/index.js +12 -10
  64. package/integrations/integration-base.js +224 -53
  65. package/integrations/integration-router.js +386 -178
  66. package/integrations/options.js +1 -1
  67. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  68. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  69. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  70. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  71. package/integrations/repositories/integration-mapping-repository.js +156 -0
  72. package/integrations/repositories/integration-repository-factory.js +44 -0
  73. package/integrations/repositories/integration-repository-interface.js +115 -0
  74. package/integrations/repositories/integration-repository-mongo.js +271 -0
  75. package/integrations/repositories/integration-repository-postgres.js +319 -0
  76. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  77. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  78. package/integrations/tests/use-cases/create-integration.test.js +131 -0
  79. package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
  80. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
  81. package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
  82. package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
  83. package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
  84. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  85. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  86. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  87. package/integrations/tests/use-cases/update-integration.test.js +141 -0
  88. package/integrations/use-cases/create-integration.js +83 -0
  89. package/integrations/use-cases/delete-integration-for-user.js +73 -0
  90. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  91. package/integrations/use-cases/get-integration-for-user.js +78 -0
  92. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  93. package/integrations/use-cases/get-integration-instance.js +83 -0
  94. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  95. package/integrations/use-cases/get-possible-integrations.js +27 -0
  96. package/integrations/use-cases/index.js +11 -0
  97. package/integrations/use-cases/load-integration-context-full.test.js +329 -0
  98. package/integrations/use-cases/load-integration-context.js +71 -0
  99. package/integrations/use-cases/load-integration-context.test.js +114 -0
  100. package/integrations/use-cases/update-integration-messages.js +44 -0
  101. package/integrations/use-cases/update-integration-status.js +32 -0
  102. package/integrations/use-cases/update-integration.js +93 -0
  103. package/integrations/utils/map-integration-dto.js +36 -0
  104. package/jest-global-setup-noop.js +3 -0
  105. package/jest-global-teardown-noop.js +3 -0
  106. package/{module-plugin → modules}/entity.js +1 -0
  107. package/{module-plugin → modules}/index.js +0 -8
  108. package/modules/module-factory.js +56 -0
  109. package/modules/module-hydration.test.js +205 -0
  110. package/modules/module.js +221 -0
  111. package/modules/repositories/module-repository-factory.js +33 -0
  112. package/modules/repositories/module-repository-interface.js +129 -0
  113. package/modules/repositories/module-repository-mongo.js +386 -0
  114. package/modules/repositories/module-repository-postgres.js +437 -0
  115. package/modules/repositories/module-repository.js +327 -0
  116. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  117. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  118. package/modules/tests/doubles/test-module-factory.js +16 -0
  119. package/modules/tests/doubles/test-module-repository.js +39 -0
  120. package/modules/use-cases/get-entities-for-user.js +32 -0
  121. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  122. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  123. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  124. package/modules/use-cases/get-module.js +56 -0
  125. package/modules/use-cases/process-authorization-callback.js +122 -0
  126. package/modules/use-cases/refresh-entity-options.js +59 -0
  127. package/modules/use-cases/test-module-auth.js +55 -0
  128. package/modules/utils/map-module-dto.js +18 -0
  129. package/package.json +14 -6
  130. package/prisma-mongodb/schema.prisma +318 -0
  131. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  132. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  133. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  134. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  135. package/prisma-postgresql/schema.prisma +300 -0
  136. package/syncs/manager.js +468 -443
  137. package/syncs/repositories/sync-repository-factory.js +38 -0
  138. package/syncs/repositories/sync-repository-interface.js +109 -0
  139. package/syncs/repositories/sync-repository-mongo.js +239 -0
  140. package/syncs/repositories/sync-repository-postgres.js +319 -0
  141. package/syncs/sync.js +0 -1
  142. package/token/repositories/token-repository-factory.js +33 -0
  143. package/token/repositories/token-repository-interface.js +131 -0
  144. package/token/repositories/token-repository-mongo.js +212 -0
  145. package/token/repositories/token-repository-postgres.js +257 -0
  146. package/token/repositories/token-repository.js +219 -0
  147. package/types/integrations/index.d.ts +2 -6
  148. package/types/module-plugin/index.d.ts +5 -57
  149. package/types/syncs/index.d.ts +0 -2
  150. package/user/repositories/user-repository-factory.js +46 -0
  151. package/user/repositories/user-repository-interface.js +198 -0
  152. package/user/repositories/user-repository-mongo.js +250 -0
  153. package/user/repositories/user-repository-postgres.js +311 -0
  154. package/user/tests/doubles/test-user-repository.js +72 -0
  155. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  156. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  157. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  158. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  159. package/user/tests/use-cases/login-user.test.js +140 -0
  160. package/user/use-cases/create-individual-user.js +61 -0
  161. package/user/use-cases/create-organization-user.js +47 -0
  162. package/user/use-cases/create-token-for-user-id.js +30 -0
  163. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  164. package/user/use-cases/login-user.js +122 -0
  165. package/user/user.js +77 -0
  166. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  167. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  168. package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
  169. package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
  170. package/websocket/repositories/websocket-connection-repository.js +160 -0
  171. package/database/models/State.js +0 -9
  172. package/database/models/Token.js +0 -70
  173. package/database/mongo.js +0 -171
  174. package/encrypt/Cryptor.test.js +0 -32
  175. package/encrypt/encrypt.js +0 -104
  176. package/encrypt/encrypt.test.js +0 -1069
  177. package/handlers/routers/middleware/loadUser.js +0 -15
  178. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  179. package/integrations/create-frigg-backend.js +0 -31
  180. package/integrations/integration-factory.js +0 -251
  181. package/integrations/integration-mapping.js +0 -43
  182. package/integrations/integration-model.js +0 -46
  183. package/integrations/integration-user.js +0 -144
  184. package/integrations/test/integration-base.test.js +0 -144
  185. package/module-plugin/auther.js +0 -393
  186. package/module-plugin/credential.js +0 -22
  187. package/module-plugin/entity-manager.js +0 -70
  188. package/module-plugin/manager.js +0 -169
  189. package/module-plugin/module-factory.js +0 -61
  190. package/module-plugin/test/auther.test.js +0 -97
  191. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  192. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  193. /package/{module-plugin → modules}/requester/basic.js +0 -0
  194. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  195. /package/{module-plugin → modules}/requester/requester.js +0 -0
  196. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  197. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,5 +1,7 @@
1
1
  const { createHandler } = require('@friggframework/core');
2
- const { WebsocketConnection } = require('@friggframework/core');
2
+ const { createWebsocketConnectionRepository } = require('../../database/websocket-connection-repository-factory');
3
+
4
+ const websocketConnectionRepository = createWebsocketConnectionRepository();
3
5
 
4
6
  const handleWebSocketConnection = async (event, context) => {
5
7
  // Handle different WebSocket events
@@ -8,7 +10,7 @@ const handleWebSocketConnection = async (event, context) => {
8
10
  // Handle new connection
9
11
  try {
10
12
  const connectionId = event.requestContext.connectionId;
11
- await WebsocketConnection.create({ connectionId });
13
+ await websocketConnectionRepository.createConnection(connectionId);
12
14
  console.log(`Stored new connection: ${connectionId}`);
13
15
  return { statusCode: 200, body: 'Connected.' };
14
16
  } catch (error) {
@@ -20,7 +22,7 @@ const handleWebSocketConnection = async (event, context) => {
20
22
  // Handle disconnection
21
23
  try {
22
24
  const connectionId = event.requestContext.connectionId;
23
- await WebsocketConnection.deleteOne({ connectionId });
25
+ await websocketConnectionRepository.deleteConnection(connectionId);
24
26
  console.log(`Removed connection: ${connectionId}`);
25
27
  return { statusCode: 200, body: 'Disconnected.' };
26
28
  } catch (error) {
@@ -0,0 +1,81 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ class CheckExternalApisHealthUseCase {
5
+ constructor({ apis = null } = {}) {
6
+ this.apis = apis || [
7
+ { name: 'github', url: 'https://api.github.com/status' },
8
+ { name: 'npm', url: 'https://registry.npmjs.org' },
9
+ ];
10
+ }
11
+
12
+ async execute() {
13
+ const results = await Promise.all(
14
+ this.apis.map((api) =>
15
+ this._checkExternalAPI(api.url).then((result) => ({
16
+ name: api.name,
17
+ ...result,
18
+ }))
19
+ )
20
+ );
21
+
22
+ const apiStatuses = {};
23
+ let allReachable = true;
24
+
25
+ results.forEach(({ name, ...checkResult }) => {
26
+ apiStatuses[name] = checkResult;
27
+ if (!checkResult.reachable) {
28
+ allReachable = false;
29
+ }
30
+ });
31
+
32
+ return { apiStatuses, allReachable };
33
+ }
34
+
35
+ _checkExternalAPI(url, timeout = 5000) {
36
+ return new Promise((resolve) => {
37
+ const protocol = url.startsWith('https:') ? https : http;
38
+ const startTime = Date.now();
39
+
40
+ try {
41
+ const request = protocol.get(url, { timeout }, (res) => {
42
+ const responseTime = Date.now() - startTime;
43
+ resolve({
44
+ status: 'healthy',
45
+ statusCode: res.statusCode,
46
+ responseTime,
47
+ reachable: res.statusCode < 500,
48
+ });
49
+ });
50
+
51
+ request.on('error', (error) => {
52
+ resolve({
53
+ status: 'unhealthy',
54
+ error: error.message,
55
+ responseTime: Date.now() - startTime,
56
+ reachable: false,
57
+ });
58
+ });
59
+
60
+ request.on('timeout', () => {
61
+ request.destroy();
62
+ resolve({
63
+ status: 'timeout',
64
+ error: 'Request timeout',
65
+ responseTime: timeout,
66
+ reachable: false,
67
+ });
68
+ });
69
+ } catch (error) {
70
+ resolve({
71
+ status: 'error',
72
+ error: error.message,
73
+ responseTime: Date.now() - startTime,
74
+ reachable: false,
75
+ });
76
+ }
77
+ });
78
+ }
79
+ }
80
+
81
+ module.exports = { CheckExternalApisHealthUseCase };
@@ -0,0 +1,32 @@
1
+ class CheckIntegrationsHealthUseCase {
2
+ constructor({ moduleFactory, integrationFactory }) {
3
+ this.moduleFactory = moduleFactory;
4
+ this.integrationFactory = integrationFactory;
5
+ }
6
+
7
+ execute() {
8
+ const moduleTypes = Array.isArray(this.moduleFactory.moduleTypes)
9
+ ? this.moduleFactory.moduleTypes
10
+ : [];
11
+
12
+ const integrationTypes = Array.isArray(
13
+ this.integrationFactory.integrationTypes
14
+ )
15
+ ? this.integrationFactory.integrationTypes
16
+ : [];
17
+
18
+ return {
19
+ status: 'healthy',
20
+ modules: {
21
+ count: moduleTypes.length,
22
+ available: moduleTypes,
23
+ },
24
+ integrations: {
25
+ count: integrationTypes.length,
26
+ available: integrationTypes,
27
+ },
28
+ };
29
+ }
30
+ }
31
+
32
+ module.exports = { CheckIntegrationsHealthUseCase };
@@ -1,9 +1,12 @@
1
1
  const { createHandler } = require('@friggframework/core');
2
- const { integrationFactory, createQueueWorker } = require('../backend-utils');
2
+ const { loadAppDefinition } = require('../app-definition-loader');
3
+ const { createQueueWorker } = require('../backend-utils');
3
4
 
4
5
  const handlers = {};
5
- integrationFactory.integrationClasses.forEach((IntegrationClass) => {
6
- const defaultQueueWorker = createQueueWorker(IntegrationClass, integrationFactory);
6
+ const { integrations: integrationClasses } = loadAppDefinition();
7
+
8
+ integrationClasses.forEach((IntegrationClass) => {
9
+ const defaultQueueWorker = createQueueWorker(IntegrationClass);
7
10
 
8
11
  handlers[`${IntegrationClass.Definition.name}`] = {
9
12
  queueWorker: createHandler({
package/index.js CHANGED
@@ -24,8 +24,26 @@ const {
24
24
  Token,
25
25
  UserModel,
26
26
  WebsocketConnection,
27
+ prisma,
28
+ TokenRepository,
29
+ WebsocketConnectionRepository,
27
30
  } = require('./database/index');
28
- const { Encrypt, Cryptor } = require('./encrypt/encrypt');
31
+ const {
32
+ createUserRepository,
33
+ UserRepositoryMongo,
34
+ UserRepositoryPostgres,
35
+ } = require('./user/repositories/user-repository-factory');
36
+
37
+ const {
38
+ CredentialRepository,
39
+ } = require('./credential/repositories/credential-repository');
40
+ const {
41
+ ModuleRepository,
42
+ } = require('./modules/repositories/module-repository');
43
+ const {
44
+ IntegrationMappingRepository,
45
+ } = require('./integrations/repositories/integration-mapping-repository');
46
+ const { Cryptor } = require('./encrypt');
29
47
  const {
30
48
  BaseError,
31
49
  FetchError,
@@ -35,30 +53,25 @@ const {
35
53
  } = require('./errors/index');
36
54
  const {
37
55
  IntegrationBase,
38
- IntegrationModel,
39
56
  Options,
40
- IntegrationMapping,
41
- IntegrationFactory,
42
- IntegrationHelper,
43
57
  createIntegrationRouter,
44
58
  checkRequiredParams,
45
- createFriggBackend,
59
+ getModulesDefinitionFromIntegrationClasses,
60
+ LoadIntegrationContextUseCase,
46
61
  } = require('./integrations/index');
47
62
  const { TimeoutCatcher } = require('./lambda/index');
48
63
  const { debug, initDebugLog, flushDebugLog } = require('./logs/index');
49
64
  const {
50
65
  Credential,
51
- EntityManager,
52
66
  Entity,
53
- ModuleManager,
54
67
  ApiKeyRequester,
55
68
  BasicAuthRequester,
56
69
  OAuth2Requester,
57
70
  Requester,
58
71
  ModuleConstants,
59
72
  ModuleFactory,
60
- Auther,
61
- } = require('./module-plugin/index');
73
+ } = require('./modules/index');
74
+ const application = require('./application');
62
75
  const utils = require('./utils');
63
76
 
64
77
  // const {Sync } = require('./syncs/model');
@@ -92,9 +105,15 @@ module.exports = {
92
105
  Token,
93
106
  UserModel,
94
107
  WebsocketConnection,
95
-
96
- // encrypt
97
- Encrypt,
108
+ prisma,
109
+ TokenRepository,
110
+ WebsocketConnectionRepository,
111
+ createUserRepository,
112
+ UserRepositoryMongo,
113
+ UserRepositoryPostgres,
114
+ CredentialRepository,
115
+ ModuleRepository,
116
+ IntegrationMappingRepository,
98
117
  Cryptor,
99
118
 
100
119
  // errors
@@ -106,14 +125,22 @@ module.exports = {
106
125
 
107
126
  // integrations
108
127
  IntegrationBase,
109
- IntegrationModel,
110
128
  Options,
111
- IntegrationMapping,
112
- IntegrationFactory,
113
- IntegrationHelper,
114
129
  checkRequiredParams,
115
130
  createIntegrationRouter,
116
- createFriggBackend,
131
+ getModulesDefinitionFromIntegrationClasses,
132
+ LoadIntegrationContextUseCase,
133
+
134
+ // application - Command factories for integration developers
135
+ application,
136
+ createFriggCommands: application.createFriggCommands,
137
+ createIntegrationCommands: application.createIntegrationCommands,
138
+ createUserCommands: application.createUserCommands,
139
+ createEntityCommands: application.createEntityCommands,
140
+ createCredentialCommands: application.createCredentialCommands,
141
+ findIntegrationContextByExternalEntityId:
142
+ application.findIntegrationContextByExternalEntityId,
143
+ integrationCommands: application.integrationCommands,
117
144
 
118
145
  // lambda
119
146
  TimeoutCatcher,
@@ -125,17 +152,13 @@ module.exports = {
125
152
 
126
153
  // module plugin
127
154
  Credential,
128
- EntityManager,
129
155
  Entity,
130
- ModuleManager,
131
156
  ApiKeyRequester,
132
157
  BasicAuthRequester,
133
158
  OAuth2Requester,
134
159
  Requester,
135
160
  ModuleConstants,
136
161
  ModuleFactory,
137
- Auther,
138
-
139
162
  // queues
140
163
  QueuerUtil,
141
164
 
@@ -1,19 +1,21 @@
1
1
  const { IntegrationBase } = require('./integration-base');
2
- const { IntegrationModel } = require('./integration-model');
3
2
  const { Options } = require('./options');
4
- const { IntegrationMapping } = require('./integration-mapping');
5
- const { IntegrationFactory, IntegrationHelper } = require('./integration-factory');
6
- const { createIntegrationRouter, checkRequiredParams } = require('./integration-router');
7
- const { createFriggBackend } = require('./create-frigg-backend');
3
+ const {
4
+ createIntegrationRouter,
5
+ checkRequiredParams,
6
+ } = require('./integration-router');
7
+ const {
8
+ getModulesDefinitionFromIntegrationClasses,
9
+ } = require('./utils/map-integration-dto');
10
+ const {
11
+ LoadIntegrationContextUseCase,
12
+ } = require('./use-cases/load-integration-context');
8
13
 
9
14
  module.exports = {
10
15
  IntegrationBase,
11
- IntegrationModel,
12
16
  Options,
13
- IntegrationMapping,
14
- IntegrationFactory,
15
- IntegrationHelper,
16
17
  createIntegrationRouter,
17
18
  checkRequiredParams,
18
- createFriggBackend
19
+ getModulesDefinitionFromIntegrationClasses,
20
+ LoadIntegrationContextUseCase,
19
21
  };
@@ -1,5 +1,17 @@
1
- const { IntegrationMapping } = require('./integration-mapping');
1
+ const {
2
+ createIntegrationMappingRepository,
3
+ } = require('./repositories/integration-mapping-repository-factory');
2
4
  const { Options } = require('./options');
5
+ const {
6
+ UpdateIntegrationStatus,
7
+ } = require('./use-cases/update-integration-status');
8
+ const {
9
+ createIntegrationRepository,
10
+ } = require('./repositories/integration-repository-factory');
11
+ const {
12
+ UpdateIntegrationMessages,
13
+ } = require('./use-cases/update-integration-messages');
14
+
3
15
  const constantsToBeMigrated = {
4
16
  defaultEvents: {
5
17
  ON_CREATE: 'ON_CREATE',
@@ -19,6 +31,16 @@ const constantsToBeMigrated = {
19
31
  };
20
32
 
21
33
  class IntegrationBase {
34
+ // todo: maybe we can pass this as Dependency Injection in the sub-class constructor
35
+ integrationRepository = createIntegrationRepository();
36
+ integrationMappingRepository = createIntegrationMappingRepository();
37
+ updateIntegrationStatus = new UpdateIntegrationStatus({
38
+ integrationRepository: this.integrationRepository,
39
+ });
40
+ updateIntegrationMessages = new UpdateIntegrationMessages({
41
+ integrationRepository: this.integrationRepository,
42
+ });
43
+
22
44
  static getOptionDetails() {
23
45
  const options = new Options({
24
46
  module: Object.values(this.Definition.modules)[0], // This is a placeholder until we revamp the frontend
@@ -26,6 +48,7 @@ class IntegrationBase {
26
48
  });
27
49
  return options.get();
28
50
  }
51
+
29
52
  /**
30
53
  * CHILDREN SHOULD SPECIFY A DEFINITION FOR THE INTEGRATION
31
54
  */
@@ -50,29 +73,30 @@ class IntegrationBase {
50
73
  static getCurrentVersion() {
51
74
  return this.Definition.version;
52
75
  }
53
- loadModules() {
54
- // Load all the modules defined in Definition.modules
55
- const moduleNames = Object.keys(this.constructor.Definition.modules);
56
- for (const moduleName of moduleNames) {
57
- const { definition } =
58
- this.constructor.Definition.modules[moduleName];
59
- if (typeof definition.API === 'function') {
60
- this[moduleName] = { api: new definition.API() };
61
- } else {
62
- throw new Error(
63
- `Module ${moduleName} must be a function that extends IntegrationModule`
64
- );
65
- }
76
+
77
+ // REMOVED: registerEventHandlers() - Event handling is now done by IntegrationEventDispatcher
78
+
79
+ constructor(params = {}) {
80
+ this.modules = {};
81
+ this.events = this.events || {};
82
+ this.messages = { errors: [], warnings: [] };
83
+ this._isHydrated = false;
84
+
85
+ if (params && Object.keys(params).length > 0) {
86
+ this.setIntegrationRecord({
87
+ record: {
88
+ id: params.id,
89
+ userId: params.userId,
90
+ entities: params.entities,
91
+ config: params.config,
92
+ status: params.status,
93
+ version: params.version,
94
+ messages: params.messages,
95
+ },
96
+ modules: params.modules || [],
97
+ });
66
98
  }
67
- }
68
- registerEventHandlers() {
69
- this.on = {
70
- ...this.defaultEvents,
71
- ...this.events,
72
- };
73
- }
74
99
 
75
- constructor(params) {
76
100
  this.defaultEvents = {
77
101
  [constantsToBeMigrated.defaultEvents.ON_CREATE]: {
78
102
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
@@ -107,21 +131,90 @@ class IntegrationBase {
107
131
  handler: this.refreshActionOptions,
108
132
  },
109
133
  };
110
- this.loadModules();
111
134
  }
112
135
 
113
- async send(event, object) {
114
- if (!this.on[event]) {
115
- throw new Error(
116
- `Event ${event} is not defined in the Integration event object`
117
- );
136
+ // todo: debate wether we want to keep this pattern to set the record or not.
137
+ /**
138
+ * Persist the database record and module instances onto this integration instance.
139
+ * Accepts either a plain object containing the persisted fields or an object with
140
+ * a `record` property plus a `modules` collection.
141
+ * @param {Object} payload
142
+ * @param {Object} [payload.record]
143
+ * @param {Array} [payload.modules]
144
+ */
145
+ setIntegrationRecord(payload = {}) {
146
+ if (!payload || Object.keys(payload).length === 0) {
147
+ throw new Error('setIntegrationRecord requires integration data');
148
+ }
149
+
150
+ const integrationRecord = payload.record;
151
+ const integrationModules = payload.modules ?? [];
152
+
153
+ if (!integrationRecord) {
154
+ throw new Error('Integration record not provided');
155
+ }
156
+
157
+ const { id, userId, entities, config, status, version, messages } =
158
+ integrationRecord;
159
+
160
+ this.id = id;
161
+ this.userId = userId;
162
+ this.entities = entities;
163
+ this.config = config;
164
+ this.status = status;
165
+ this.version = version;
166
+ this.messages = messages || { errors: [], warnings: [] };
167
+
168
+ this.modules = this._appendModules(integrationModules);
169
+
170
+ this.record = {
171
+ id: this.id,
172
+ userId: this.userId,
173
+ entities: this.entities,
174
+ config: this.config,
175
+ status: this.status,
176
+ version: this.version,
177
+ messages: this.messages,
178
+ };
179
+
180
+ this._isHydrated = Boolean(this.id);
181
+ return this;
182
+ }
183
+
184
+ get isHydrated() {
185
+ return this._isHydrated;
186
+ }
187
+
188
+ assertHydrated(message = 'Integration instance is not hydrated') {
189
+ if (!this.isHydrated) {
190
+ throw new Error(message);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Returns the modules as object with keys as module names.
196
+ * @private
197
+ * @param {Array} integrationModules - Array of module instances
198
+ * @returns {Object} The modules object
199
+ */
200
+ _appendModules(integrationModules) {
201
+ const modules = {};
202
+ for (const module of integrationModules) {
203
+ const key =
204
+ typeof module.getName === 'function'
205
+ ? module.getName()
206
+ : module.name;
207
+ if (key) {
208
+ modules[key] = module;
209
+ this[key] = module;
210
+ }
118
211
  }
119
- return this.on[event].handler.call(this, object);
212
+ return modules;
120
213
  }
121
214
 
122
215
  async validateConfig() {
123
216
  const configOptions = await this.getConfigOptions();
124
- const currentConfig = this.record.config;
217
+ const currentConfig = this.getConfig();
125
218
  let needsConfig = false;
126
219
  for (const option of configOptions) {
127
220
  if (option.required) {
@@ -133,56 +226,62 @@ class IntegrationBase {
133
226
  )
134
227
  ) {
135
228
  needsConfig = true;
136
- this.record.messages.warnings.push({
137
- title: 'Config Validation Error',
138
- message: `Missing required field of ${option.label}`,
139
- timestamp: Date.now(),
140
- });
229
+ await this.updateIntegrationMessages.execute(
230
+ this.id,
231
+ 'warnings',
232
+ 'Config Validation Error',
233
+ `Missing required field of ${option.label}`,
234
+ Date.now()
235
+ );
141
236
  }
142
237
  }
143
238
  }
144
239
  if (needsConfig) {
145
- this.record.status = 'NEEDS_CONFIG';
146
- await this.record.save();
240
+ await this.updateIntegrationStatus.execute(this.id, 'NEEDS_CONFIG');
147
241
  }
148
242
  }
149
243
 
150
244
  async testAuth() {
151
245
  let didAuthPass = true;
152
246
 
153
- for (const module of Object.keys(IntegrationBase.Definition.modules)) {
247
+ for (const module of Object.keys(this.constructor.Definition.modules)) {
154
248
  try {
155
249
  await this[module].testAuth();
156
250
  } catch {
157
251
  didAuthPass = false;
158
- this.record.messages.errors.push({
159
- title: 'Authentication Error',
160
- message: `There was an error with your ${this[
252
+ await this.updateIntegrationMessages.execute(
253
+ this.id,
254
+ 'errors',
255
+ 'Authentication Error',
256
+ `There was an error with your ${this[
161
257
  module
162
258
  ].constructor.getName()} Entity.
163
259
  Please reconnect/re-authenticate, or reach out to Support for assistance.`,
164
- timestamp: Date.now(),
165
- });
260
+ Date.now()
261
+ );
166
262
  }
167
263
  }
168
264
 
169
265
  if (!didAuthPass) {
170
- this.record.status = 'ERROR';
171
- this.record.markModified('messages.error');
172
- await this.record.save();
266
+ await this.updateIntegrationStatus.execute(this.id, 'ERROR');
173
267
  }
174
268
  }
175
269
 
176
270
  async getMapping(sourceId) {
177
- return IntegrationMapping.findBy(this.record.id, sourceId);
271
+ // todo: not sure we should call the repository directly from here
272
+ return this.integrationMappingRepository.findMappingBy(
273
+ this.id,
274
+ sourceId
275
+ );
178
276
  }
179
277
 
180
278
  async upsertMapping(sourceId, mapping) {
181
279
  if (!sourceId) {
182
280
  throw new Error(`sourceId must be set`);
183
281
  }
184
- return await IntegrationMapping.upsert(
185
- this.record.id,
282
+ // todo: not sure we should call the repository directly from here
283
+ return await this.integrationMappingRepository.upsertMapping(
284
+ this.id,
186
285
  sourceId,
187
286
  mapping
188
287
  );
@@ -191,10 +290,8 @@ class IntegrationBase {
191
290
  /**
192
291
  * CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
193
292
  */
194
- async onCreate(params) {
195
- this.record.status = 'ENABLED';
196
- await this.record.save();
197
- return this.record;
293
+ async onCreate({ integrationId }) {
294
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
198
295
  }
199
296
 
200
297
  async onUpdate(params) {}
@@ -259,6 +356,80 @@ class IntegrationBase {
259
356
  };
260
357
  return options;
261
358
  }
359
+
360
+ // === Domain Methods (moved from Integration.js) ===
361
+
362
+ getConfig() {
363
+ return this.config;
364
+ }
365
+
366
+ getModule(key) {
367
+ return this.modules[key];
368
+ }
369
+
370
+ setModule(key, module) {
371
+ this.modules[key] = module;
372
+ this[key] = module;
373
+ }
374
+
375
+ addError(error) {
376
+ if (!this.messages.errors) {
377
+ this.messages.errors = [];
378
+ }
379
+ this.messages.errors.push(error);
380
+ this.status = 'ERROR';
381
+ }
382
+
383
+ addWarning(warning) {
384
+ if (!this.messages.warnings) {
385
+ this.messages.warnings = [];
386
+ }
387
+ this.messages.warnings.push(warning);
388
+ }
389
+
390
+ isActive() {
391
+ return this.status === 'ENABLED' || this.status === 'ACTIVE';
392
+ }
393
+
394
+ needsConfiguration() {
395
+ return this.status === 'NEEDS_CONFIG';
396
+ }
397
+
398
+ hasErrors() {
399
+ return this.status === 'ERROR';
400
+ }
401
+
402
+ belongsToUser(userId) {
403
+ return this.userId.toString() === userId.toString();
404
+ }
405
+
406
+ async initialize() {
407
+ // Load dynamic user actions
408
+ try {
409
+ const additionalUserActions = await this.loadDynamicUserActions();
410
+ this.events = { ...this.events, ...additionalUserActions };
411
+ } catch (e) {
412
+ this.addError(e);
413
+ }
414
+
415
+ // Event handlers are no longer registered here - handled by IntegrationEventDispatcher
416
+ }
417
+
418
+ getOptionDetails() {
419
+ const options = new Options({
420
+ module: Object.values(this.constructor.Definition.modules)[0],
421
+ ...this.constructor.Definition,
422
+ });
423
+ return options.get();
424
+ }
425
+
426
+ // Legacy method for backward compatibility
427
+ async loadModules() {
428
+ // This method was used in the old architecture for loading modules
429
+ // In the new architecture, modules are injected via constructor
430
+ // For backward compatibility, this is a no-op
431
+ return;
432
+ }
262
433
  }
263
434
 
264
435
  module.exports = { IntegrationBase };