@friggframework/core 2.0.0-next.9 → 2.0.0-next.90

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 (309) hide show
  1. package/CLAUDE.md +702 -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 +271 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +84 -6
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +401 -0
  30. package/database/encryption/field-encryption-service.js +254 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +230 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -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/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  69. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  72. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  73. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  74. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  75. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  76. package/generated/prisma-mongodb/runtime/library.js +146 -0
  77. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  78. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  79. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  80. package/generated/prisma-mongodb/schema.prisma +368 -0
  81. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  82. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  84. package/generated/prisma-mongodb/wasm.js +342 -0
  85. package/generated/prisma-postgresql/client.d.ts +1 -0
  86. package/generated/prisma-postgresql/client.js +4 -0
  87. package/generated/prisma-postgresql/default.d.ts +1 -0
  88. package/generated/prisma-postgresql/default.js +4 -0
  89. package/generated/prisma-postgresql/edge.d.ts +1 -0
  90. package/generated/prisma-postgresql/edge.js +357 -0
  91. package/generated/prisma-postgresql/index-browser.js +339 -0
  92. package/generated/prisma-postgresql/index.d.ts +25135 -0
  93. package/generated/prisma-postgresql/index.js +382 -0
  94. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  95. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  96. package/generated/prisma-postgresql/package.json +183 -0
  97. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  98. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  99. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  100. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  101. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  102. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  103. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  104. package/generated/prisma-postgresql/runtime/library.js +146 -0
  105. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  106. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  107. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  108. package/generated/prisma-postgresql/schema.prisma +351 -0
  109. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  110. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  111. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  112. package/generated/prisma-postgresql/wasm.js +364 -0
  113. package/handlers/WEBHOOKS.md +653 -0
  114. package/handlers/app-definition-loader.js +38 -0
  115. package/handlers/app-handler-helpers.js +57 -0
  116. package/handlers/backend-utils.js +297 -0
  117. package/handlers/database-migration-handler.js +227 -0
  118. package/handlers/integration-event-dispatcher.js +54 -0
  119. package/handlers/routers/HEALTHCHECK.md +342 -0
  120. package/handlers/routers/auth.js +15 -0
  121. package/handlers/routers/db-migration.handler.js +29 -0
  122. package/handlers/routers/db-migration.js +326 -0
  123. package/handlers/routers/health.js +518 -0
  124. package/handlers/routers/integration-defined-routers.js +117 -0
  125. package/handlers/routers/integration-webhook-routers.js +67 -0
  126. package/handlers/routers/user.js +63 -0
  127. package/handlers/routers/websocket.js +57 -0
  128. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  129. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  130. package/handlers/workers/db-migration.js +352 -0
  131. package/handlers/workers/dlq-processor.js +63 -0
  132. package/handlers/workers/integration-defined-workers.js +30 -0
  133. package/index.js +82 -46
  134. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  135. package/infrastructure/scheduler/index.js +33 -0
  136. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  137. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  138. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  139. package/integrations/EXTENSIONS.md +240 -0
  140. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  141. package/integrations/extension.js +254 -0
  142. package/integrations/index.js +20 -10
  143. package/integrations/integration-base.js +487 -55
  144. package/integrations/integration-router.js +396 -179
  145. package/integrations/options.js +1 -1
  146. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  147. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  148. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  149. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  150. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  151. package/integrations/repositories/integration-mapping-repository.js +156 -0
  152. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  153. package/integrations/repositories/integration-repository-factory.js +51 -0
  154. package/integrations/repositories/integration-repository-interface.js +144 -0
  155. package/integrations/repositories/integration-repository-mongo.js +330 -0
  156. package/integrations/repositories/integration-repository-postgres.js +385 -0
  157. package/integrations/repositories/process-repository-documentdb.js +311 -0
  158. package/integrations/repositories/process-repository-factory.js +53 -0
  159. package/integrations/repositories/process-repository-interface.js +136 -0
  160. package/integrations/repositories/process-repository-mongo.js +262 -0
  161. package/integrations/repositories/process-repository-postgres.js +380 -0
  162. package/integrations/repositories/process-update-ops-shared.js +112 -0
  163. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  164. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  165. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  166. package/integrations/use-cases/create-integration.js +83 -0
  167. package/integrations/use-cases/create-process.js +128 -0
  168. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  169. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  170. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  171. package/integrations/use-cases/get-integration-for-user.js +78 -0
  172. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  173. package/integrations/use-cases/get-integration-instance.js +83 -0
  174. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  175. package/integrations/use-cases/get-possible-integrations.js +27 -0
  176. package/integrations/use-cases/get-process.js +87 -0
  177. package/integrations/use-cases/index.js +19 -0
  178. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  179. package/integrations/use-cases/load-integration-context.js +71 -0
  180. package/integrations/use-cases/update-integration-messages.js +44 -0
  181. package/integrations/use-cases/update-integration-status.js +32 -0
  182. package/integrations/use-cases/update-integration.js +92 -0
  183. package/integrations/use-cases/update-process-metrics.js +214 -0
  184. package/integrations/use-cases/update-process-state.js +158 -0
  185. package/integrations/utils/map-integration-dto.js +37 -0
  186. package/jest-global-setup-noop.js +3 -0
  187. package/jest-global-teardown-noop.js +3 -0
  188. package/logs/logger.js +0 -4
  189. package/{module-plugin → modules}/index.js +0 -10
  190. package/modules/module-factory.js +56 -0
  191. package/modules/module.js +274 -0
  192. package/modules/repositories/module-repository-documentdb.js +350 -0
  193. package/modules/repositories/module-repository-factory.js +40 -0
  194. package/modules/repositories/module-repository-interface.js +145 -0
  195. package/modules/repositories/module-repository-mongo.js +436 -0
  196. package/modules/repositories/module-repository-postgres.js +481 -0
  197. package/modules/repositories/module-repository.js +369 -0
  198. package/modules/requester/api-key.js +52 -0
  199. package/modules/requester/oauth-2.js +396 -0
  200. package/modules/requester/requester.js +280 -0
  201. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  202. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  203. package/modules/tests/doubles/test-module-factory.js +16 -0
  204. package/modules/tests/doubles/test-module-repository.js +39 -0
  205. package/modules/use-cases/get-entities-for-user.js +32 -0
  206. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  207. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  208. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  209. package/modules/use-cases/get-module.js +74 -0
  210. package/modules/use-cases/process-authorization-callback.js +243 -0
  211. package/modules/use-cases/refresh-entity-options.js +72 -0
  212. package/modules/use-cases/test-module-auth.js +72 -0
  213. package/modules/utils/map-module-dto.js +18 -0
  214. package/package.json +82 -50
  215. package/prisma-mongodb/schema.prisma +368 -0
  216. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  217. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  218. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  219. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  220. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  221. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  222. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  223. package/prisma-postgresql/schema.prisma +351 -0
  224. package/queues/queuer-util.js +103 -21
  225. package/syncs/manager.js +468 -443
  226. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  227. package/syncs/repositories/sync-repository-factory.js +43 -0
  228. package/syncs/repositories/sync-repository-interface.js +109 -0
  229. package/syncs/repositories/sync-repository-mongo.js +239 -0
  230. package/syncs/repositories/sync-repository-postgres.js +319 -0
  231. package/syncs/sync.js +0 -1
  232. package/token/repositories/token-repository-documentdb.js +137 -0
  233. package/token/repositories/token-repository-factory.js +40 -0
  234. package/token/repositories/token-repository-interface.js +131 -0
  235. package/token/repositories/token-repository-mongo.js +219 -0
  236. package/token/repositories/token-repository-postgres.js +264 -0
  237. package/token/repositories/token-repository.js +219 -0
  238. package/types/associations/index.d.ts +0 -17
  239. package/types/core/index.d.ts +12 -4
  240. package/types/database/index.d.ts +10 -2
  241. package/types/encrypt/index.d.ts +5 -3
  242. package/types/integrations/index.d.ts +3 -8
  243. package/types/module-plugin/index.d.ts +17 -69
  244. package/types/syncs/index.d.ts +0 -17
  245. package/user/repositories/user-repository-documentdb.js +441 -0
  246. package/user/repositories/user-repository-factory.js +52 -0
  247. package/user/repositories/user-repository-interface.js +201 -0
  248. package/user/repositories/user-repository-mongo.js +308 -0
  249. package/user/repositories/user-repository-postgres.js +360 -0
  250. package/user/tests/doubles/test-user-repository.js +72 -0
  251. package/user/use-cases/authenticate-user.js +127 -0
  252. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  253. package/user/use-cases/create-individual-user.js +61 -0
  254. package/user/use-cases/create-organization-user.js +47 -0
  255. package/user/use-cases/create-token-for-user-id.js +30 -0
  256. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  257. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  258. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  259. package/user/use-cases/login-user.js +122 -0
  260. package/user/user.js +125 -0
  261. package/utils/backend-path.js +38 -0
  262. package/utils/index.js +6 -0
  263. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  264. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  265. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  266. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  267. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  268. package/websocket/repositories/websocket-connection-repository.js +161 -0
  269. package/assertions/is-equal.js +0 -17
  270. package/associations/model.js +0 -54
  271. package/database/models/IndividualUser.js +0 -76
  272. package/database/models/OrganizationUser.js +0 -29
  273. package/database/models/State.js +0 -9
  274. package/database/models/Token.js +0 -70
  275. package/database/models/UserModel.js +0 -7
  276. package/database/models/WebsocketConnection.js +0 -49
  277. package/database/mongo.js +0 -45
  278. package/database/mongoose.js +0 -5
  279. package/encrypt/Cryptor.test.js +0 -32
  280. package/encrypt/encrypt.js +0 -132
  281. package/encrypt/encrypt.test.js +0 -1069
  282. package/encrypt/test-encrypt.js +0 -107
  283. package/errors/base-error.test.js +0 -32
  284. package/errors/fetch-error.test.js +0 -79
  285. package/errors/halt-error.test.js +0 -11
  286. package/errors/validation-errors.test.js +0 -120
  287. package/integrations/create-frigg-backend.js +0 -31
  288. package/integrations/integration-factory.js +0 -251
  289. package/integrations/integration-mapping.js +0 -43
  290. package/integrations/integration-model.js +0 -46
  291. package/integrations/integration-user.js +0 -144
  292. package/integrations/test/integration-base.test.js +0 -144
  293. package/lambda/TimeoutCatcher.test.js +0 -68
  294. package/logs/logger.test.js +0 -76
  295. package/module-plugin/auther.js +0 -393
  296. package/module-plugin/credential.js +0 -22
  297. package/module-plugin/entity-manager.js +0 -70
  298. package/module-plugin/entity.js +0 -46
  299. package/module-plugin/manager.js +0 -169
  300. package/module-plugin/module-factory.js +0 -61
  301. package/module-plugin/requester/api-key.js +0 -36
  302. package/module-plugin/requester/oauth-2.js +0 -219
  303. package/module-plugin/requester/requester.js +0 -165
  304. package/module-plugin/requester/requester.test.js +0 -28
  305. package/module-plugin/test/auther.test.js +0 -97
  306. package/syncs/model.js +0 -62
  307. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  308. /package/{module-plugin → modules}/requester/basic.js +0 -0
  309. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,22 +1,26 @@
1
1
  require('dotenv').config();
2
- const {Api} = require('./api');
3
- const {get} = require('../../assertions');
4
- const config = {name: 'anapi'}
2
+ const { Api } = require('./api');
3
+ const { get } = require('../../../assertions');
4
+ const config = { name: 'anapi' }
5
5
 
6
6
  const Definition = {
7
7
  API: Api,
8
- getName: function() {return config.name},
8
+ getAuthorizationRequirements: () => ({
9
+ url: 'http://localhost:3000/redirect/anapi',
10
+ type: 'oauth2',
11
+ }),
12
+ getName: function () { return config.name },
9
13
  moduleName: config.name,
10
14
  modelName: 'AnApi',
11
15
  requiredAuthMethods: {
12
- getToken: async function(api, params){
16
+ getToken: async function (api, params) {
13
17
  const code = get(params.data, 'code');
14
18
  return api.getTokenFromCode(code);
15
19
  },
16
- getEntityDetails: async function(api, callbackParams, tokenResponse, userId) {
20
+ getEntityDetails: async function (api, callbackParams, tokenResponse, userId) {
17
21
  const userDetails = await api.getUserDetails();
18
22
  return {
19
- identifiers: { externalId: userDetails.portalId, user: userId },
23
+ identifiers: { externalId: userDetails.portalId, userId },
20
24
  details: { name: userDetails.hub_domain },
21
25
  }
22
26
  },
@@ -26,14 +30,14 @@ const Definition = {
26
30
  ],
27
31
  entity: [],
28
32
  },
29
- getCredentialDetails: async function(api, userId) {
33
+ getCredentialDetails: async function (api, userId) {
30
34
  const userDetails = await api.getUserDetails();
31
35
  return {
32
- identifiers: { externalId: userDetails.portalId, user: userId },
36
+ identifiers: { externalId: userDetails.portalId, userId },
33
37
  details: {}
34
38
  };
35
39
  },
36
- testAuthRequest: async function(api){
40
+ testAuthRequest: async function (api) {
37
41
  return api.getUserDetails()
38
42
  },
39
43
  },
@@ -0,0 +1,16 @@
1
+ class TestModuleFactory {
2
+ constructor() { }
3
+
4
+ async getModuleInstance(entityId, userId) {
5
+ // return minimal stub module with getName and api property
6
+ return {
7
+ getName() { return 'stubModule'; },
8
+ api: {},
9
+ entityId,
10
+ userId,
11
+ testAuth: async () => true,
12
+ };
13
+ }
14
+ }
15
+
16
+ module.exports = { TestModuleFactory };
@@ -0,0 +1,39 @@
1
+ class TestModuleRepository {
2
+ constructor() {
3
+ this.entities = new Map();
4
+ }
5
+
6
+ addEntity(entity) {
7
+ this.entities.set(entity.id, entity);
8
+ }
9
+
10
+ async findEntityById(id) {
11
+ return this.entities.get(id);
12
+ }
13
+
14
+ async findEntitiesByIds(ids) {
15
+ return ids.map((id) => this.entities.get(id));
16
+ }
17
+
18
+ async findEntity(filter) {
19
+ if (!filter || typeof filter !== 'object') {
20
+ return null;
21
+ }
22
+
23
+ if (filter.id && this.entities.has(filter.id)) {
24
+ return this.entities.get(filter.id);
25
+ }
26
+
27
+ if (filter.externalId) {
28
+ for (const entity of this.entities.values()) {
29
+ if (entity.externalId === filter.externalId) {
30
+ return entity;
31
+ }
32
+ }
33
+ }
34
+
35
+ return null;
36
+ }
37
+ }
38
+
39
+ module.exports = { TestModuleRepository };
@@ -0,0 +1,32 @@
1
+ const { Module } = require('../module');
2
+ const { mapModuleClassToModuleDTO } = require('../utils/map-module-dto');
3
+
4
+ class GetEntitiesForUser {
5
+ constructor({ moduleRepository, moduleDefinitions }) {
6
+ this.moduleRepository = moduleRepository;
7
+
8
+ this.definitionMap = new Map();
9
+ for (const definition of moduleDefinitions) {
10
+ this.definitionMap.set(definition.moduleName, definition);
11
+ }
12
+ }
13
+
14
+ async execute(userId) {
15
+ const entities = await this.moduleRepository.findEntitiesByUserId(
16
+ userId
17
+ );
18
+
19
+ return entities.map((entity) => {
20
+ const definition = this.definitionMap.get(entity.moduleName);
21
+
22
+ const moduleInstance = new Module({
23
+ userId,
24
+ definition: definition,
25
+ entity: entity,
26
+ });
27
+ return mapModuleClassToModuleDTO(moduleInstance);
28
+ });
29
+ }
30
+ }
31
+
32
+ module.exports = { GetEntitiesForUser };
@@ -0,0 +1,71 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetEntityOptionsById {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
7
+ * @param {} params.moduleDefinitions
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ /**
15
+ * Retrieve entity options for a given entity
16
+ *
17
+ * @param {string|number} entityId - Entity ID to retrieve options for
18
+ * @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
19
+ * @returns {Promise<Object>} Entity options
20
+ */
21
+ async execute(entityId, userIdOrUser) {
22
+ // Support both userId (backward compatible) and User object (new pattern)
23
+ const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
24
+ ? userIdOrUser.getId()
25
+ : userIdOrUser;
26
+
27
+ const entity = await this.moduleRepository.findEntityById(
28
+ entityId,
29
+ userId
30
+ );
31
+
32
+ if (!entity) {
33
+ throw new Error(`Entity ${entityId} not found`);
34
+ }
35
+
36
+ // Validate entity ownership
37
+ const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
38
+ ? userIdOrUser.ownsUserId(entity.userId)
39
+ : entity.userId?.toString() === userId?.toString();
40
+
41
+ if (!isOwned) {
42
+ throw new Error(
43
+ `Entity ${entityId} does not belong to user ${userId}`
44
+ );
45
+ }
46
+
47
+ const entityType = entity.moduleName;
48
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
49
+ const modelName =
50
+ Module.getEntityModelFromDefinition(def).modelName;
51
+ return entityType === modelName;
52
+ });
53
+
54
+ if (!moduleDefinition) {
55
+ throw new Error(
56
+ `Module definition not found for entity type: ${entityType}`
57
+ );
58
+ }
59
+
60
+ const module = new Module({
61
+ userId,
62
+ entity,
63
+ definition: moduleDefinition,
64
+ });
65
+
66
+ const entityOptions = await module.getEntityOptions();
67
+ return entityOptions;
68
+ }
69
+ }
70
+
71
+ module.exports = { GetEntityOptionsById };
@@ -0,0 +1,34 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetEntityOptionsByType {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {} params.moduleDefinitions
7
+ */
8
+ constructor({ moduleDefinitions }) {
9
+ this.moduleDefinitions = moduleDefinitions;
10
+ }
11
+
12
+ /**
13
+ * Retrieve a Module instance for a given user and entity/module type.
14
+ * @param {string} userId
15
+ * @param {string} type – human-readable module/entity type (e.g. "Hubspot")
16
+ */
17
+ async execute(userId, type) {
18
+ const moduleDefinition = this.moduleDefinitions.find(
19
+ (def) => def.getName() === type
20
+ );
21
+ if (!moduleDefinition) {
22
+ throw new Error(`Module definition not found for type: ${type}`);
23
+ }
24
+ const moduleInstance = new Module({
25
+ userId,
26
+ definition: moduleDefinition,
27
+ });
28
+
29
+ const entityOptions = await moduleInstance.getEntityOptions();
30
+ return entityOptions;
31
+ }
32
+ }
33
+
34
+ module.exports = { GetEntityOptionsByType };
@@ -0,0 +1,34 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetModuleInstanceFromType {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {} params.moduleDefinitions
7
+ */
8
+ constructor({ moduleDefinitions }) {
9
+ this.moduleDefinitions = moduleDefinitions;
10
+ }
11
+
12
+ /**
13
+ * Retrieve a Module instance for a given user and entity/module type.
14
+ * @param {string} userId
15
+ * @param {string} type – human-readable module/entity type (e.g. "Hubspot")
16
+ * @param {Object} [options]
17
+ * @param {string} [options.state] – optional OAuth state value to be forwarded to the API client (round-trips through the OAuth provider).
18
+ */
19
+ async execute(userId, type, options = {}) {
20
+ const moduleDefinition = this.moduleDefinitions.find(
21
+ (def) => def.getName() === type
22
+ );
23
+ if (!moduleDefinition) {
24
+ throw new Error(`Module definition not found for type: ${type}`);
25
+ }
26
+ return new Module({
27
+ userId,
28
+ definition: moduleDefinition,
29
+ state: options.state,
30
+ });
31
+ }
32
+ }
33
+
34
+ module.exports = { GetModuleInstanceFromType };
@@ -0,0 +1,74 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetModule {
4
+ constructor({ moduleRepository, moduleDefinitions }) {
5
+ this.moduleRepository = moduleRepository;
6
+ this.moduleDefinitions = moduleDefinitions;
7
+ }
8
+
9
+ /**
10
+ * Get module instance for an entity
11
+ *
12
+ * @param {string|number} entityId - Entity ID to retrieve
13
+ * @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
14
+ * @returns {Promise<Object>} Module details
15
+ */
16
+ async execute(entityId, userIdOrUser) {
17
+ // Support both userId (backward compatible) and User object (new pattern)
18
+ const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
19
+ ? userIdOrUser.getId()
20
+ : userIdOrUser;
21
+
22
+ const entity = await this.moduleRepository.findEntityById(
23
+ entityId,
24
+ userId
25
+ );
26
+
27
+ if (!entity) {
28
+ throw new Error(`Entity ${entityId} not found`);
29
+ }
30
+
31
+ // Validate entity ownership
32
+ // If User object provided, use ownsUserId to check linked users
33
+ // Otherwise fall back to simple equality check
34
+ const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
35
+ ? userIdOrUser.ownsUserId(entity.userId)
36
+ : entity.userId?.toString() === userId?.toString();
37
+
38
+ if (!isOwned) {
39
+ throw new Error(
40
+ `Entity ${entityId} does not belong to user ${userId}`
41
+ );
42
+ }
43
+
44
+ const entityType = entity.moduleName;
45
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
46
+ const modelName = Module.getEntityModelFromDefinition(def).modelName;
47
+ return entityType === modelName;
48
+ });
49
+
50
+ if (!moduleDefinition) {
51
+ throw new Error(
52
+ `Module definition not found for entity type: ${entityType}`
53
+ );
54
+ }
55
+
56
+ const module = new Module({
57
+ userId,
58
+ entity,
59
+ definition: moduleDefinition,
60
+ });
61
+
62
+ // todo: this properties should be methods in the Module class
63
+ return {
64
+ id: module.entity.id,
65
+ name: module.entity.name,
66
+ moduleName: module.entity.moduleName,
67
+ credential: module.credential,
68
+ externalId: module.entity.externalId,
69
+ userId: module.entity.user.toString(),
70
+ }
71
+ }
72
+ }
73
+
74
+ module.exports = { GetModule };
@@ -0,0 +1,243 @@
1
+ const { Module } = require('../module');
2
+ const { ModuleConstants } = require('../ModuleConstants');
3
+
4
+ // Statuses considered "broken" for an integration whose credentials have just
5
+ // been successfully re-authorized. Both ERROR (system-driven auth failure) and
6
+ // DISABLED (user paused the integration) are flipped back to ENABLED when the
7
+ // user completes a new authorization flow.
8
+ const STATUSES_RESET_ON_REAUTH = ['ERROR', 'DISABLED'];
9
+
10
+ class ProcessAuthorizationCallback {
11
+ /**
12
+ * @param {Object} params - Configuration parameters.
13
+ * @param {import('../repositories/module-repository-factory').ModuleRepositoryInterface} params.moduleRepository - Repository for module data operations.
14
+ * @param {import('../../credential/repositories/credential-repository-factory').CredentialRepositoryInterface} params.credentialRepository - Repository for credential data operations.
15
+ * @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
16
+ * @param {import('../../integrations/repositories/integration-repository-interface').IntegrationRepositoryInterface} [params.integrationRepository] - Repository for integration data operations. When provided, integrations in a broken state linked to the re-authorized entity are restored to ENABLED.
17
+ */
18
+ constructor({
19
+ moduleRepository,
20
+ credentialRepository,
21
+ moduleDefinitions,
22
+ integrationRepository,
23
+ }) {
24
+ this.moduleRepository = moduleRepository;
25
+ this.credentialRepository = credentialRepository;
26
+ this.moduleDefinitions = moduleDefinitions;
27
+ this.integrationRepository = integrationRepository;
28
+ }
29
+
30
+ async execute(userId, entityType, params) {
31
+ const hasCode = Boolean(params && params.code);
32
+ console.log(
33
+ `[Frigg] processAuthorizationCallback start userId=${userId} entityType=${entityType} hasCode=${hasCode}`
34
+ );
35
+
36
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
37
+ return entityType === def.moduleName;
38
+ });
39
+
40
+ if (!moduleDefinition) {
41
+ throw new Error(
42
+ `Module definition not found for entity type: ${entityType}`
43
+ );
44
+ }
45
+
46
+ // Bootstrap the Module with the existing entity (if any) so the API
47
+ // requester is preloaded with prior tokens. This enables a refresh
48
+ // fallback when callers lack a fresh OAuth code, and lets us match a
49
+ // re-auth back to the existing credential record by id.
50
+ const existingEntities =
51
+ await this.moduleRepository.findEntitiesByUserIdAndModuleName(
52
+ userId,
53
+ entityType
54
+ );
55
+ const existingEntity =
56
+ existingEntities && existingEntities.length > 0
57
+ ? existingEntities[0]
58
+ : null;
59
+
60
+ if (existingEntity) {
61
+ console.log(
62
+ `[Frigg] processAuthorizationCallback found existing entity id=${existingEntity.id} credentialId=${existingEntity.credential?.id}`
63
+ );
64
+ }
65
+
66
+ const module = new Module({
67
+ userId,
68
+ entity: existingEntity,
69
+ definition: moduleDefinition,
70
+ });
71
+
72
+ let tokenResponse;
73
+ if (module.apiClass.requesterType === ModuleConstants.authType.oauth2) {
74
+ tokenResponse = await moduleDefinition.requiredAuthMethods.getToken(
75
+ module.api,
76
+ params
77
+ );
78
+ console.log(
79
+ `[Frigg] processAuthorizationCallback OAuth getToken complete userId=${userId} entityType=${entityType}`
80
+ );
81
+ // Belt-and-suspenders: persist tokens explicitly here rather than
82
+ // relying solely on the DLGT_TOKEN_UPDATE notification chain
83
+ // inside setTokens. The notification path remains in place but
84
+ // has been observed to no-op silently in some prod paths,
85
+ // leaving newly-issued tokens unsaved while the user-visible
86
+ // OAuth flow appears to succeed.
87
+ await this.onTokenUpdate(module, moduleDefinition, userId);
88
+ } else {
89
+ tokenResponse =
90
+ await moduleDefinition.requiredAuthMethods.setAuthParams(
91
+ module.api,
92
+ params
93
+ );
94
+ await this.onTokenUpdate(module, moduleDefinition, userId);
95
+ }
96
+
97
+ console.log(
98
+ `[Frigg] processAuthorizationCallback credential persisted credentialId=${module.credential?.id} authIsValid=${module.credential?.authIsValid}`
99
+ );
100
+
101
+ const authRes = await module.testAuth();
102
+ if (!authRes) {
103
+ throw new Error('Authorization failed');
104
+ }
105
+
106
+ const entityDetails =
107
+ await moduleDefinition.requiredAuthMethods.getEntityDetails(
108
+ module.api,
109
+ params,
110
+ tokenResponse,
111
+ userId
112
+ );
113
+
114
+ Object.assign(
115
+ entityDetails.details,
116
+ module.apiParamsFromEntity(module.api)
117
+ );
118
+
119
+ const persistedEntity = await this.findOrCreateEntity(
120
+ entityDetails,
121
+ entityType,
122
+ module.credential.id
123
+ );
124
+
125
+ // Best-effort: a hiccup here must not fail a successful re-auth whose
126
+ // credential + entity are already persisted. Operators can recover
127
+ // stuck integrations manually.
128
+ try {
129
+ const restoredCount = await this.restoreIntegrationsForEntity(
130
+ persistedEntity.id
131
+ );
132
+ console.log(
133
+ `[Frigg] processAuthorizationCallback restored ${restoredCount} integration(s) for entityId=${persistedEntity.id}`
134
+ );
135
+ } catch (err) {
136
+ console.error(
137
+ `[Frigg] Failed to restore integrations for entity ${persistedEntity.id} after successful re-auth — manual intervention may be needed`,
138
+ err
139
+ );
140
+ }
141
+
142
+ return {
143
+ credential_id: module.credential.id,
144
+ entity_id: persistedEntity.id,
145
+ type: module.getName(),
146
+ };
147
+ }
148
+
149
+ async restoreIntegrationsForEntity(entityId) {
150
+ if (!this.integrationRepository) return 0;
151
+ const integrations =
152
+ await this.integrationRepository.findIntegrationsByEntityId(
153
+ entityId
154
+ );
155
+ let restored = 0;
156
+ for (const integration of integrations) {
157
+ if (STATUSES_RESET_ON_REAUTH.includes(integration.status)) {
158
+ console.log(
159
+ `[Frigg] Restoring integration ${integration.id} from ${integration.status} to ENABLED after successful re-auth (entityId=${entityId})`
160
+ );
161
+ await this.integrationRepository.updateIntegrationStatus(
162
+ integration.id,
163
+ 'ENABLED'
164
+ );
165
+ restored++;
166
+ }
167
+ }
168
+ return restored;
169
+ }
170
+
171
+ async onTokenUpdate(module, moduleDefinition, userId) {
172
+ const credentialDetails =
173
+ await moduleDefinition.requiredAuthMethods.getCredentialDetails(
174
+ module.api,
175
+ userId
176
+ );
177
+
178
+ Object.assign(
179
+ credentialDetails.details,
180
+ module.apiParamsFromCredential(module.api)
181
+ );
182
+ credentialDetails.details.authIsValid = true;
183
+
184
+ const persisted = await this.credentialRepository.upsertCredential(credentialDetails);
185
+ module.credential = persisted;
186
+ }
187
+
188
+ async findOrCreateEntity(entityDetails, moduleName, credentialId) {
189
+ const { identifiers, details } = entityDetails;
190
+
191
+ // Support both 'user' and 'userId' field names from module definitions
192
+ // Some modules use 'user' (legacy), others use 'userId' (newer pattern)
193
+ const userId = identifiers.user || identifiers.userId;
194
+
195
+ if (!userId) {
196
+ throw new Error(
197
+ `Module definition for ${moduleName} must return 'user' or 'userId' in identifiers from getEntityDetails(). ` +
198
+ `Without userId, entity lookup would match across all users (security issue).`
199
+ );
200
+ }
201
+
202
+ const existingEntity = await this.moduleRepository.findEntity({
203
+ externalId: identifiers.externalId,
204
+ user: userId,
205
+ moduleName: moduleName,
206
+ });
207
+
208
+ if (existingEntity) {
209
+ // Repoint the entity's credentialId when re-auth produced a
210
+ // different credential than the one currently linked. This
211
+ // happens when the user re-authenticates against a different
212
+ // workspace/account of the same provider — `upsertCredential`
213
+ // matches/creates a credential keyed by externalId, but
214
+ // findEntity matches the entity by its own externalId, leaving
215
+ // the entity still linked to the prior workspace's credential
216
+ // unless we explicitly update the link.
217
+ const existingCredentialId = existingEntity.credential?.id;
218
+ if (
219
+ credentialId &&
220
+ String(existingCredentialId) !== String(credentialId)
221
+ ) {
222
+ console.log(
223
+ `[Frigg] Repointing entity ${existingEntity.id} credentialId ${existingCredentialId} -> ${credentialId} after re-auth`
224
+ );
225
+ const updated = await this.moduleRepository.updateEntity(
226
+ existingEntity.id,
227
+ { credential: credentialId }
228
+ );
229
+ if (updated) return updated;
230
+ }
231
+ return existingEntity;
232
+ }
233
+
234
+ return await this.moduleRepository.createEntity({
235
+ ...identifiers,
236
+ ...details,
237
+ moduleName: moduleName,
238
+ credential: credentialId,
239
+ });
240
+ }
241
+ }
242
+
243
+ module.exports = { ProcessAuthorizationCallback };
@@ -0,0 +1,72 @@
1
+ const { Module } = require('../module');
2
+
3
+ class RefreshEntityOptions {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
7
+ * @param {} params.moduleDefinitions
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ /**
15
+ * Refresh entity options for a given entity
16
+ *
17
+ * @param {string|number} entityId - Entity ID to refresh
18
+ * @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
19
+ * @param {Object} options - Refresh options
20
+ * @returns {Promise<Object>} Updated entity options
21
+ */
22
+ async execute(entityId, userIdOrUser, options) {
23
+ // Support both userId (backward compatible) and User object (new pattern)
24
+ const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
25
+ ? userIdOrUser.getId()
26
+ : userIdOrUser;
27
+
28
+ const entity = await this.moduleRepository.findEntityById(
29
+ entityId,
30
+ userId
31
+ );
32
+
33
+ if (!entity) {
34
+ throw new Error(`Entity ${entityId} not found`);
35
+ }
36
+
37
+ // Validate entity ownership
38
+ const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
39
+ ? userIdOrUser.ownsUserId(entity.userId)
40
+ : entity.userId?.toString() === userId?.toString();
41
+
42
+ if (!isOwned) {
43
+ throw new Error(
44
+ `Entity ${entityId} does not belong to user ${userId}`
45
+ );
46
+ }
47
+
48
+ const entityType = entity.moduleName;
49
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
50
+ const modelName =
51
+ Module.getEntityModelFromDefinition(def).modelName;
52
+ return entityType === modelName;
53
+ });
54
+
55
+ if (!moduleDefinition) {
56
+ throw new Error(
57
+ `Module definition not found for entity type: ${entityType}`
58
+ );
59
+ }
60
+
61
+ const module = new Module({
62
+ userId,
63
+ entity,
64
+ definition: moduleDefinition,
65
+ });
66
+
67
+ await module.refreshEntityOptions(options);
68
+ return module.getEntityOptions();
69
+ }
70
+ }
71
+
72
+ module.exports = { RefreshEntityOptions };