@friggframework/core 2.0.0-next.1 → 2.0.0-next.100

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 (342) 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 +57 -0
  129. package/handlers/backend-utils.js +316 -0
  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 +15 -0
  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 +117 -0
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +63 -0
  142. package/handlers/routers/websocket.js +57 -0
  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 +30 -0
  148. package/index.js +111 -50
  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 +619 -72
  159. package/integrations/integration-router.js +460 -218
  160. package/integrations/options.js +3 -4
  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 +274 -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 +285 -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/integrations/create-frigg-backend.js +0 -31
  321. package/integrations/integration-factory.js +0 -251
  322. package/integrations/integration-mapping.js +0 -43
  323. package/integrations/integration-model.js +0 -46
  324. package/integrations/integration-user.js +0 -144
  325. package/integrations/test/integration-base.test.js +0 -144
  326. package/lambda/TimeoutCatcher.test.js +0 -68
  327. package/logs/logger.test.js +0 -76
  328. package/module-plugin/auther.js +0 -350
  329. package/module-plugin/credential.js +0 -22
  330. package/module-plugin/entity-manager.js +0 -70
  331. package/module-plugin/entity.js +0 -46
  332. package/module-plugin/manager.js +0 -169
  333. package/module-plugin/module-factory.js +0 -61
  334. package/module-plugin/requester/api-key.js +0 -36
  335. package/module-plugin/requester/oauth-2.js +0 -219
  336. package/module-plugin/requester/requester.js +0 -165
  337. package/module-plugin/requester/requester.test.js +0 -28
  338. package/module-plugin/test/auther.test.js +0 -97
  339. package/syncs/model.js +0 -62
  340. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  341. /package/{module-plugin → modules}/requester/basic.js +0 -0
  342. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,51 @@
1
+ const { IntegrationRepositoryMongo } = require('./integration-repository-mongo');
2
+ const { IntegrationRepositoryPostgres } = require('./integration-repository-postgres');
3
+ const {
4
+ IntegrationRepositoryDocumentDB,
5
+ } = require('./integration-repository-documentdb');
6
+ const config = require('../../database/config');
7
+
8
+ /**
9
+ * Integration Repository Factory
10
+ * Creates the appropriate repository adapter based on database type
11
+ *
12
+ * This implements the Factory pattern for Hexagonal Architecture:
13
+ * - Reads database type from app definition (backend/index.js)
14
+ * - Returns correct adapter (MongoDB or PostgreSQL)
15
+ * - Provides clear error for unsupported databases
16
+ *
17
+ * Usage:
18
+ * ```javascript
19
+ * const repository = createIntegrationRepository();
20
+ * ```
21
+ *
22
+ * @returns {IntegrationRepositoryInterface} Configured repository adapter
23
+ * @throws {Error} If database type is not supported
24
+ */
25
+ function createIntegrationRepository() {
26
+ const dbType = config.DB_TYPE;
27
+
28
+ switch (dbType) {
29
+ case 'mongodb':
30
+ return new IntegrationRepositoryMongo();
31
+
32
+ case 'postgresql':
33
+ return new IntegrationRepositoryPostgres();
34
+
35
+ case 'documentdb':
36
+ return new IntegrationRepositoryDocumentDB();
37
+
38
+ default:
39
+ throw new Error(
40
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
41
+ );
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ createIntegrationRepository,
47
+ // Export adapters for direct testing
48
+ IntegrationRepositoryMongo,
49
+ IntegrationRepositoryPostgres,
50
+ IntegrationRepositoryDocumentDB,
51
+ };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Integration Repository Interface
3
+ * Abstract base class defining the contract for integration persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters (MongoDB, PostgreSQL) implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * @abstract
11
+ */
12
+ class IntegrationRepositoryInterface {
13
+ /**
14
+ * Find all integrations for a user
15
+ *
16
+ * @param {string|number} userId - User ID
17
+ * @returns {Promise<Array>} Array of integration objects
18
+ * @abstract
19
+ */
20
+ async findIntegrationsByUserId(userId) {
21
+ throw new Error('Method findIntegrationsByUserId must be implemented by subclass');
22
+ }
23
+
24
+ /**
25
+ * Find integrations, optionally filtered by config type and/or status.
26
+ * With no filter, returns every integration.
27
+ *
28
+ * @param {Object} [filter={}]
29
+ * @param {string} [filter.type] - Integration type (config.type)
30
+ * @param {string} [filter.status] - Integration status
31
+ * @returns {Promise<Array>} Array of integration objects (possibly empty)
32
+ * @abstract
33
+ */
34
+ async findIntegrations(filter = {}) {
35
+ throw new Error('Method findIntegrations must be implemented by subclass');
36
+ }
37
+
38
+ /**
39
+ * Delete integration by ID
40
+ *
41
+ * @param {string|number} integrationId - Integration ID
42
+ * @returns {Promise<Object>} Deletion result
43
+ * @abstract
44
+ */
45
+ async deleteIntegrationById(integrationId) {
46
+ throw new Error('Method deleteIntegrationById must be implemented by subclass');
47
+ }
48
+
49
+ /**
50
+ * Find integration by name
51
+ *
52
+ * @param {string} name - Integration type name
53
+ * @returns {Promise<Object>} Integration object
54
+ * @abstract
55
+ */
56
+ async findIntegrationByName(name) {
57
+ throw new Error('Method findIntegrationByName must be implemented by subclass');
58
+ }
59
+
60
+ /**
61
+ * Find integration by ID
62
+ *
63
+ * @param {string|number} id - Integration ID
64
+ * @returns {Promise<Object>} Integration object
65
+ * @abstract
66
+ */
67
+ async findIntegrationById(id) {
68
+ throw new Error('Method findIntegrationById must be implemented by subclass');
69
+ }
70
+
71
+ /**
72
+ * Update integration status
73
+ *
74
+ * @param {string|number} integrationId - Integration ID
75
+ * @param {string} status - New status
76
+ * @returns {Promise<boolean>} Success indicator
77
+ * @abstract
78
+ */
79
+ async updateIntegrationStatus(integrationId, status) {
80
+ throw new Error('Method updateIntegrationStatus must be implemented by subclass');
81
+ }
82
+
83
+ /**
84
+ * Update integration messages
85
+ *
86
+ * @param {string|number} integrationId - Integration ID
87
+ * @param {string} messageType - Type of message (errors, warnings, info, logs)
88
+ * @param {string} messageTitle - Message title
89
+ * @param {string} messageBody - Message body
90
+ * @param {Date} messageTimestamp - Message timestamp
91
+ * @returns {Promise<boolean>} Success indicator
92
+ * @abstract
93
+ */
94
+ async updateIntegrationMessages(
95
+ integrationId,
96
+ messageType,
97
+ messageTitle,
98
+ messageBody,
99
+ messageTimestamp
100
+ ) {
101
+ throw new Error('Method updateIntegrationMessages must be implemented by subclass');
102
+ }
103
+
104
+ /**
105
+ * Create a new integration
106
+ *
107
+ * @param {Array<string|number>} entities - Array of entity IDs
108
+ * @param {string|number} userId - User ID
109
+ * @param {Object} config - Integration configuration
110
+ * @returns {Promise<Object>} Created integration object
111
+ * @abstract
112
+ */
113
+ async createIntegration(entities, userId, config) {
114
+ throw new Error('Method createIntegration must be implemented by subclass');
115
+ }
116
+
117
+ /**
118
+ * Find integration by user ID (returns single integration)
119
+ *
120
+ * @param {string|number} userId - User ID
121
+ * @returns {Promise<Object|null>} Integration object or null
122
+ * @abstract
123
+ */
124
+ async findIntegrationByUserId(userId) {
125
+ throw new Error('Method findIntegrationByUserId must be implemented by subclass');
126
+ }
127
+
128
+ /**
129
+ * Update integration configuration
130
+ *
131
+ * @param {string|number} integrationId - Integration ID
132
+ * @param {Object} config - Updated configuration object
133
+ * @returns {Promise<Object>} Updated integration object
134
+ * @abstract
135
+ */
136
+ async updateIntegrationConfig(integrationId, config) {
137
+ throw new Error('Method updateIntegrationConfig must be implemented by subclass');
138
+ }
139
+
140
+ /**
141
+ * Atomically merge a partial update into an integration's config. Keys
142
+ * not present in the patch are left untouched; concurrent patches with
143
+ * disjoint keys must both persist. Patch values may not be null or
144
+ * undefined — key deletion is only supported via updateIntegrationConfig.
145
+ *
146
+ * @param {string|number} integrationId - Integration ID
147
+ * @param {Object} patch - Keys to merge into the existing config
148
+ * @returns {Promise<Object>} Updated integration object
149
+ * @abstract
150
+ */
151
+ async patchIntegrationConfig(integrationId, patch) {
152
+ throw new Error('Method patchIntegrationConfig must be implemented by subclass');
153
+ }
154
+
155
+ /**
156
+ * Find all integrations whose entity set includes the given entity ID.
157
+ *
158
+ * Used by the authorization callback flow to walk up from a re-authorized
159
+ * entity to its parent integrations so that any in a broken state (ERROR,
160
+ * DISABLED) can be restored to ENABLED.
161
+ *
162
+ * @param {string|number} entityId - Entity ID
163
+ * @returns {Promise<Array>} Array of integration objects (possibly empty)
164
+ * @abstract
165
+ */
166
+ async findIntegrationsByEntityId(entityId) {
167
+ throw new Error(
168
+ 'Method findIntegrationsByEntityId must be implemented by subclass'
169
+ );
170
+ }
171
+ }
172
+
173
+ module.exports = { IntegrationRepositoryInterface };
@@ -0,0 +1,414 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ IntegrationRepositoryInterface,
4
+ } = require('./integration-repository-interface');
5
+ const { validateConfigPatch } = require('./config-patch-shared');
6
+
7
+ /**
8
+ * MongoDB Integration Repository Adapter
9
+ * Handles integration persistence using Prisma with MongoDB
10
+ *
11
+ * MongoDB-specific characteristics:
12
+ * - Uses scalar fields for relations (userId, entityIds)
13
+ * - IDs are strings with @db.ObjectId
14
+ * - Arrays used for many-to-many relationships
15
+ *
16
+ * Migration from Mongoose:
17
+ * - Constructor injection of Prisma client
18
+ * - populate() → include in Prisma queries
19
+ * - lean: true → No longer needed (Prisma returns plain objects)
20
+ * - toString() conversions → Done automatically by Prisma
21
+ */
22
+ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
23
+ constructor() {
24
+ super();
25
+ this.prisma = prisma;
26
+ }
27
+
28
+ /**
29
+ * Find all integrations for a user
30
+ * Replaces: IntegrationModel.find({ user: userId }).populate('entities')
31
+ *
32
+ * @param {string} userId - User ID (MongoDB ObjectId as string)
33
+ * @returns {Promise<Array>} Array of integration objects
34
+ */
35
+ async findIntegrationsByUserId(userId) {
36
+ const integrations = await this.prisma.integration.findMany({
37
+ where: { userId },
38
+ include: {
39
+ entities: true,
40
+ },
41
+ });
42
+
43
+ // Map to domain objects (maintains same API)
44
+ return integrations.map((integration) => ({
45
+ id: integration.id,
46
+ entitiesIds: integration.entities.map((e) => e.id),
47
+ userId: integration.userId,
48
+ config: integration.config,
49
+ version: integration.version,
50
+ status: integration.status,
51
+ messages: integration.messages,
52
+ createdAt: integration.createdAt,
53
+ }));
54
+ }
55
+
56
+ /**
57
+ * Find integrations, optionally filtered by config type and/or status.
58
+ *
59
+ * @param {Object} [filter={}]
60
+ * @param {string} [filter.type] - Integration type (config.type)
61
+ * @param {string} [filter.status] - Integration status
62
+ * @returns {Promise<Array>} Array of integration objects (possibly empty)
63
+ */
64
+ async findIntegrations({ type, status } = {}) {
65
+ const where = {};
66
+ if (type) {
67
+ where.config = { path: ['type'], equals: type };
68
+ }
69
+ if (status) {
70
+ where.status = status;
71
+ }
72
+
73
+ const integrations = await this.prisma.integration.findMany({
74
+ where,
75
+ include: {
76
+ entities: true,
77
+ },
78
+ });
79
+
80
+ return integrations.map((integration) => ({
81
+ id: integration.id,
82
+ entitiesIds: integration.entities.map((e) => e.id),
83
+ userId: integration.userId,
84
+ config: integration.config,
85
+ version: integration.version,
86
+ status: integration.status,
87
+ messages: integration.messages,
88
+ createdAt: integration.createdAt,
89
+ }));
90
+ }
91
+
92
+ /**
93
+ * Delete integration by ID
94
+ * Replaces: IntegrationModel.deleteOne({ _id: integrationId })
95
+ *
96
+ * @param {string} integrationId - Integration ID
97
+ * @returns {Promise<Object>} Deletion result
98
+ */
99
+ async deleteIntegrationById(integrationId) {
100
+ await this.prisma.integration.delete({
101
+ where: { id: integrationId },
102
+ });
103
+
104
+ // Return Mongoose-compatible result
105
+ return { acknowledged: true, deletedCount: 1 };
106
+ }
107
+
108
+ /**
109
+ * Find integration by name
110
+ * Replaces: IntegrationModel.findOne({ 'config.type': name }).populate('entities')
111
+ *
112
+ * @param {string} name - Integration type name
113
+ * @returns {Promise<Object>} Integration object
114
+ */
115
+ async findIntegrationByName(name) {
116
+ const integration = await this.prisma.integration.findFirst({
117
+ where: {
118
+ config: {
119
+ path: ['type'],
120
+ equals: name,
121
+ },
122
+ },
123
+ include: {
124
+ entities: true,
125
+ },
126
+ });
127
+
128
+ if (!integration) {
129
+ throw new Error(`Integration with name ${name} not found`);
130
+ }
131
+
132
+ return {
133
+ id: integration.id,
134
+ entitiesIds: integration.entities.map((e) => e.id),
135
+ userId: integration.userId,
136
+ config: integration.config,
137
+ version: integration.version,
138
+ status: integration.status,
139
+ messages: integration.messages,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Find integration by ID
145
+ * Replaces: IntegrationModel.findById(id).populate('entities')
146
+ *
147
+ * @param {string} id - Integration ID
148
+ * @returns {Promise<Object>} Integration object
149
+ */
150
+ async findIntegrationById(id) {
151
+ const integration = await this.prisma.integration.findUnique({
152
+ where: { id },
153
+ include: {
154
+ entities: true,
155
+ },
156
+ });
157
+
158
+ if (!integration) {
159
+ throw new Error(`Integration with id ${id} not found`);
160
+ }
161
+
162
+ return {
163
+ id: integration.id,
164
+ entitiesIds: integration.entities.map((e) => e.id),
165
+ userId: integration.userId,
166
+ config: integration.config,
167
+ version: integration.version,
168
+ status: integration.status,
169
+ messages: integration.messages,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Update integration status
175
+ * Replaces: IntegrationModel.updateOne({ _id: integrationId }, { status })
176
+ *
177
+ * @param {string} integrationId - Integration ID
178
+ * @param {string} status - New status
179
+ * @returns {Promise<boolean>} Success indicator
180
+ */
181
+ async updateIntegrationStatus(integrationId, status) {
182
+ await this.prisma.integration.update({
183
+ where: { id: integrationId },
184
+ data: { status },
185
+ });
186
+
187
+ return true; // Mongoose compatibility
188
+ }
189
+
190
+ /**
191
+ * Update integration messages
192
+ * Replaces: IntegrationModel.updateOne with $push operator
193
+ *
194
+ * @param {string} integrationId - Integration ID
195
+ * @param {string} messageType - Type of message (errors, warnings, info, logs)
196
+ * @param {string} messageTitle - Message title
197
+ * @param {string} messageBody - Message body
198
+ * @param {Date} messageTimestamp - Message timestamp
199
+ * @returns {Promise<boolean>} Success indicator
200
+ */
201
+ async updateIntegrationMessages(
202
+ integrationId,
203
+ messageType,
204
+ messageTitle,
205
+ messageBody,
206
+ messageTimestamp
207
+ ) {
208
+ // Get current integration
209
+ const integration = await this.prisma.integration.findUnique({
210
+ where: { id: integrationId },
211
+ });
212
+
213
+ if (!integration) {
214
+ throw new Error(`Integration ${integrationId} not found`);
215
+ }
216
+
217
+ // Parse existing messages (JSON field)
218
+ const messages = integration.messages || {};
219
+ const messageArray = Array.isArray(messages[messageType])
220
+ ? messages[messageType]
221
+ : [];
222
+
223
+ // Add new message
224
+ messageArray.push({
225
+ title: messageTitle,
226
+ message: messageBody,
227
+ timestamp: messageTimestamp,
228
+ });
229
+
230
+ // Update messages
231
+ await this.prisma.integration.update({
232
+ where: { id: integrationId },
233
+ data: {
234
+ [messageType]: messageArray,
235
+ },
236
+ });
237
+
238
+ return true; // Mongoose compatibility
239
+ }
240
+
241
+ /**
242
+ * Find all integrations whose entity set includes the given entity ID.
243
+ *
244
+ * @param {string} entityId - Entity ID (MongoDB ObjectId as string)
245
+ * @returns {Promise<Array>} Array of integration objects (possibly empty)
246
+ */
247
+ async findIntegrationsByEntityId(entityId) {
248
+ const integrations = await this.prisma.integration.findMany({
249
+ where: {
250
+ entityIds: { has: entityId },
251
+ },
252
+ include: {
253
+ entities: true,
254
+ },
255
+ });
256
+
257
+ return integrations.map((integration) => ({
258
+ id: integration.id,
259
+ entitiesIds: integration.entities.map((e) => e.id),
260
+ userId: integration.userId,
261
+ config: integration.config,
262
+ version: integration.version,
263
+ status: integration.status,
264
+ messages: integration.messages,
265
+ }));
266
+ }
267
+
268
+ /**
269
+ * Create a new integration
270
+ * Replaces: IntegrationModel.create({ entities, user, config })
271
+ *
272
+ * MongoDB-specific: Uses scalar fields for relations
273
+ *
274
+ * @param {Array<string>} entities - Array of entity IDs (MongoDB ObjectIds)
275
+ * @param {string} userId - User ID (MongoDB ObjectId)
276
+ * @param {Object} config - Integration configuration
277
+ * @returns {Promise<Object>} Created integration object
278
+ */
279
+ async createIntegration(entities, userId, config) {
280
+ const data = {
281
+ config,
282
+ version: '0.0.0',
283
+ userId: userId,
284
+ entityIds: entities,
285
+ };
286
+
287
+ const integration = await this.prisma.integration.create({
288
+ data,
289
+ include: {
290
+ entities: true,
291
+ },
292
+ });
293
+
294
+ return {
295
+ id: integration.id,
296
+ entitiesIds: integration.entities.map((e) => e.id),
297
+ userId: integration.userId,
298
+ config: integration.config,
299
+ version: integration.version,
300
+ status: integration.status,
301
+ messages: integration.messages,
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Find integration by user ID (returns single integration)
307
+ * Replaces: IntegrationModel.findOne({ user: userId }).populate('entities')
308
+ *
309
+ * @param {string} userId - User ID
310
+ * @returns {Promise<Object|null>} Integration object or null
311
+ */
312
+ async findIntegrationByUserId(userId) {
313
+ const integration = await this.prisma.integration.findFirst({
314
+ where: { userId },
315
+ include: {
316
+ entities: true,
317
+ },
318
+ });
319
+
320
+ if (!integration) {
321
+ return null;
322
+ }
323
+
324
+ return {
325
+ id: integration.id,
326
+ entitiesIds: integration.entities.map((e) => e.id),
327
+ userId: integration.userId,
328
+ config: integration.config,
329
+ version: integration.version,
330
+ status: integration.status,
331
+ messages: integration.messages,
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Update integration configuration
337
+ * Replaces: IntegrationModel.updateOne({ _id: integrationId }, { config })
338
+ *
339
+ * @param {string} integrationId - Integration ID (MongoDB ObjectId as string)
340
+ * @param {Object} config - Updated configuration object
341
+ * @returns {Promise<Object>} Updated integration object
342
+ */
343
+ async updateIntegrationConfig(integrationId, config) {
344
+ if (config === null || config === undefined) {
345
+ throw new Error('Config parameter is required');
346
+ }
347
+
348
+ const integration = await this.prisma.integration.update({
349
+ where: { id: integrationId },
350
+ data: { config },
351
+ include: {
352
+ entities: true,
353
+ },
354
+ });
355
+
356
+ return {
357
+ id: integration.id,
358
+ entitiesIds: integration.entities.map((e) => e.id),
359
+ userId: integration.userId,
360
+ config: integration.config,
361
+ version: integration.version,
362
+ status: integration.status,
363
+ messages: integration.messages,
364
+ };
365
+ }
366
+
367
+ /**
368
+ * Atomically merge a patch into the existing config via findAndModify,
369
+ * so the write and the post-write read happen in one server-side round
370
+ * trip with no JS-side read-modify-write to race on. entityIds is a
371
+ * scalar array directly on the Integration document in Mongo, so the
372
+ * raw document already carries everything needed to shape the return
373
+ * value — no follow-up findUnique.
374
+ *
375
+ * @param {string} integrationId - Integration ID
376
+ * @param {Object} patch - Keys to merge into the existing config
377
+ * @returns {Promise<Object>} Updated integration object
378
+ */
379
+ async patchIntegrationConfig(integrationId, patch) {
380
+ validateConfigPatch(patch);
381
+
382
+ const $set = {};
383
+ for (const [key, value] of Object.entries(patch)) {
384
+ $set[`config.${key}`] = value;
385
+ }
386
+ $set.updatedAt = new Date();
387
+
388
+ const result = await this.prisma.$runCommandRaw({
389
+ findAndModify: 'Integration',
390
+ query: { _id: { $oid: integrationId } },
391
+ update: { $set },
392
+ new: true,
393
+ });
394
+
395
+ const doc = result && result.value;
396
+ if (!doc) {
397
+ throw new Error(`Integration with id ${integrationId} not found`);
398
+ }
399
+
400
+ return {
401
+ id: doc._id.$oid ?? doc._id,
402
+ entitiesIds: (doc.entityIds || []).map(
403
+ (entityId) => entityId.$oid ?? entityId
404
+ ),
405
+ userId: doc.userId?.$oid ?? doc.userId ?? null,
406
+ config: doc.config,
407
+ version: doc.version,
408
+ status: doc.status,
409
+ messages: doc.messages,
410
+ };
411
+ }
412
+ }
413
+
414
+ module.exports = { IntegrationRepositoryMongo };