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

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
@@ -0,0 +1,336 @@
1
+ const {
2
+ createModuleRepository,
3
+ } = require('../../modules/repositories/module-repository-factory');
4
+
5
+ const ERROR_CODE_MAP = {
6
+ ENTITY_NOT_FOUND: 404,
7
+ INVALID_ENTITY_DATA: 400,
8
+ };
9
+
10
+ function mapErrorToResponse(error) {
11
+ const status = ERROR_CODE_MAP[error?.code] || 500;
12
+ return {
13
+ error: status,
14
+ reason: error?.message,
15
+ code: error?.code,
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Create entity command factory
21
+ *
22
+ * NOTE: This is an internal API. Integration developers should use createFriggCommands() instead.
23
+ *
24
+ * @returns {Object} Entity command object with CRUD operations
25
+ */
26
+ function createEntityCommands() {
27
+ const moduleRepo = createModuleRepository();
28
+
29
+ return {
30
+ /**
31
+ * Create a new entity
32
+ * @param {Object} params
33
+ * @param {string} params.userId - User ID who owns this entity
34
+ * @param {string} params.externalId - External identifier from the API module
35
+ * @param {string} params.name - Entity name
36
+ * @param {string} params.moduleName - Module name (e.g., 'husbpot', 'frontify')
37
+ * @param {string} [params.credentialId] - Associated credential ID
38
+ * @returns {Promise<Object>} Created entity object
39
+ */
40
+ async createEntity({
41
+ userId,
42
+ externalId,
43
+ name,
44
+ moduleName,
45
+ credentialId,
46
+ } = {}) {
47
+ try {
48
+ if (!userId || !externalId || !moduleName) {
49
+ const error = new Error(
50
+ 'userId, externalId, and moduleName are required'
51
+ );
52
+ error.code = 'INVALID_ENTITY_DATA';
53
+ throw error;
54
+ }
55
+
56
+ const entityData = {
57
+ user: userId,
58
+ externalId,
59
+ name,
60
+ moduleName,
61
+ };
62
+
63
+ if (credentialId) {
64
+ entityData.credential = credentialId;
65
+ }
66
+
67
+ const entity = await moduleRepo.createEntity(entityData);
68
+
69
+ return {
70
+ id: entity.id,
71
+ userId: entity.userId,
72
+ externalId: entity.externalId,
73
+ name: entity.name,
74
+ moduleName: entity.moduleName,
75
+ credentialId: entity.credential?._id
76
+ ? entity.credential._id.toString()
77
+ : entity.credential,
78
+ };
79
+ } catch (error) {
80
+ return mapErrorToResponse(error);
81
+ }
82
+ },
83
+
84
+ /**
85
+ * Find an entity by filter criteria
86
+ * @param {Object} filter
87
+ * @param {string} [filter.externalId] - External ID to search for
88
+ * @param {string} [filter.userId] - User ID to search for
89
+ * @param {string} [filter.moduleName] - Module name to search for
90
+ * @returns {Promise<Object|null>} Entity object or null if not found
91
+ */
92
+ async findEntity(filter = {}) {
93
+ try {
94
+ if (
95
+ !filter.externalId &&
96
+ !filter.userId &&
97
+ !filter.moduleName
98
+ ) {
99
+ const error = new Error(
100
+ 'At least one filter criterion is required'
101
+ );
102
+ error.code = 'INVALID_ENTITY_DATA';
103
+ throw error;
104
+ }
105
+
106
+ const entity = await moduleRepo.findEntity(filter);
107
+
108
+ if (!entity) {
109
+ return null;
110
+ }
111
+
112
+ return {
113
+ id: entity.id,
114
+ userId: entity.userId,
115
+ externalId: entity.externalId,
116
+ name: entity.name,
117
+ moduleName: entity.moduleName,
118
+ credentialId: entity.credential?._id
119
+ ? entity.credential._id.toString()
120
+ : entity.credential,
121
+ };
122
+ } catch (error) {
123
+ return mapErrorToResponse(error);
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Find all entities for a user
129
+ * @param {string} userId - User ID to search for
130
+ * @returns {Promise<Array>} Array of entity objects
131
+ */
132
+ async findEntitiesByUserId(userId) {
133
+ try {
134
+ if (!userId) {
135
+ const error = new Error('userId is required');
136
+ error.code = 'INVALID_ENTITY_DATA';
137
+ throw error;
138
+ }
139
+
140
+ const entities = await moduleRepo.findEntitiesByUserId(userId);
141
+
142
+ return entities.map((entity) => ({
143
+ id: entity.id,
144
+ userId: entity.userId,
145
+ externalId: entity.externalId,
146
+ name: entity.name,
147
+ moduleName: entity.moduleName,
148
+ credentialId: entity.credential?._id
149
+ ? entity.credential._id.toString()
150
+ : entity.credential,
151
+ }));
152
+ } catch (error) {
153
+ if (error.code) {
154
+ return mapErrorToResponse(error);
155
+ }
156
+ // For find operations, return empty array on error instead of error object
157
+ return [];
158
+ }
159
+ },
160
+
161
+ /**
162
+ * Find entities by user ID and module name
163
+ * @param {string} userId - User ID to search for
164
+ * @param {string} moduleName - Module name to filter by
165
+ * @returns {Promise<Array>} Array of entity objects
166
+ */
167
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
168
+ try {
169
+ if (!userId || !moduleName) {
170
+ const error = new Error(
171
+ 'userId and moduleName are required'
172
+ );
173
+ error.code = 'INVALID_ENTITY_DATA';
174
+ throw error;
175
+ }
176
+
177
+ const entities =
178
+ await moduleRepo.findEntitiesByUserIdAndModuleName(
179
+ userId,
180
+ moduleName
181
+ );
182
+
183
+ return entities.map((entity) => ({
184
+ id: entity.id,
185
+ userId: entity.userId,
186
+ externalId: entity.externalId,
187
+ name: entity.name,
188
+ moduleName: entity.moduleName,
189
+ credentialId: entity.credential?._id
190
+ ? entity.credential._id.toString()
191
+ : entity.credential,
192
+ }));
193
+ } catch (error) {
194
+ if (error.code) {
195
+ return mapErrorToResponse(error);
196
+ }
197
+ return [];
198
+ }
199
+ },
200
+
201
+ /**
202
+ * Find an entity by ID
203
+ * @param {string} entityId - Entity ID to search for
204
+ * @returns {Promise<Object>} Entity object
205
+ */
206
+ async findEntityById(entityId) {
207
+ try {
208
+ if (!entityId) {
209
+ const error = new Error('entityId is required');
210
+ error.code = 'INVALID_ENTITY_DATA';
211
+ throw error;
212
+ }
213
+
214
+ const entity = await moduleRepo.findEntityById(entityId);
215
+
216
+ return {
217
+ id: entity.id,
218
+ userId: entity.userId,
219
+ externalId: entity.externalId,
220
+ name: entity.name,
221
+ moduleName: entity.moduleName,
222
+ credentialId: entity.credential?._id
223
+ ? entity.credential._id.toString()
224
+ : entity.credential,
225
+ };
226
+ } catch (error) {
227
+ return mapErrorToResponse(error);
228
+ }
229
+ },
230
+
231
+ /**
232
+ * Update an entity
233
+ * @param {string} entityId - Entity ID to update
234
+ * @param {Object} updates - Fields to update
235
+ * @returns {Promise<Object>} Updated entity object
236
+ */
237
+ async updateEntity(entityId, updates) {
238
+ try {
239
+ if (!entityId) {
240
+ const error = new Error('entityId is required');
241
+ error.code = 'INVALID_ENTITY_DATA';
242
+ throw error;
243
+ }
244
+
245
+ const entity = await moduleRepo.updateEntity(entityId, updates);
246
+
247
+ if (!entity) {
248
+ const error = new Error(`Entity ${entityId} not found`);
249
+ error.code = 'ENTITY_NOT_FOUND';
250
+ throw error;
251
+ }
252
+
253
+ return {
254
+ id: entity.id,
255
+ userId: entity.userId,
256
+ externalId: entity.externalId,
257
+ name: entity.name,
258
+ moduleName: entity.moduleName,
259
+ credentialId: entity.credential?._id
260
+ ? entity.credential._id.toString()
261
+ : entity.credential,
262
+ };
263
+ } catch (error) {
264
+ return mapErrorToResponse(error);
265
+ }
266
+ },
267
+
268
+ /**
269
+ * Delete an entity
270
+ * @param {string} entityId - Entity ID to delete
271
+ * @returns {Promise<Object>} Result object with success flag
272
+ */
273
+ async deleteEntity(entityId) {
274
+ try {
275
+ if (!entityId) {
276
+ const error = new Error('entityId is required');
277
+ error.code = 'INVALID_ENTITY_DATA';
278
+ throw error;
279
+ }
280
+
281
+ await moduleRepo.deleteEntity(entityId);
282
+
283
+ return { success: true };
284
+ } catch (error) {
285
+ return mapErrorToResponse(error);
286
+ }
287
+ },
288
+
289
+ /**
290
+ * Delete an entity by ID (alias for deleteEntity)
291
+ * @param {string} entityId - Entity ID to delete
292
+ * @returns {Promise<Object>} Result object with success flag
293
+ */
294
+ async deleteEntityById(entityId) {
295
+ try {
296
+ if (!entityId) {
297
+ const error = new Error('entityId is required');
298
+ error.code = 'INVALID_ENTITY_DATA';
299
+ throw error;
300
+ }
301
+
302
+ await moduleRepo.deleteEntity(entityId);
303
+
304
+ return { success: true };
305
+ } catch (error) {
306
+ return mapErrorToResponse(error);
307
+ }
308
+ },
309
+
310
+ /**
311
+ * Remove credential reference from an entity
312
+ * @param {string} entityId - Entity ID to update
313
+ * @returns {Promise<Object>} Result object with success flag
314
+ */
315
+ async unsetCredential(entityId) {
316
+ try {
317
+ if (!entityId) {
318
+ const error = new Error('entityId is required');
319
+ error.code = 'INVALID_ENTITY_DATA';
320
+ throw error;
321
+ }
322
+
323
+ const acknowledged = await moduleRepo.unsetCredential(entityId);
324
+
325
+ return { success: acknowledged };
326
+ } catch (error) {
327
+ return mapErrorToResponse(error);
328
+ }
329
+ },
330
+ };
331
+ }
332
+
333
+ module.exports = {
334
+ createEntityCommands,
335
+ ERROR_CODE_MAP,
336
+ };
@@ -0,0 +1,271 @@
1
+ const {
2
+ createIntegrationRepository,
3
+ } = require('../../integrations/repositories/integration-repository-factory');
4
+ const {
5
+ createModuleRepository,
6
+ } = require('../../modules/repositories/module-repository-factory');
7
+ const { ModuleFactory } = require('../../modules/module-factory');
8
+ const {
9
+ LoadIntegrationContextUseCase,
10
+ } = require('../../integrations/use-cases/load-integration-context');
11
+ const {
12
+ FindIntegrationContextByExternalEntityIdUseCase,
13
+ } = require('../../integrations/use-cases/find-integration-context-by-external-entity-id');
14
+ const {
15
+ FindIntegrationByEntityExternalIdUseCase,
16
+ } = require('../../integrations/use-cases/find-integration-by-entity-external-id');
17
+ const {
18
+ ListIntegrationsByEntityExternalIdUseCase,
19
+ } = require('../../integrations/use-cases/list-integrations-by-entity-external-id');
20
+ const {
21
+ GetIntegrationsForUser,
22
+ } = require('../../integrations/use-cases/get-integrations-for-user');
23
+ const {
24
+ CreateIntegration,
25
+ } = require('../../integrations/use-cases/create-integration');
26
+ const {
27
+ getModulesDefinitionFromIntegrationClasses,
28
+ } = require('../../integrations/utils/map-integration-dto');
29
+
30
+ const ERROR_CODE_MAP = {
31
+ ENTITY_NOT_FOUND: 401,
32
+ ENTITY_USER_NOT_FOUND: 401,
33
+ INTEGRATION_NOT_FOUND: 404,
34
+ EXTERNAL_ID_REQUIRED: 400,
35
+ TYPE_REQUIRED: 400,
36
+ INTEGRATION_RECORD_NOT_FOUND: 404,
37
+ };
38
+
39
+ function mapErrorToResponse(error) {
40
+ const status = ERROR_CODE_MAP[error?.code] || 500;
41
+ return {
42
+ error: status,
43
+ reason: error?.message,
44
+ code: error?.code,
45
+ };
46
+ }
47
+
48
+ function createIntegrationCommands({ integrationClass }) {
49
+ if (!integrationClass) {
50
+ throw new Error('integrationClass is required');
51
+ }
52
+
53
+ // Always use Frigg's default repositories and use cases
54
+ const integrationRepository = createIntegrationRepository();
55
+ const moduleRepository = createModuleRepository();
56
+
57
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses([
58
+ integrationClass,
59
+ ]);
60
+
61
+ const moduleFactory = new ModuleFactory({
62
+ moduleRepository,
63
+ moduleDefinitions,
64
+ });
65
+
66
+ const loadIntegrationContextUseCase = new LoadIntegrationContextUseCase({
67
+ integrationRepository,
68
+ moduleRepository,
69
+ moduleFactory,
70
+ });
71
+
72
+ const findByExternalEntityIdUseCase =
73
+ new FindIntegrationContextByExternalEntityIdUseCase({
74
+ integrationRepository,
75
+ moduleRepository,
76
+ loadIntegrationContextUseCase: loadIntegrationContextUseCase,
77
+ });
78
+
79
+ const findIntegrationByEntityExternalIdUseCase =
80
+ new FindIntegrationByEntityExternalIdUseCase({
81
+ integrationRepository,
82
+ moduleRepository,
83
+ });
84
+
85
+ const listIntegrationsByEntityExternalIdUseCase =
86
+ new ListIntegrationsByEntityExternalIdUseCase({
87
+ integrationRepository,
88
+ moduleRepository,
89
+ });
90
+
91
+ const getIntegrationsForUserUseCase = new GetIntegrationsForUser({
92
+ integrationRepository,
93
+ integrationClasses: [integrationClass],
94
+ moduleFactory,
95
+ moduleRepository,
96
+ });
97
+
98
+ const createIntegrationUseCase = new CreateIntegration({
99
+ integrationRepository,
100
+ integrationClasses: [integrationClass],
101
+ moduleFactory,
102
+ });
103
+
104
+ return {
105
+ /**
106
+ * Find integration context by external entity ID and type
107
+ * @param {Object} params
108
+ * @param {string} params.externalId - External ID of the entity
109
+ * @param {string} params.type - Integration type (config.type)
110
+ * @returns {Promise<Object>} Integration context, entity, and record
111
+ */
112
+ async findIntegrationContextByExternalEntityId({ externalId, type }) {
113
+ try {
114
+ const result = await findByExternalEntityIdUseCase.execute({
115
+ externalId,
116
+ type,
117
+ });
118
+ return result;
119
+ } catch (error) {
120
+ return mapErrorToResponse(error);
121
+ }
122
+ },
123
+
124
+ /**
125
+ * Resolve an externalId (e.g. HubSpot portalId, Slack team_id) to a
126
+ * single integration ID. Throws on ambiguous resolution at either the
127
+ * entity or integration layer — cross-tenant routing is refused.
128
+ *
129
+ * @param {string|number} externalId - Provider's stable identifier.
130
+ * @param {string} [moduleName] - Disambiguates when multiple modules in
131
+ * the same app could carry colliding externalIds.
132
+ * @returns {Promise<string|null>} Integration ID, or null on no match.
133
+ * @throws {Error} On ambiguous resolution (multiple entities or
134
+ * multiple owning integrations).
135
+ */
136
+ async findIntegrationByEntityExternalId(externalId, moduleName) {
137
+ return findIntegrationByEntityExternalIdUseCase.execute({
138
+ externalId,
139
+ moduleName,
140
+ });
141
+ },
142
+
143
+ /**
144
+ * List all integration IDs whose module entities match an externalId.
145
+ * Use when one externalId is expected to map to multiple integrations
146
+ * (intentional fan-out). Does not throw on ambiguity.
147
+ *
148
+ * @param {string|number} externalId - Provider's stable identifier.
149
+ * @param {string} [moduleName] - Disambiguates across modules.
150
+ * @returns {Promise<Array<string>>} Array of integration IDs (possibly empty).
151
+ */
152
+ async listIntegrationsByEntityExternalId(externalId, moduleName) {
153
+ return listIntegrationsByEntityExternalIdUseCase.execute({
154
+ externalId,
155
+ moduleName,
156
+ });
157
+ },
158
+
159
+ async loadIntegrationContextById(integrationId) {
160
+ try {
161
+ const context = await loadIntegrationContextUseCase.execute({
162
+ integrationId,
163
+ });
164
+ return { context };
165
+ } catch (error) {
166
+ return mapErrorToResponse(error);
167
+ }
168
+ },
169
+
170
+ /**
171
+ * Find all integrations for a user
172
+ * @param {string} userId - User ID to search for
173
+ * @returns {Promise<Array>} Array of integration records
174
+ */
175
+ async findIntegrationsByUserId(userId) {
176
+ try {
177
+ const integrations =
178
+ await getIntegrationsForUserUseCase.execute(userId);
179
+ return integrations;
180
+ } catch (error) {
181
+ return mapErrorToResponse(error);
182
+ }
183
+ },
184
+
185
+ /**
186
+ * Create a new integration
187
+ * @param {Object} params
188
+ * @param {Array<string>} params.entityIds - Array of entity IDs
189
+ * @param {string} params.userId - User ID
190
+ * @param {Object} params.config - Integration configuration (must include type)
191
+ * @returns {Promise<Object>} Created integration object
192
+ */
193
+ async createIntegration({ entityIds, userId, config }) {
194
+ try {
195
+ const integration = await createIntegrationUseCase.execute(
196
+ entityIds,
197
+ userId,
198
+ config
199
+ );
200
+ return integration;
201
+ } catch (error) {
202
+ return mapErrorToResponse(error);
203
+ }
204
+ },
205
+
206
+ /**
207
+ * Update integration configuration
208
+ * @param {Object} params
209
+ * @param {string} params.integrationId - Integration ID
210
+ * @param {Object} params.config - Updated config object
211
+ * @returns {Promise<Object>} Updated integration
212
+ */
213
+ async updateIntegrationConfig({ integrationId, config }) {
214
+ try {
215
+ const integration = await integrationRepository.updateIntegrationConfig(
216
+ integrationId,
217
+ config
218
+ );
219
+ return integration;
220
+ } catch (error) {
221
+ return mapErrorToResponse(error);
222
+ }
223
+ },
224
+
225
+ /**
226
+ * Delete an integration by ID
227
+ * @param {string} integrationId - Integration ID to delete
228
+ * @returns {Promise<Object>} Deletion result
229
+ */
230
+ async deleteIntegrationById(integrationId) {
231
+ try {
232
+ if (!integrationId) {
233
+ const error = new Error('integrationId is required');
234
+ error.code = 'INVALID_INTEGRATION_DATA';
235
+ throw error;
236
+ }
237
+
238
+ const deleted = await integrationRepository.deleteIntegrationById(integrationId);
239
+
240
+ if (!deleted) {
241
+ const error = new Error(`Integration ${integrationId} not found`);
242
+ error.code = 'INTEGRATION_NOT_FOUND';
243
+ return mapErrorToResponse(error);
244
+ }
245
+
246
+ return {
247
+ success: true,
248
+ integrationId,
249
+ message: 'Integration deleted successfully',
250
+ };
251
+ } catch (error) {
252
+ return mapErrorToResponse(error);
253
+ }
254
+ },
255
+ };
256
+ }
257
+
258
+ async function findIntegrationContextByExternalEntityId({
259
+ integrationClass,
260
+ externalId,
261
+ type,
262
+ } = {}) {
263
+ const commands = createIntegrationCommands({ integrationClass });
264
+
265
+ return commands.findIntegrationContextByExternalEntityId({ externalId, type });
266
+ }
267
+
268
+ module.exports = {
269
+ createIntegrationCommands,
270
+ findIntegrationContextByExternalEntityId,
271
+ };