@friggframework/core 2.0.0-next.5 → 2.0.0-next.50

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 (267) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +959 -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 +179 -0
  7. package/application/commands/user-commands.js +213 -0
  8. package/application/index.js +69 -0
  9. package/core/CLAUDE.md +690 -0
  10. package/core/Worker.js +8 -21
  11. package/core/create-handler.js +2 -7
  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 +307 -0
  15. package/credential/repositories/credential-repository-postgres.js +313 -0
  16. package/credential/repositories/credential-repository.js +302 -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/MONGODB_TRANSACTION_FIX.md +198 -0
  20. package/database/adapters/lambda-invoker.js +97 -0
  21. package/database/config.js +154 -0
  22. package/database/encryption/README.md +684 -0
  23. package/database/encryption/encryption-schema-registry.js +141 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/logger.js +79 -0
  26. package/database/encryption/prisma-encryption-extension.js +222 -0
  27. package/database/index.js +25 -12
  28. package/database/models/WebsocketConnection.js +16 -10
  29. package/database/models/readme.md +1 -0
  30. package/database/prisma.js +222 -0
  31. package/database/repositories/health-check-repository-factory.js +43 -0
  32. package/database/repositories/health-check-repository-interface.js +87 -0
  33. package/database/repositories/health-check-repository-mongodb.js +91 -0
  34. package/database/repositories/health-check-repository-postgres.js +82 -0
  35. package/database/repositories/health-check-repository.js +108 -0
  36. package/database/repositories/migration-status-repository-s3.js +137 -0
  37. package/database/use-cases/check-database-health-use-case.js +29 -0
  38. package/database/use-cases/check-database-state-use-case.js +81 -0
  39. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  40. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  41. package/database/use-cases/get-migration-status-use-case.js +93 -0
  42. package/database/use-cases/run-database-migration-use-case.js +137 -0
  43. package/database/use-cases/test-encryption-use-case.js +253 -0
  44. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  45. package/database/utils/mongodb-collection-utils.js +91 -0
  46. package/database/utils/mongodb-schema-init.js +106 -0
  47. package/database/utils/prisma-runner.js +400 -0
  48. package/database/utils/prisma-schema-parser.js +182 -0
  49. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  50. package/encrypt/Cryptor.js +34 -168
  51. package/encrypt/index.js +1 -2
  52. package/encrypt/test-encrypt.js +0 -2
  53. package/generated/prisma-mongodb/client.d.ts +1 -0
  54. package/generated/prisma-mongodb/client.js +4 -0
  55. package/generated/prisma-mongodb/default.d.ts +1 -0
  56. package/generated/prisma-mongodb/default.js +4 -0
  57. package/generated/prisma-mongodb/edge.d.ts +1 -0
  58. package/generated/prisma-mongodb/edge.js +334 -0
  59. package/generated/prisma-mongodb/index-browser.js +316 -0
  60. package/generated/prisma-mongodb/index.d.ts +22898 -0
  61. package/generated/prisma-mongodb/index.js +359 -0
  62. package/generated/prisma-mongodb/package.json +183 -0
  63. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  64. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  65. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  66. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  67. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  68. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  69. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  70. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  71. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  72. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  73. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  74. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  75. package/generated/prisma-mongodb/schema.prisma +362 -0
  76. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  77. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  78. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  79. package/generated/prisma-mongodb/wasm.js +341 -0
  80. package/generated/prisma-postgresql/client.d.ts +1 -0
  81. package/generated/prisma-postgresql/client.js +4 -0
  82. package/generated/prisma-postgresql/default.d.ts +1 -0
  83. package/generated/prisma-postgresql/default.js +4 -0
  84. package/generated/prisma-postgresql/edge.d.ts +1 -0
  85. package/generated/prisma-postgresql/edge.js +356 -0
  86. package/generated/prisma-postgresql/index-browser.js +338 -0
  87. package/generated/prisma-postgresql/index.d.ts +25072 -0
  88. package/generated/prisma-postgresql/index.js +381 -0
  89. package/generated/prisma-postgresql/package.json +183 -0
  90. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  91. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  92. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  93. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  94. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  95. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  96. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  97. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  98. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  99. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  100. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  101. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  102. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  103. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  104. package/generated/prisma-postgresql/schema.prisma +345 -0
  105. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  106. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  107. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  108. package/generated/prisma-postgresql/wasm.js +363 -0
  109. package/handlers/WEBHOOKS.md +653 -0
  110. package/handlers/app-definition-loader.js +38 -0
  111. package/handlers/app-handler-helpers.js +56 -0
  112. package/handlers/backend-utils.js +180 -0
  113. package/handlers/database-migration-handler.js +227 -0
  114. package/handlers/integration-event-dispatcher.js +54 -0
  115. package/handlers/routers/HEALTHCHECK.md +342 -0
  116. package/handlers/routers/auth.js +15 -0
  117. package/handlers/routers/db-migration.handler.js +29 -0
  118. package/handlers/routers/db-migration.js +256 -0
  119. package/handlers/routers/health.js +519 -0
  120. package/handlers/routers/integration-defined-routers.js +45 -0
  121. package/handlers/routers/integration-webhook-routers.js +67 -0
  122. package/handlers/routers/user.js +63 -0
  123. package/handlers/routers/websocket.js +57 -0
  124. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  125. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  126. package/handlers/workers/db-migration.js +352 -0
  127. package/handlers/workers/integration-defined-workers.js +27 -0
  128. package/index.js +77 -22
  129. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  130. package/integrations/index.js +12 -10
  131. package/integrations/integration-base.js +296 -54
  132. package/integrations/integration-router.js +381 -182
  133. package/integrations/options.js +1 -1
  134. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  135. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  136. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  137. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  138. package/integrations/repositories/integration-mapping-repository.js +156 -0
  139. package/integrations/repositories/integration-repository-factory.js +44 -0
  140. package/integrations/repositories/integration-repository-interface.js +127 -0
  141. package/integrations/repositories/integration-repository-mongo.js +303 -0
  142. package/integrations/repositories/integration-repository-postgres.js +352 -0
  143. package/integrations/repositories/process-repository-factory.js +46 -0
  144. package/integrations/repositories/process-repository-interface.js +90 -0
  145. package/integrations/repositories/process-repository-mongo.js +190 -0
  146. package/integrations/repositories/process-repository-postgres.js +217 -0
  147. package/integrations/tests/doubles/dummy-integration-class.js +83 -0
  148. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  149. package/integrations/use-cases/create-integration.js +83 -0
  150. package/integrations/use-cases/create-process.js +128 -0
  151. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  152. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  153. package/integrations/use-cases/get-integration-for-user.js +78 -0
  154. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  155. package/integrations/use-cases/get-integration-instance.js +83 -0
  156. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  157. package/integrations/use-cases/get-possible-integrations.js +27 -0
  158. package/integrations/use-cases/get-process.js +87 -0
  159. package/integrations/use-cases/index.js +19 -0
  160. package/integrations/use-cases/load-integration-context.js +71 -0
  161. package/integrations/use-cases/update-integration-messages.js +44 -0
  162. package/integrations/use-cases/update-integration-status.js +32 -0
  163. package/integrations/use-cases/update-integration.js +93 -0
  164. package/integrations/use-cases/update-process-metrics.js +201 -0
  165. package/integrations/use-cases/update-process-state.js +119 -0
  166. package/integrations/utils/map-integration-dto.js +36 -0
  167. package/jest-global-setup-noop.js +3 -0
  168. package/jest-global-teardown-noop.js +3 -0
  169. package/logs/logger.js +0 -4
  170. package/{module-plugin → modules}/entity.js +1 -1
  171. package/{module-plugin → modules}/index.js +0 -8
  172. package/modules/module-factory.js +56 -0
  173. package/modules/module.js +221 -0
  174. package/modules/repositories/module-repository-factory.js +33 -0
  175. package/modules/repositories/module-repository-interface.js +129 -0
  176. package/modules/repositories/module-repository-mongo.js +377 -0
  177. package/modules/repositories/module-repository-postgres.js +426 -0
  178. package/modules/repositories/module-repository.js +316 -0
  179. package/{module-plugin → modules}/requester/requester.js +1 -0
  180. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  181. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  182. package/modules/tests/doubles/test-module-factory.js +16 -0
  183. package/modules/tests/doubles/test-module-repository.js +39 -0
  184. package/modules/use-cases/get-entities-for-user.js +32 -0
  185. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  186. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  187. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  188. package/modules/use-cases/get-module.js +55 -0
  189. package/modules/use-cases/process-authorization-callback.js +122 -0
  190. package/modules/use-cases/refresh-entity-options.js +59 -0
  191. package/modules/use-cases/test-module-auth.js +55 -0
  192. package/modules/utils/map-module-dto.js +18 -0
  193. package/package.json +82 -50
  194. package/prisma-mongodb/schema.prisma +362 -0
  195. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  196. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  197. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  198. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  199. package/prisma-postgresql/schema.prisma +345 -0
  200. package/queues/queuer-util.js +28 -15
  201. package/syncs/manager.js +468 -443
  202. package/syncs/repositories/sync-repository-factory.js +38 -0
  203. package/syncs/repositories/sync-repository-interface.js +109 -0
  204. package/syncs/repositories/sync-repository-mongo.js +239 -0
  205. package/syncs/repositories/sync-repository-postgres.js +319 -0
  206. package/syncs/sync.js +0 -1
  207. package/token/repositories/token-repository-factory.js +33 -0
  208. package/token/repositories/token-repository-interface.js +131 -0
  209. package/token/repositories/token-repository-mongo.js +212 -0
  210. package/token/repositories/token-repository-postgres.js +257 -0
  211. package/token/repositories/token-repository.js +219 -0
  212. package/types/core/index.d.ts +2 -2
  213. package/types/integrations/index.d.ts +2 -6
  214. package/types/module-plugin/index.d.ts +5 -59
  215. package/types/syncs/index.d.ts +0 -2
  216. package/user/repositories/user-repository-factory.js +46 -0
  217. package/user/repositories/user-repository-interface.js +198 -0
  218. package/user/repositories/user-repository-mongo.js +291 -0
  219. package/user/repositories/user-repository-postgres.js +350 -0
  220. package/user/tests/doubles/test-user-repository.js +72 -0
  221. package/user/use-cases/authenticate-user.js +127 -0
  222. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  223. package/user/use-cases/create-individual-user.js +61 -0
  224. package/user/use-cases/create-organization-user.js +47 -0
  225. package/user/use-cases/create-token-for-user-id.js +30 -0
  226. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  227. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  228. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  229. package/user/use-cases/login-user.js +122 -0
  230. package/user/user.js +93 -0
  231. package/utils/backend-path.js +38 -0
  232. package/utils/index.js +6 -0
  233. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  234. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  235. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  236. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  237. package/websocket/repositories/websocket-connection-repository.js +161 -0
  238. package/database/models/State.js +0 -9
  239. package/database/models/Token.js +0 -70
  240. package/database/mongo.js +0 -45
  241. package/encrypt/Cryptor.test.js +0 -32
  242. package/encrypt/encrypt.js +0 -132
  243. package/encrypt/encrypt.test.js +0 -1069
  244. package/errors/base-error.test.js +0 -32
  245. package/errors/fetch-error.test.js +0 -79
  246. package/errors/halt-error.test.js +0 -11
  247. package/errors/validation-errors.test.js +0 -120
  248. package/integrations/create-frigg-backend.js +0 -31
  249. package/integrations/integration-factory.js +0 -251
  250. package/integrations/integration-mapping.js +0 -43
  251. package/integrations/integration-model.js +0 -46
  252. package/integrations/integration-user.js +0 -144
  253. package/integrations/test/integration-base.test.js +0 -144
  254. package/lambda/TimeoutCatcher.test.js +0 -68
  255. package/logs/logger.test.js +0 -76
  256. package/module-plugin/auther.js +0 -393
  257. package/module-plugin/credential.js +0 -22
  258. package/module-plugin/entity-manager.js +0 -70
  259. package/module-plugin/manager.js +0 -169
  260. package/module-plugin/module-factory.js +0 -61
  261. package/module-plugin/requester/requester.test.js +0 -28
  262. package/module-plugin/test/auther.test.js +0 -97
  263. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  264. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  265. /package/{module-plugin → modules}/requester/basic.js +0 -0
  266. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  267. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -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',
@@ -10,6 +22,8 @@ const constantsToBeMigrated = {
10
22
  GET_USER_ACTIONS: 'GET_USER_ACTIONS',
11
23
  GET_USER_ACTION_OPTIONS: 'GET_USER_ACTION_OPTIONS',
12
24
  REFRESH_USER_ACTION_OPTIONS: 'REFRESH_USER_ACTION_OPTIONS',
25
+ WEBHOOK_RECEIVED: 'WEBHOOK_RECEIVED', // HTTP handler, no DB
26
+ ON_WEBHOOK: 'ON_WEBHOOK', // Queue worker, DB-connected
13
27
  // etc...
14
28
  },
15
29
  types: {
@@ -19,6 +33,16 @@ const constantsToBeMigrated = {
19
33
  };
20
34
 
21
35
  class IntegrationBase {
36
+ // todo: maybe we can pass this as Dependency Injection in the sub-class constructor
37
+ integrationRepository = createIntegrationRepository();
38
+ integrationMappingRepository = createIntegrationMappingRepository();
39
+ updateIntegrationStatus = new UpdateIntegrationStatus({
40
+ integrationRepository: this.integrationRepository,
41
+ });
42
+ updateIntegrationMessages = new UpdateIntegrationMessages({
43
+ integrationRepository: this.integrationRepository,
44
+ });
45
+
22
46
  static getOptionDetails() {
23
47
  const options = new Options({
24
48
  module: Object.values(this.Definition.modules)[0], // This is a placeholder until we revamp the frontend
@@ -26,6 +50,7 @@ class IntegrationBase {
26
50
  });
27
51
  return options.get();
28
52
  }
53
+
29
54
  /**
30
55
  * CHILDREN SHOULD SPECIFY A DEFINITION FOR THE INTEGRATION
31
56
  */
@@ -50,29 +75,30 @@ class IntegrationBase {
50
75
  static getCurrentVersion() {
51
76
  return this.Definition.version;
52
77
  }
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
- }
78
+
79
+ // REMOVED: registerEventHandlers() - Event handling is now done by IntegrationEventDispatcher
80
+
81
+ constructor(params = {}) {
82
+ this.modules = {};
83
+ this.events = this.events || {};
84
+ this.messages = { errors: [], warnings: [] };
85
+ this._isHydrated = false;
86
+
87
+ if (params && Object.keys(params).length > 0) {
88
+ this.setIntegrationRecord({
89
+ record: {
90
+ id: params.id,
91
+ userId: params.userId,
92
+ entities: params.entities,
93
+ config: params.config,
94
+ status: params.status,
95
+ version: params.version,
96
+ messages: params.messages,
97
+ },
98
+ modules: params.modules || [],
99
+ });
66
100
  }
67
- }
68
- registerEventHandlers() {
69
- this.on = {
70
- ...this.defaultEvents,
71
- ...this.events,
72
- };
73
- }
74
101
 
75
- constructor(params) {
76
102
  this.defaultEvents = {
77
103
  [constantsToBeMigrated.defaultEvents.ON_CREATE]: {
78
104
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
@@ -106,22 +132,99 @@ class IntegrationBase {
106
132
  type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
107
133
  handler: this.refreshActionOptions,
108
134
  },
135
+ [constantsToBeMigrated.defaultEvents.WEBHOOK_RECEIVED]: {
136
+ type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
137
+ handler: this.onWebhookReceived,
138
+ },
139
+ [constantsToBeMigrated.defaultEvents.ON_WEBHOOK]: {
140
+ type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
141
+ handler: this.onWebhook,
142
+ },
109
143
  };
110
- this.loadModules();
111
144
  }
112
145
 
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
- );
146
+ // todo: debate wether we want to keep this pattern to set the record or not.
147
+ /**
148
+ * Persist the database record and module instances onto this integration instance.
149
+ * Accepts either a plain object containing the persisted fields or an object with
150
+ * a `record` property plus a `modules` collection.
151
+ * @param {Object} payload
152
+ * @param {Object} [payload.record]
153
+ * @param {Array} [payload.modules]
154
+ */
155
+ setIntegrationRecord(payload = {}) {
156
+ if (!payload || Object.keys(payload).length === 0) {
157
+ throw new Error('setIntegrationRecord requires integration data');
118
158
  }
119
- return this.on[event].handler.call(this, object);
159
+
160
+ const integrationRecord = payload.record;
161
+ const integrationModules = payload.modules ?? [];
162
+
163
+ if (!integrationRecord) {
164
+ throw new Error('Integration record not provided');
165
+ }
166
+
167
+ const { id, userId, entities, config, status, version, messages } =
168
+ integrationRecord;
169
+
170
+ this.id = id;
171
+ this.userId = userId;
172
+ this.entities = entities;
173
+ this.config = config;
174
+ this.status = status;
175
+ this.version = version;
176
+ this.messages = messages || { errors: [], warnings: [] };
177
+
178
+ this.modules = this._appendModules(integrationModules);
179
+
180
+ this.record = {
181
+ id: this.id,
182
+ userId: this.userId,
183
+ entities: this.entities,
184
+ config: this.config,
185
+ status: this.status,
186
+ version: this.version,
187
+ messages: this.messages,
188
+ };
189
+
190
+ this._isHydrated = Boolean(this.id);
191
+ return this;
192
+ }
193
+
194
+ get isHydrated() {
195
+ return this._isHydrated;
196
+ }
197
+
198
+ assertHydrated(message = 'Integration instance is not hydrated') {
199
+ if (!this.isHydrated) {
200
+ throw new Error(message);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Returns the modules as object with keys as module names.
206
+ * @private
207
+ * @param {Array} integrationModules - Array of module instances
208
+ * @returns {Object} The modules object
209
+ */
210
+ _appendModules(integrationModules) {
211
+ const modules = {};
212
+ for (const module of integrationModules) {
213
+ const key =
214
+ typeof module.getName === 'function'
215
+ ? module.getName()
216
+ : module.name;
217
+ if (key) {
218
+ modules[key] = module;
219
+ this[key] = module;
220
+ }
221
+ }
222
+ return modules;
120
223
  }
121
224
 
122
225
  async validateConfig() {
123
226
  const configOptions = await this.getConfigOptions();
124
- const currentConfig = this.record.config;
227
+ const currentConfig = this.getConfig();
125
228
  let needsConfig = false;
126
229
  for (const option of configOptions) {
127
230
  if (option.required) {
@@ -133,56 +236,62 @@ class IntegrationBase {
133
236
  )
134
237
  ) {
135
238
  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
- });
239
+ await this.updateIntegrationMessages.execute(
240
+ this.id,
241
+ 'warnings',
242
+ 'Config Validation Error',
243
+ `Missing required field of ${option.label}`,
244
+ Date.now()
245
+ );
141
246
  }
142
247
  }
143
248
  }
144
249
  if (needsConfig) {
145
- this.record.status = 'NEEDS_CONFIG';
146
- await this.record.save();
250
+ await this.updateIntegrationStatus.execute(this.id, 'NEEDS_CONFIG');
147
251
  }
148
252
  }
149
253
 
150
254
  async testAuth() {
151
255
  let didAuthPass = true;
152
256
 
153
- for (const module of Object.keys(IntegrationBase.Definition.modules)) {
257
+ for (const module of Object.keys(this.constructor.Definition.modules)) {
154
258
  try {
155
259
  await this[module].testAuth();
156
260
  } catch {
157
261
  didAuthPass = false;
158
- this.record.messages.errors.push({
159
- title: 'Authentication Error',
160
- message: `There was an error with your ${this[
262
+ await this.updateIntegrationMessages.execute(
263
+ this.id,
264
+ 'errors',
265
+ 'Authentication Error',
266
+ `There was an error with your ${this[
161
267
  module
162
268
  ].constructor.getName()} Entity.
163
269
  Please reconnect/re-authenticate, or reach out to Support for assistance.`,
164
- timestamp: Date.now(),
165
- });
270
+ Date.now()
271
+ );
166
272
  }
167
273
  }
168
274
 
169
275
  if (!didAuthPass) {
170
- this.record.status = 'ERROR';
171
- this.record.markModified('messages.error');
172
- await this.record.save();
276
+ await this.updateIntegrationStatus.execute(this.id, 'ERROR');
173
277
  }
174
278
  }
175
279
 
176
280
  async getMapping(sourceId) {
177
- return IntegrationMapping.findBy(this.record.id, sourceId);
281
+ // todo: not sure we should call the repository directly from here
282
+ return this.integrationMappingRepository.findMappingBy(
283
+ this.id,
284
+ sourceId
285
+ );
178
286
  }
179
287
 
180
288
  async upsertMapping(sourceId, mapping) {
181
289
  if (!sourceId) {
182
290
  throw new Error(`sourceId must be set`);
183
291
  }
184
- return await IntegrationMapping.upsert(
185
- this.record.id,
292
+ // todo: not sure we should call the repository directly from here
293
+ return await this.integrationMappingRepository.upsertMapping(
294
+ this.id,
186
295
  sourceId,
187
296
  mapping
188
297
  );
@@ -191,13 +300,13 @@ class IntegrationBase {
191
300
  /**
192
301
  * CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
193
302
  */
194
- async onCreate(params) {
195
- this.record.status = 'ENABLED';
196
- await this.record.save();
197
- return this.record;
303
+ async onCreate({ integrationId }) {
304
+ await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
198
305
  }
199
306
 
200
- async onUpdate(params) {}
307
+ async onUpdate(params) {
308
+ return this.validateConfig();
309
+ }
201
310
 
202
311
  async onDelete(params) {}
203
312
 
@@ -259,6 +368,139 @@ class IntegrationBase {
259
368
  };
260
369
  return options;
261
370
  }
371
+
372
+ /**
373
+ * WEBHOOK EVENT HANDLERS
374
+ */
375
+ async onWebhookReceived({ req, res }) {
376
+ // Default: queue webhook for processing
377
+ const body = req.body;
378
+ const integrationId = req.params.integrationId || null;
379
+
380
+ await this.queueWebhook({
381
+ integrationId,
382
+ body,
383
+ headers: req.headers,
384
+ query: req.query,
385
+ });
386
+
387
+ res.status(200).json({ received: true });
388
+ }
389
+
390
+ async onWebhook({ data }) {
391
+ // Default: no-op, integrations override this
392
+ console.log('Webhook received:', data);
393
+ }
394
+
395
+ async queueWebhook(data) {
396
+ const { QueuerUtil } = require('../queues');
397
+
398
+ const queueName = `${this.constructor.Definition.name
399
+ .toUpperCase()
400
+ .replace(/-/g, '_')}_QUEUE_URL`;
401
+ const queueUrl = process.env[queueName];
402
+
403
+ if (!queueUrl) {
404
+ throw new Error(`Queue URL not found for ${queueName}`);
405
+ }
406
+
407
+ return QueuerUtil.send(
408
+ {
409
+ event: 'ON_WEBHOOK',
410
+ data,
411
+ },
412
+ queueUrl
413
+ );
414
+ }
415
+
416
+ // === Domain Methods (moved from Integration.js) ===
417
+
418
+ getConfig() {
419
+ return this.config;
420
+ }
421
+
422
+ getModule(key) {
423
+ return this.modules[key];
424
+ }
425
+
426
+ setModule(key, module) {
427
+ this.modules[key] = module;
428
+ this[key] = module;
429
+ }
430
+
431
+ addError(error) {
432
+ if (!this.messages.errors) {
433
+ this.messages.errors = [];
434
+ }
435
+ this.messages.errors.push(error);
436
+ this.status = 'ERROR';
437
+ }
438
+
439
+ addWarning(warning) {
440
+ if (!this.messages.warnings) {
441
+ this.messages.warnings = [];
442
+ }
443
+ this.messages.warnings.push(warning);
444
+ }
445
+
446
+ isActive() {
447
+ return this.status === 'ENABLED' || this.status === 'ACTIVE';
448
+ }
449
+
450
+ needsConfiguration() {
451
+ return this.status === 'NEEDS_CONFIG';
452
+ }
453
+
454
+ hasErrors() {
455
+ return this.status === 'ERROR';
456
+ }
457
+
458
+ belongsToUser(userId) {
459
+ return this.userId.toString() === userId.toString();
460
+ }
461
+
462
+ registerEventHandlers() {
463
+ this.on = {
464
+ ...this.defaultEvents,
465
+ ...this.events,
466
+ };
467
+ }
468
+
469
+ async initialize() {
470
+ try {
471
+ const additionalUserActions = await this.loadDynamicUserActions();
472
+ this.events = { ...this.events, ...additionalUserActions };
473
+ } catch (e) {
474
+ this.addError(e);
475
+ }
476
+
477
+ this.registerEventHandlers();
478
+ }
479
+
480
+ async send(event, object) {
481
+ if (!this.on[event]) {
482
+ throw new Error(
483
+ `Event ${event} is not defined in the Integration event object`
484
+ );
485
+ }
486
+ return this.on[event].handler.call(this, object);
487
+ }
488
+
489
+ getOptionDetails() {
490
+ const options = new Options({
491
+ module: Object.values(this.constructor.Definition.modules)[0],
492
+ ...this.constructor.Definition,
493
+ });
494
+ return options.get();
495
+ }
496
+
497
+ // Legacy method for backward compatibility
498
+ async loadModules() {
499
+ // This method was used in the old architecture for loading modules
500
+ // In the new architecture, modules are injected via constructor
501
+ // For backward compatibility, this is a no-op
502
+ return;
503
+ }
262
504
  }
263
505
 
264
506
  module.exports = { IntegrationBase };