@friggframework/core 2.0.0-next.10 → 2.0.0-next.101

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 (344) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/admin-scripts/index.js +52 -0
  4. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +166 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +228 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +268 -0
  9. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  10. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  11. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  12. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  13. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  14. package/application/commands/README.md +451 -0
  15. package/application/commands/admin-script-commands.js +358 -0
  16. package/application/commands/credential-commands.js +245 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +343 -0
  19. package/application/commands/process-commands.js +135 -0
  20. package/application/commands/scheduler-commands.js +263 -0
  21. package/application/commands/user-commands.js +312 -0
  22. package/application/index.js +82 -0
  23. package/assertions/index.js +0 -3
  24. package/core/CLAUDE.md +690 -0
  25. package/core/Worker.js +60 -24
  26. package/core/create-handler.js +84 -6
  27. package/credential/repositories/credential-repository-documentdb.js +304 -0
  28. package/credential/repositories/credential-repository-factory.js +54 -0
  29. package/credential/repositories/credential-repository-interface.js +98 -0
  30. package/credential/repositories/credential-repository-mongo.js +269 -0
  31. package/credential/repositories/credential-repository-postgres.js +287 -0
  32. package/credential/repositories/credential-repository.js +300 -0
  33. package/credential/use-cases/get-credential-for-user.js +25 -0
  34. package/credential/use-cases/update-authentication-status.js +15 -0
  35. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  36. package/database/adapters/lambda-invoker.js +97 -0
  37. package/database/config.js +154 -0
  38. package/database/documentdb-encryption-service.js +330 -0
  39. package/database/documentdb-utils.js +136 -0
  40. package/database/encryption/README.md +839 -0
  41. package/database/encryption/documentdb-encryption-service.md +3575 -0
  42. package/database/encryption/encryption-schema-registry.js +401 -0
  43. package/database/encryption/field-encryption-service.js +254 -0
  44. package/database/encryption/logger.js +79 -0
  45. package/database/encryption/prisma-encryption-extension.js +230 -0
  46. package/database/index.js +21 -21
  47. package/database/prisma.js +182 -0
  48. package/database/repositories/health-check-repository-documentdb.js +138 -0
  49. package/database/repositories/health-check-repository-factory.js +48 -0
  50. package/database/repositories/health-check-repository-interface.js +82 -0
  51. package/database/repositories/health-check-repository-mongodb.js +89 -0
  52. package/database/repositories/health-check-repository-postgres.js +82 -0
  53. package/database/repositories/migration-status-repository-s3.js +137 -0
  54. package/database/use-cases/check-database-health-use-case.js +29 -0
  55. package/database/use-cases/check-database-state-use-case.js +81 -0
  56. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  57. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  58. package/database/use-cases/get-migration-status-use-case.js +93 -0
  59. package/database/use-cases/run-database-migration-use-case.js +139 -0
  60. package/database/use-cases/test-encryption-use-case.js +253 -0
  61. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  62. package/database/utils/mongodb-collection-utils.js +94 -0
  63. package/database/utils/mongodb-schema-init.js +108 -0
  64. package/database/utils/prisma-runner.js +477 -0
  65. package/database/utils/prisma-schema-parser.js +182 -0
  66. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  67. package/encrypt/Cryptor.js +34 -168
  68. package/encrypt/index.js +1 -2
  69. package/errors/client-safe-error.js +26 -0
  70. package/errors/fetch-error.js +15 -7
  71. package/errors/index.js +2 -0
  72. package/generated/prisma-mongodb/client.d.ts +1 -0
  73. package/generated/prisma-mongodb/client.js +5 -0
  74. package/generated/prisma-mongodb/default.d.ts +1 -0
  75. package/generated/prisma-mongodb/default.js +5 -0
  76. package/generated/prisma-mongodb/edge.d.ts +1 -0
  77. package/generated/prisma-mongodb/edge.js +374 -0
  78. package/generated/prisma-mongodb/index-browser.js +356 -0
  79. package/generated/prisma-mongodb/index.d.ts +25667 -0
  80. package/generated/prisma-mongodb/index.js +399 -0
  81. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  82. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  83. package/generated/prisma-mongodb/package.json +183 -0
  84. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  85. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  86. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  87. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  88. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  89. package/generated/prisma-mongodb/runtime/library.js +147 -0
  90. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  91. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  92. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  93. package/generated/prisma-mongodb/schema.prisma +432 -0
  94. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  95. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  96. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  97. package/generated/prisma-mongodb/wasm.js +381 -0
  98. package/generated/prisma-postgresql/client.d.ts +1 -0
  99. package/generated/prisma-postgresql/client.js +5 -0
  100. package/generated/prisma-postgresql/default.d.ts +1 -0
  101. package/generated/prisma-postgresql/default.js +5 -0
  102. package/generated/prisma-postgresql/edge.d.ts +1 -0
  103. package/generated/prisma-postgresql/edge.js +395 -0
  104. package/generated/prisma-postgresql/index-browser.js +377 -0
  105. package/generated/prisma-postgresql/index.d.ts +28455 -0
  106. package/generated/prisma-postgresql/index.js +420 -0
  107. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  108. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  109. package/generated/prisma-postgresql/package.json +183 -0
  110. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  111. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  112. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  113. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  114. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  115. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  116. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  117. package/generated/prisma-postgresql/runtime/library.js +147 -0
  118. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  119. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  120. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  121. package/generated/prisma-postgresql/schema.prisma +414 -0
  122. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  123. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  124. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  125. package/generated/prisma-postgresql/wasm.js +402 -0
  126. package/handlers/WEBHOOKS.md +653 -0
  127. package/handlers/app-definition-loader.js +43 -0
  128. package/handlers/app-handler-helpers.js +3 -3
  129. package/handlers/backend-utils.js +271 -42
  130. package/handlers/database-migration-handler.js +227 -0
  131. package/handlers/integration-event-dispatcher.js +54 -0
  132. package/handlers/middleware/admin-auth.js +73 -0
  133. package/handlers/routers/HEALTHCHECK.md +342 -0
  134. package/handlers/routers/auth.js +4 -15
  135. package/handlers/routers/db-migration.handler.js +29 -0
  136. package/handlers/routers/db-migration.js +311 -0
  137. package/handlers/routers/health.js +518 -0
  138. package/handlers/routers/integration-defined-routers.js +85 -10
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +27 -5
  142. package/handlers/routers/websocket.js +5 -3
  143. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  144. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  145. package/handlers/workers/db-migration.js +352 -0
  146. package/handlers/workers/dlq-processor.js +63 -0
  147. package/handlers/workers/integration-defined-workers.js +13 -7
  148. package/index.js +91 -46
  149. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  150. package/infrastructure/scheduler/index.js +33 -0
  151. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  152. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  153. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  154. package/integrations/EXTENSIONS.md +240 -0
  155. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  156. package/integrations/extension.js +254 -0
  157. package/integrations/index.js +20 -10
  158. package/integrations/integration-base.js +633 -72
  159. package/integrations/integration-router.js +398 -180
  160. package/integrations/options.js +1 -1
  161. package/integrations/repositories/config-patch-shared.js +43 -0
  162. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  163. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  164. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  165. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  166. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  167. package/integrations/repositories/integration-mapping-repository.js +156 -0
  168. package/integrations/repositories/integration-repository-documentdb.js +284 -0
  169. package/integrations/repositories/integration-repository-factory.js +51 -0
  170. package/integrations/repositories/integration-repository-interface.js +173 -0
  171. package/integrations/repositories/integration-repository-mongo.js +414 -0
  172. package/integrations/repositories/integration-repository-postgres.js +452 -0
  173. package/integrations/repositories/process-repository-documentdb.js +311 -0
  174. package/integrations/repositories/process-repository-factory.js +53 -0
  175. package/integrations/repositories/process-repository-interface.js +136 -0
  176. package/integrations/repositories/process-repository-mongo.js +262 -0
  177. package/integrations/repositories/process-repository-postgres.js +380 -0
  178. package/integrations/repositories/process-update-ops-shared.js +112 -0
  179. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  180. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  181. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  182. package/integrations/use-cases/create-integration.js +215 -0
  183. package/integrations/use-cases/create-process.js +130 -0
  184. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  185. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  186. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  187. package/integrations/use-cases/get-integration-for-user.js +78 -0
  188. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  189. package/integrations/use-cases/get-integration-instance.js +83 -0
  190. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  191. package/integrations/use-cases/get-possible-integrations.js +27 -0
  192. package/integrations/use-cases/get-process.js +89 -0
  193. package/integrations/use-cases/index.js +19 -0
  194. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  195. package/integrations/use-cases/load-integration-context.js +71 -0
  196. package/integrations/use-cases/patch-integration-config.js +39 -0
  197. package/integrations/use-cases/process-errors.js +28 -0
  198. package/integrations/use-cases/update-integration-config.js +32 -0
  199. package/integrations/use-cases/update-integration-messages.js +44 -0
  200. package/integrations/use-cases/update-integration-status.js +32 -0
  201. package/integrations/use-cases/update-integration.js +92 -0
  202. package/integrations/use-cases/update-process-metrics.js +216 -0
  203. package/integrations/use-cases/update-process-state.js +163 -0
  204. package/integrations/utils/map-integration-dto.js +37 -0
  205. package/jest-global-setup-noop.js +3 -0
  206. package/jest-global-teardown-noop.js +3 -0
  207. package/logs/logger.js +0 -4
  208. package/{module-plugin → modules}/index.js +0 -10
  209. package/modules/module-factory.js +56 -0
  210. package/modules/module.js +287 -0
  211. package/modules/repositories/module-repository-documentdb.js +350 -0
  212. package/modules/repositories/module-repository-factory.js +40 -0
  213. package/modules/repositories/module-repository-interface.js +145 -0
  214. package/modules/repositories/module-repository-mongo.js +436 -0
  215. package/modules/repositories/module-repository-postgres.js +481 -0
  216. package/modules/repositories/module-repository.js +369 -0
  217. package/modules/requester/api-key.js +52 -0
  218. package/modules/requester/oauth-2.js +396 -0
  219. package/modules/requester/requester.js +334 -0
  220. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  221. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  222. package/modules/tests/doubles/test-module-factory.js +16 -0
  223. package/modules/tests/doubles/test-module-repository.js +39 -0
  224. package/modules/use-cases/get-entities-for-user.js +32 -0
  225. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  226. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  227. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  228. package/modules/use-cases/get-module.js +74 -0
  229. package/modules/use-cases/process-authorization-callback.js +243 -0
  230. package/modules/use-cases/refresh-entity-options.js +72 -0
  231. package/modules/use-cases/test-module-auth.js +72 -0
  232. package/modules/utils/map-module-dto.js +18 -0
  233. package/package.json +82 -50
  234. package/prisma-mongodb/schema.prisma +432 -0
  235. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  236. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  237. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  238. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  239. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  240. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  241. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  242. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  243. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  244. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  245. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  246. package/prisma-postgresql/schema.prisma +414 -0
  247. package/queues/queuer-util.js +103 -21
  248. package/reporting/README.md +86 -0
  249. package/reporting/index.js +17 -0
  250. package/reporting/reporting-router.js +77 -0
  251. package/reporting/repositories/reporting-repository-documentdb.js +127 -0
  252. package/reporting/repositories/reporting-repository-factory.js +35 -0
  253. package/reporting/repositories/reporting-repository-interface.js +16 -0
  254. package/reporting/repositories/reporting-repository-mongo.js +54 -0
  255. package/reporting/repositories/reporting-repository-postgres.js +70 -0
  256. package/reporting/use-cases/index.js +6 -0
  257. package/reporting/use-cases/list-integrations-report.js +142 -0
  258. package/syncs/manager.js +468 -443
  259. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  260. package/syncs/repositories/sync-repository-factory.js +43 -0
  261. package/syncs/repositories/sync-repository-interface.js +109 -0
  262. package/syncs/repositories/sync-repository-mongo.js +239 -0
  263. package/syncs/repositories/sync-repository-postgres.js +319 -0
  264. package/syncs/sync.js +0 -1
  265. package/token/repositories/token-repository-documentdb.js +137 -0
  266. package/token/repositories/token-repository-factory.js +40 -0
  267. package/token/repositories/token-repository-interface.js +131 -0
  268. package/token/repositories/token-repository-mongo.js +219 -0
  269. package/token/repositories/token-repository-postgres.js +264 -0
  270. package/token/repositories/token-repository.js +219 -0
  271. package/types/associations/index.d.ts +0 -17
  272. package/types/core/index.d.ts +12 -4
  273. package/types/database/index.d.ts +10 -2
  274. package/types/encrypt/index.d.ts +5 -3
  275. package/types/integrations/index.d.ts +3 -8
  276. package/types/module-plugin/index.d.ts +17 -69
  277. package/types/syncs/index.d.ts +0 -17
  278. package/user/repositories/user-repository-documentdb.js +458 -0
  279. package/user/repositories/user-repository-factory.js +52 -0
  280. package/user/repositories/user-repository-interface.js +214 -0
  281. package/user/repositories/user-repository-mongo.js +323 -0
  282. package/user/repositories/user-repository-postgres.js +377 -0
  283. package/user/tests/doubles/test-user-repository.js +72 -0
  284. package/user/use-cases/authenticate-user.js +127 -0
  285. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  286. package/user/use-cases/create-individual-user.js +61 -0
  287. package/user/use-cases/create-organization-user.js +47 -0
  288. package/user/use-cases/create-token-for-user-id.js +30 -0
  289. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  290. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  291. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  292. package/user/use-cases/login-user.js +122 -0
  293. package/user/user.js +125 -0
  294. package/utils/backend-path.js +38 -0
  295. package/utils/index.js +6 -0
  296. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  297. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  298. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  299. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  300. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  301. package/websocket/repositories/websocket-connection-repository.js +161 -0
  302. package/assertions/is-equal.js +0 -17
  303. package/associations/model.js +0 -54
  304. package/database/models/IndividualUser.js +0 -76
  305. package/database/models/OrganizationUser.js +0 -29
  306. package/database/models/State.js +0 -9
  307. package/database/models/Token.js +0 -70
  308. package/database/models/UserModel.js +0 -7
  309. package/database/models/WebsocketConnection.js +0 -49
  310. package/database/mongo.js +0 -45
  311. package/database/mongoose.js +0 -5
  312. package/encrypt/Cryptor.test.js +0 -32
  313. package/encrypt/encrypt.js +0 -132
  314. package/encrypt/encrypt.test.js +0 -1069
  315. package/encrypt/test-encrypt.js +0 -107
  316. package/errors/base-error.test.js +0 -32
  317. package/errors/fetch-error.test.js +0 -79
  318. package/errors/halt-error.test.js +0 -11
  319. package/errors/validation-errors.test.js +0 -120
  320. package/handlers/routers/middleware/loadUser.js +0 -15
  321. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  322. package/integrations/create-frigg-backend.js +0 -31
  323. package/integrations/integration-factory.js +0 -251
  324. package/integrations/integration-mapping.js +0 -43
  325. package/integrations/integration-model.js +0 -46
  326. package/integrations/integration-user.js +0 -144
  327. package/integrations/test/integration-base.test.js +0 -144
  328. package/lambda/TimeoutCatcher.test.js +0 -68
  329. package/logs/logger.test.js +0 -76
  330. package/module-plugin/auther.js +0 -393
  331. package/module-plugin/credential.js +0 -22
  332. package/module-plugin/entity-manager.js +0 -70
  333. package/module-plugin/entity.js +0 -46
  334. package/module-plugin/manager.js +0 -169
  335. package/module-plugin/module-factory.js +0 -61
  336. package/module-plugin/requester/api-key.js +0 -36
  337. package/module-plugin/requester/oauth-2.js +0 -219
  338. package/module-plugin/requester/requester.js +0 -165
  339. package/module-plugin/requester/requester.test.js +0 -28
  340. package/module-plugin/test/auther.test.js +0 -97
  341. package/syncs/model.js +0 -62
  342. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  343. /package/{module-plugin → modules}/requester/basic.js +0 -0
  344. /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,343 @@
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
+ UpdateIntegrationConfig,
28
+ } = require('../../integrations/use-cases/update-integration-config');
29
+ const {
30
+ PatchIntegrationConfig,
31
+ } = require('../../integrations/use-cases/patch-integration-config');
32
+ const {
33
+ getModulesDefinitionFromIntegrationClasses,
34
+ } = require('../../integrations/utils/map-integration-dto');
35
+
36
+ const ERROR_CODE_MAP = {
37
+ ENTITY_NOT_FOUND: 401,
38
+ ENTITY_USER_NOT_FOUND: 401,
39
+ INTEGRATION_NOT_FOUND: 404,
40
+ EXTERNAL_ID_REQUIRED: 400,
41
+ TYPE_REQUIRED: 400,
42
+ INTEGRATION_RECORD_NOT_FOUND: 404,
43
+ };
44
+
45
+ function mapErrorToResponse(error) {
46
+ const status = ERROR_CODE_MAP[error?.code] || 500;
47
+ return {
48
+ error: status,
49
+ reason: error?.message,
50
+ code: error?.code,
51
+ };
52
+ }
53
+
54
+ function createIntegrationCommands({ integrationClass } = {}) {
55
+ // Always use Frigg's default repositories and use cases
56
+ const integrationRepository = createIntegrationRepository();
57
+
58
+ // Class-agnostic read commands. Available with or without an integrationClass
59
+ // so callers that operate across integration types (e.g. admin scripts) can
60
+ // look up integrations by id or list them by type/status.
61
+
62
+ /**
63
+ * Find a single integration record by id.
64
+ * @param {string} integrationId
65
+ * @returns {Promise<Object>} Integration record, or an error object.
66
+ */
67
+ async function findIntegrationById(integrationId) {
68
+ try {
69
+ return await integrationRepository.findIntegrationById(
70
+ integrationId
71
+ );
72
+ } catch (error) {
73
+ return mapErrorToResponse(error);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * List integrations, optionally filtered by config type and/or status.
79
+ * @param {Object} [filter={}]
80
+ * @param {string} [filter.type] - Integration type (config.type)
81
+ * @param {string} [filter.status] - Integration status
82
+ * @returns {Promise<Array|Object>} Array of integrations, or an error object.
83
+ */
84
+ async function listIntegrations(filter = {}) {
85
+ try {
86
+ return await integrationRepository.findIntegrations(filter);
87
+ } catch (error) {
88
+ return mapErrorToResponse(error);
89
+ }
90
+ }
91
+
92
+ // The remaining commands hydrate/modify integrations for a specific class.
93
+ if (!integrationClass) {
94
+ return { findIntegrationById, listIntegrations };
95
+ }
96
+
97
+ const moduleRepository = createModuleRepository();
98
+
99
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses([
100
+ integrationClass,
101
+ ]);
102
+
103
+ const moduleFactory = new ModuleFactory({
104
+ moduleRepository,
105
+ moduleDefinitions,
106
+ });
107
+
108
+ const loadIntegrationContextUseCase = new LoadIntegrationContextUseCase({
109
+ integrationRepository,
110
+ moduleRepository,
111
+ moduleFactory,
112
+ });
113
+
114
+ const findByExternalEntityIdUseCase =
115
+ new FindIntegrationContextByExternalEntityIdUseCase({
116
+ integrationRepository,
117
+ moduleRepository,
118
+ loadIntegrationContextUseCase: loadIntegrationContextUseCase,
119
+ });
120
+
121
+ const findIntegrationByEntityExternalIdUseCase =
122
+ new FindIntegrationByEntityExternalIdUseCase({
123
+ integrationRepository,
124
+ moduleRepository,
125
+ });
126
+
127
+ const listIntegrationsByEntityExternalIdUseCase =
128
+ new ListIntegrationsByEntityExternalIdUseCase({
129
+ integrationRepository,
130
+ moduleRepository,
131
+ });
132
+
133
+ const getIntegrationsForUserUseCase = new GetIntegrationsForUser({
134
+ integrationRepository,
135
+ integrationClasses: [integrationClass],
136
+ moduleFactory,
137
+ moduleRepository,
138
+ });
139
+
140
+ const createIntegrationUseCase = new CreateIntegration({
141
+ integrationRepository,
142
+ integrationClasses: [integrationClass],
143
+ moduleFactory,
144
+ });
145
+
146
+ const updateIntegrationConfigUseCase = new UpdateIntegrationConfig({
147
+ integrationRepository,
148
+ });
149
+
150
+ const patchIntegrationConfigUseCase = new PatchIntegrationConfig({
151
+ integrationRepository,
152
+ });
153
+
154
+ return {
155
+ findIntegrationById,
156
+ listIntegrations,
157
+
158
+ /**
159
+ * Find integration context by external entity ID and type
160
+ * @param {Object} params
161
+ * @param {string} params.externalId - External ID of the entity
162
+ * @param {string} params.type - Integration type (config.type)
163
+ * @returns {Promise<Object>} Integration context, entity, and record
164
+ */
165
+ async findIntegrationContextByExternalEntityId({ externalId, type }) {
166
+ try {
167
+ const result = await findByExternalEntityIdUseCase.execute({
168
+ externalId,
169
+ type,
170
+ });
171
+ return result;
172
+ } catch (error) {
173
+ return mapErrorToResponse(error);
174
+ }
175
+ },
176
+
177
+ /**
178
+ * Resolve an externalId (e.g. HubSpot portalId, Slack team_id) to a
179
+ * single integration ID. Throws on ambiguous resolution at either the
180
+ * entity or integration layer — cross-tenant routing is refused.
181
+ *
182
+ * @param {string|number} externalId - Provider's stable identifier.
183
+ * @param {string} [moduleName] - Disambiguates when multiple modules in
184
+ * the same app could carry colliding externalIds.
185
+ * @returns {Promise<string|null>} Integration ID, or null on no match.
186
+ * @throws {Error} On ambiguous resolution (multiple entities or
187
+ * multiple owning integrations).
188
+ */
189
+ async findIntegrationByEntityExternalId(externalId, moduleName) {
190
+ return findIntegrationByEntityExternalIdUseCase.execute({
191
+ externalId,
192
+ moduleName,
193
+ });
194
+ },
195
+
196
+ /**
197
+ * List all integration IDs whose module entities match an externalId.
198
+ * Use when one externalId is expected to map to multiple integrations
199
+ * (intentional fan-out). Does not throw on ambiguity.
200
+ *
201
+ * @param {string|number} externalId - Provider's stable identifier.
202
+ * @param {string} [moduleName] - Disambiguates across modules.
203
+ * @returns {Promise<Array<string>>} Array of integration IDs (possibly empty).
204
+ */
205
+ async listIntegrationsByEntityExternalId(externalId, moduleName) {
206
+ return listIntegrationsByEntityExternalIdUseCase.execute({
207
+ externalId,
208
+ moduleName,
209
+ });
210
+ },
211
+
212
+ async loadIntegrationContextById(integrationId) {
213
+ try {
214
+ const context = await loadIntegrationContextUseCase.execute({
215
+ integrationId,
216
+ });
217
+ return { context };
218
+ } catch (error) {
219
+ return mapErrorToResponse(error);
220
+ }
221
+ },
222
+
223
+ /**
224
+ * Find all integrations for a user
225
+ * @param {string} userId - User ID to search for
226
+ * @returns {Promise<Array>} Array of integration records
227
+ */
228
+ async findIntegrationsByUserId(userId) {
229
+ try {
230
+ const integrations =
231
+ await getIntegrationsForUserUseCase.execute(userId);
232
+ return integrations;
233
+ } catch (error) {
234
+ return mapErrorToResponse(error);
235
+ }
236
+ },
237
+
238
+ /**
239
+ * Create a new integration
240
+ * @param {Object} params
241
+ * @param {Array<string>} params.entityIds - Array of entity IDs
242
+ * @param {string} params.userId - User ID
243
+ * @param {Object} params.config - Integration configuration (must include type)
244
+ * @returns {Promise<Object>} Created integration object
245
+ */
246
+ async createIntegration({ entityIds, userId, config }) {
247
+ try {
248
+ const integration = await createIntegrationUseCase.execute(
249
+ entityIds,
250
+ userId,
251
+ config
252
+ );
253
+ return integration;
254
+ } catch (error) {
255
+ return mapErrorToResponse(error);
256
+ }
257
+ },
258
+
259
+ /**
260
+ * Update integration configuration
261
+ * @param {Object} params
262
+ * @param {string} params.integrationId - Integration ID
263
+ * @param {Object} params.config - Updated config object
264
+ * @returns {Promise<Object>} Updated integration
265
+ */
266
+ async updateIntegrationConfig({ integrationId, config }) {
267
+ try {
268
+ const integration = await updateIntegrationConfigUseCase.execute(
269
+ integrationId,
270
+ config
271
+ );
272
+ return integration;
273
+ } catch (error) {
274
+ return mapErrorToResponse(error);
275
+ }
276
+ },
277
+
278
+ /**
279
+ * Atomically merge a partial update into an integration's config
280
+ * @param {Object} params
281
+ * @param {string} params.integrationId - Integration ID
282
+ * @param {Object} params.patch - Keys to merge into the existing config
283
+ * @returns {Promise<Object>} Updated integration
284
+ */
285
+ async patchIntegrationConfig({ integrationId, patch }) {
286
+ try {
287
+ const integration = await patchIntegrationConfigUseCase.execute(
288
+ integrationId,
289
+ patch
290
+ );
291
+ return integration;
292
+ } catch (error) {
293
+ return mapErrorToResponse(error);
294
+ }
295
+ },
296
+
297
+ /**
298
+ * Delete an integration by ID
299
+ * @param {string} integrationId - Integration ID to delete
300
+ * @returns {Promise<Object>} Deletion result
301
+ */
302
+ async deleteIntegrationById(integrationId) {
303
+ try {
304
+ if (!integrationId) {
305
+ const error = new Error('integrationId is required');
306
+ error.code = 'INVALID_INTEGRATION_DATA';
307
+ throw error;
308
+ }
309
+
310
+ const deleted = await integrationRepository.deleteIntegrationById(integrationId);
311
+
312
+ if (!deleted) {
313
+ const error = new Error(`Integration ${integrationId} not found`);
314
+ error.code = 'INTEGRATION_NOT_FOUND';
315
+ return mapErrorToResponse(error);
316
+ }
317
+
318
+ return {
319
+ success: true,
320
+ integrationId,
321
+ message: 'Integration deleted successfully',
322
+ };
323
+ } catch (error) {
324
+ return mapErrorToResponse(error);
325
+ }
326
+ },
327
+ };
328
+ }
329
+
330
+ async function findIntegrationContextByExternalEntityId({
331
+ integrationClass,
332
+ externalId,
333
+ type,
334
+ } = {}) {
335
+ const commands = createIntegrationCommands({ integrationClass });
336
+
337
+ return commands.findIntegrationContextByExternalEntityId({ externalId, type });
338
+ }
339
+
340
+ module.exports = {
341
+ createIntegrationCommands,
342
+ findIntegrationContextByExternalEntityId,
343
+ };