@friggframework/core 2.0.0-next.6 → 2.0.0-next.61

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