@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,227 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ IntegrationMappingRepositoryInterface,
4
+ } = require('./integration-mapping-repository-interface');
5
+
6
+ /**
7
+ * PostgreSQL Integration Mapping Repository Adapter
8
+ * Handles persistence of integration mappings used for data transformation
9
+ *
10
+ * PostgreSQL-specific characteristics:
11
+ * - Uses Int IDs with autoincrement
12
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
13
+ * - All returned IDs are converted to strings for application layer consistency
14
+ */
15
+ class IntegrationMappingRepositoryPostgres extends IntegrationMappingRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Convert string ID to integer for PostgreSQL queries
23
+ * @private
24
+ * @param {string|number|null|undefined} id - ID to convert
25
+ * @returns {number|null|undefined} Integer ID or null/undefined
26
+ * @throws {Error} If ID cannot be converted to integer
27
+ */
28
+ _stringToInt(id) {
29
+ if (id === null || id === undefined) return id;
30
+ const parsed = parseInt(id, 10);
31
+ if (isNaN(parsed)) {
32
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
33
+ }
34
+ return parsed;
35
+ }
36
+
37
+ /**
38
+ * Convert any value to string (handles null/undefined)
39
+ * @private
40
+ * @param {*} value - Value to convert
41
+ * @returns {string|null|undefined} String value or null/undefined
42
+ */
43
+ _toString(value) {
44
+ if (value === null || value === undefined) return value;
45
+ return String(value);
46
+ }
47
+
48
+ /**
49
+ * Convert integer to string for application layer
50
+ * @private
51
+ * @param {number|null|undefined} id - Integer ID from database
52
+ * @returns {string|null|undefined} String ID or null/undefined
53
+ */
54
+ _intToString(id) {
55
+ if (id === null || id === undefined) return id;
56
+ return id.toString();
57
+ }
58
+
59
+ /**
60
+ * Legacy alias for _stringToInt (for backward compatibility)
61
+ * @private
62
+ */
63
+ _convertId(id) {
64
+ return this._stringToInt(id);
65
+ }
66
+
67
+ /**
68
+ * Convert mapping object IDs to strings
69
+ * @private
70
+ * @param {Object|null} mapping - Mapping object from database
71
+ * @returns {Object|null} Mapping with string IDs
72
+ */
73
+ _convertMappingIds(mapping) {
74
+ if (!mapping) return mapping;
75
+ return {
76
+ ...mapping,
77
+ id: this._intToString(mapping.id),
78
+ integrationId: this._intToString(mapping.integrationId),
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Find mapping by integration ID and source ID
84
+ * Replaces: IntegrationMapping.findBy(integrationId, sourceId)
85
+ *
86
+ * @param {string} integrationId - The integration ID (string from application layer)
87
+ * @param {string} sourceId - The source ID for lookup
88
+ * @returns {Promise<Object|null>} The mapping object with string IDs or null
89
+ */
90
+ async findMappingBy(integrationId, sourceId) {
91
+ const mapping = await this.prisma.integrationMapping.findFirst({
92
+ where: {
93
+ integrationId: this._stringToInt(integrationId),
94
+ sourceId: this._toString(sourceId),
95
+ },
96
+ });
97
+ return this._convertMappingIds(mapping);
98
+ }
99
+
100
+ /**
101
+ * Create or update a mapping
102
+ * Replaces: IntegrationMapping.upsert(integrationId, sourceId, mapping)
103
+ *
104
+ * @param {string} integrationId - The integration ID (string from application layer)
105
+ * @param {string} sourceId - The source ID for lookup
106
+ * @param {Object} mapping - The mapping data
107
+ * @returns {Promise<Object>} The created or updated mapping document with string IDs
108
+ */
109
+ async upsertMapping(integrationId, sourceId, mapping) {
110
+ const result = await this.prisma.integrationMapping.upsert({
111
+ where: {
112
+ integrationId_sourceId: {
113
+ integrationId: this._stringToInt(integrationId),
114
+ sourceId: this._toString(sourceId),
115
+ },
116
+ },
117
+ update: {
118
+ mapping,
119
+ },
120
+ create: {
121
+ integrationId: this._stringToInt(integrationId),
122
+ sourceId: this._toString(sourceId),
123
+ mapping,
124
+ },
125
+ });
126
+ return this._convertMappingIds(result);
127
+ }
128
+
129
+ /**
130
+ * Find all mappings for an integration
131
+ * Replaces: IntegrationMapping.find({ integration: integrationId })
132
+ *
133
+ * @param {string} integrationId - The integration ID (string from application layer)
134
+ * @returns {Promise<Array>} Array of mapping documents with string IDs
135
+ */
136
+ async findMappingsByIntegration(integrationId) {
137
+ const intIntegrationId = this._convertId(integrationId);
138
+ const mappings = await this.prisma.integrationMapping.findMany({
139
+ where: { integrationId: intIntegrationId },
140
+ });
141
+ return mappings.map((m) => this._convertMappingIds(m));
142
+ }
143
+
144
+ /**
145
+ * Delete a mapping by integration and source ID
146
+ * Replaces: IntegrationMapping.deleteOne({ integration, sourceId })
147
+ *
148
+ * @param {string} integrationId - The integration ID (string from application layer)
149
+ * @param {string} sourceId - The source ID
150
+ * @returns {Promise<Object>} The deletion result
151
+ */
152
+ async deleteMapping(integrationId, sourceId) {
153
+ try {
154
+ await this.prisma.integrationMapping.delete({
155
+ where: {
156
+ integrationId_sourceId: {
157
+ integrationId: this._stringToInt(integrationId),
158
+ sourceId: this._toString(sourceId),
159
+ },
160
+ },
161
+ });
162
+ return { acknowledged: true, deletedCount: 1 };
163
+ } catch (error) {
164
+ if (error.code === 'P2025') {
165
+ // Record not found
166
+ return { acknowledged: true, deletedCount: 0 };
167
+ }
168
+ throw error;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Delete all mappings for an integration
174
+ * Replaces: IntegrationMapping.deleteMany({ integration: integrationId })
175
+ *
176
+ * @param {string} integrationId - The integration ID (string from application layer)
177
+ * @returns {Promise<Object>} The deletion result
178
+ */
179
+ async deleteMappingsByIntegration(integrationId) {
180
+ const intIntegrationId = this._convertId(integrationId);
181
+ const result = await this.prisma.integrationMapping.deleteMany({
182
+ where: { integrationId: intIntegrationId },
183
+ });
184
+
185
+ return {
186
+ acknowledged: true,
187
+ deletedCount: result.count,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Find mapping by ID
193
+ * @param {string} id - Mapping ID (string from application layer)
194
+ * @returns {Promise<Object|null>} Mapping object with string IDs or null
195
+ */
196
+ async findMappingById(id) {
197
+ const intId = this._convertId(id);
198
+ const mapping = await this.prisma.integrationMapping.findUnique({
199
+ where: { id: intId },
200
+ });
201
+ return this._convertMappingIds(mapping);
202
+ }
203
+
204
+ /**
205
+ * Update mapping by ID
206
+ * @param {string} id - Mapping ID (string from application layer)
207
+ * @param {Object} updates - Fields to update (with string IDs from application layer)
208
+ * @returns {Promise<Object>} Updated mapping object with string IDs
209
+ */
210
+ async updateMapping(id, updates) {
211
+ const intId = this._convertId(id);
212
+
213
+ // Convert integrationId if present in updates
214
+ const data = { ...updates };
215
+ if (data.integrationId !== undefined) {
216
+ data.integrationId = this._convertId(data.integrationId);
217
+ }
218
+
219
+ const mapping = await this.prisma.integrationMapping.update({
220
+ where: { id: intId },
221
+ data,
222
+ });
223
+ return this._convertMappingIds(mapping);
224
+ }
225
+ }
226
+
227
+ module.exports = { IntegrationMappingRepositoryPostgres };
@@ -0,0 +1,156 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ IntegrationMappingRepositoryInterface,
4
+ } = require('./integration-mapping-repository-interface');
5
+
6
+ /**
7
+ * Prisma-based Integration Mapping Repository
8
+ * Handles persistence of integration mappings used for data transformation
9
+ *
10
+ * Works identically for both MongoDB and PostgreSQL:
11
+ * - MongoDB: String IDs with @db.ObjectId
12
+ * - PostgreSQL: Integer IDs with auto-increment
13
+ * - Both use same query patterns (no many-to-many differences)
14
+ *
15
+ * Migration from Mongoose:
16
+ * - Constructor injection of Prisma client
17
+ * - IntegrationMapping.findBy() → findFirst with where clause
18
+ * - IntegrationMapping.upsert() → Prisma upsert with unique constraint
19
+ * - mapping data stored in JSON field
20
+ */
21
+ class IntegrationMappingRepository extends IntegrationMappingRepositoryInterface {
22
+ constructor(prismaClient = prisma) {
23
+ super();
24
+ this.prisma = prismaClient; // Allow injection for testing
25
+ }
26
+
27
+ /**
28
+ * Find mapping by integration ID and source ID
29
+ * Replaces: IntegrationMapping.findBy(integrationId, sourceId)
30
+ *
31
+ * @param {string} integrationId - The integration ID
32
+ * @param {string} sourceId - The source ID for lookup
33
+ * @returns {Promise<Object|null>} The mapping object or null
34
+ */
35
+ async findMappingBy(integrationId, sourceId) {
36
+ return await this.prisma.integrationMapping.findFirst({
37
+ where: {
38
+ integrationId,
39
+ sourceId,
40
+ },
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Create or update a mapping
46
+ * Replaces: IntegrationMapping.upsert(integrationId, sourceId, mapping)
47
+ *
48
+ * @param {string} integrationId - The integration ID
49
+ * @param {string} sourceId - The source ID for lookup
50
+ * @param {Object} mapping - The mapping data
51
+ * @returns {Promise<Object>} The created or updated mapping document
52
+ */
53
+ async upsertMapping(integrationId, sourceId, mapping) {
54
+ return await this.prisma.integrationMapping.upsert({
55
+ where: {
56
+ integrationId_sourceId: {
57
+ integrationId,
58
+ sourceId,
59
+ },
60
+ },
61
+ update: {
62
+ mapping,
63
+ },
64
+ create: {
65
+ integrationId,
66
+ sourceId,
67
+ mapping,
68
+ },
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Find all mappings for an integration
74
+ * Replaces: IntegrationMapping.find({ integration: integrationId })
75
+ *
76
+ * @param {string} integrationId - The integration ID
77
+ * @returns {Promise<Array>} Array of mapping documents
78
+ */
79
+ async findMappingsByIntegration(integrationId) {
80
+ return await this.prisma.integrationMapping.findMany({
81
+ where: { integrationId },
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Delete a mapping by integration and source ID
87
+ * Replaces: IntegrationMapping.deleteOne({ integration, sourceId })
88
+ *
89
+ * @param {string} integrationId - The integration ID
90
+ * @param {string} sourceId - The source ID
91
+ * @returns {Promise<Object>} The deletion result
92
+ */
93
+ async deleteMapping(integrationId, sourceId) {
94
+ try {
95
+ await this.prisma.integrationMapping.delete({
96
+ where: {
97
+ integrationId_sourceId: {
98
+ integrationId,
99
+ sourceId,
100
+ },
101
+ },
102
+ });
103
+ return { acknowledged: true, deletedCount: 1 };
104
+ } catch (error) {
105
+ if (error.code === 'P2025') {
106
+ // Record not found
107
+ return { acknowledged: true, deletedCount: 0 };
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Delete all mappings for an integration
115
+ * Replaces: IntegrationMapping.deleteMany({ integration: integrationId })
116
+ *
117
+ * @param {string} integrationId - The integration ID
118
+ * @returns {Promise<Object>} The deletion result
119
+ */
120
+ async deleteMappingsByIntegration(integrationId) {
121
+ const result = await this.prisma.integrationMapping.deleteMany({
122
+ where: { integrationId },
123
+ });
124
+
125
+ return {
126
+ acknowledged: true,
127
+ deletedCount: result.count,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Find mapping by ID
133
+ * @param {string} id - Mapping ID
134
+ * @returns {Promise<Object|null>} Mapping object or null
135
+ */
136
+ async findMappingById(id) {
137
+ return await this.prisma.integrationMapping.findUnique({
138
+ where: { id },
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Update mapping by ID
144
+ * @param {string} id - Mapping ID
145
+ * @param {Object} updates - Fields to update
146
+ * @returns {Promise<Object>} Updated mapping object
147
+ */
148
+ async updateMapping(id, updates) {
149
+ return await this.prisma.integrationMapping.update({
150
+ where: { id },
151
+ data: updates,
152
+ });
153
+ }
154
+ }
155
+
156
+ module.exports = { IntegrationMappingRepository };
@@ -0,0 +1,284 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ toObjectId,
4
+ toObjectIdArray,
5
+ fromObjectId,
6
+ findMany,
7
+ findOne,
8
+ insertOne,
9
+ updateOne,
10
+ deleteOne,
11
+ } = require('../../database/documentdb-utils');
12
+ const {
13
+ IntegrationRepositoryInterface,
14
+ } = require('./integration-repository-interface');
15
+ const { validateConfigPatch } = require('./config-patch-shared');
16
+
17
+ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
18
+ constructor() {
19
+ super();
20
+ this.prisma = prisma;
21
+ }
22
+
23
+ async findIntegrationsByUserId(userId) {
24
+ const objectId = toObjectId(userId);
25
+ const filter = objectId ? { userId: objectId } : {};
26
+ const records = await findMany(this.prisma, 'Integration', filter);
27
+ return records.map((doc) => this._mapIntegration(doc));
28
+ }
29
+
30
+ async findIntegrations({ type, status } = {}) {
31
+ const filter = {};
32
+ if (type) {
33
+ filter['config.type'] = type;
34
+ }
35
+ if (status) {
36
+ filter.status = status;
37
+ }
38
+ const records = await findMany(this.prisma, 'Integration', filter);
39
+ return records.map((doc) => this._mapIntegration(doc));
40
+ }
41
+
42
+ async findIntegrationsByEntityId(entityId) {
43
+ const objectId = toObjectId(entityId);
44
+ if (!objectId) return [];
45
+ const records = await findMany(this.prisma, 'Integration', {
46
+ entityIds: objectId,
47
+ });
48
+ return records.map((doc) => this._mapIntegration(doc));
49
+ }
50
+
51
+ async deleteIntegrationById(integrationId) {
52
+ const objectId = toObjectId(integrationId);
53
+ if (!objectId) return { acknowledged: true, deletedCount: 0 };
54
+ const result = await deleteOne(this.prisma, 'Integration', { _id: objectId });
55
+ const deleted = result?.n ?? 0;
56
+ return { acknowledged: true, deletedCount: deleted };
57
+ }
58
+
59
+ async findIntegrationByName(name) {
60
+ const doc = await findOne(this.prisma, 'Integration', { 'config.type': name });
61
+ if (!doc) {
62
+ throw new Error(`Integration with name ${name} not found`);
63
+ }
64
+ return this._mapIntegration(doc);
65
+ }
66
+
67
+ async findIntegrationById(id) {
68
+ const objectId = toObjectId(id);
69
+ if (!objectId) {
70
+ throw new Error(`Integration with id ${id} not found`);
71
+ }
72
+ const doc = await findOne(this.prisma, 'Integration', { _id: objectId });
73
+ if (!doc) {
74
+ throw new Error(`Integration with id ${id} not found`);
75
+ }
76
+ return this._mapIntegration(doc);
77
+ }
78
+
79
+ async updateIntegrationStatus(integrationId, status) {
80
+ const objectId = toObjectId(integrationId);
81
+ if (!objectId) return false;
82
+ await updateOne(
83
+ this.prisma,
84
+ 'Integration',
85
+ { _id: objectId },
86
+ {
87
+ $set: { status, updatedAt: new Date() },
88
+ }
89
+ );
90
+ return true;
91
+ }
92
+
93
+ async updateIntegrationMessages(
94
+ integrationId,
95
+ messageType,
96
+ messageTitle,
97
+ messageBody,
98
+ messageTimestamp
99
+ ) {
100
+ const objectId = toObjectId(integrationId);
101
+ if (!objectId) {
102
+ throw new Error(`Integration ${integrationId} not found`);
103
+ }
104
+ const existing = await findOne(this.prisma, 'Integration', { _id: objectId });
105
+ if (!existing) {
106
+ throw new Error(`Integration ${integrationId} not found`);
107
+ }
108
+ const messages = this._extractMessages(existing);
109
+ const list = Array.isArray(messages[messageType]) ? [...messages[messageType]] : [];
110
+ list.push({
111
+ title: messageTitle ?? null,
112
+ message: messageBody,
113
+ timestamp: messageTimestamp,
114
+ });
115
+ const updatedMessages = { ...messages, [messageType]: list };
116
+ await updateOne(
117
+ this.prisma,
118
+ 'Integration',
119
+ { _id: objectId },
120
+ {
121
+ $set: {
122
+ messages: updatedMessages,
123
+ errors: updatedMessages.errors ?? [],
124
+ warnings: updatedMessages.warnings ?? [],
125
+ info: updatedMessages.info ?? [],
126
+ logs: updatedMessages.logs ?? [],
127
+ updatedAt: new Date(),
128
+ },
129
+ }
130
+ );
131
+ return true;
132
+ }
133
+
134
+ async createIntegration(entities, userId, config) {
135
+ const now = new Date();
136
+ const document = {
137
+ userId: toObjectId(userId) || null,
138
+ config,
139
+ version: '0.0.0',
140
+ status: 'IN_CREATION',
141
+ entityIds: toObjectIdArray(entities),
142
+ messages: { errors: [], warnings: [], info: [], logs: [] },
143
+ errors: [],
144
+ warnings: [],
145
+ info: [],
146
+ logs: [],
147
+ createdAt: now,
148
+ updatedAt: now,
149
+ };
150
+ const insertedId = await insertOne(this.prisma, 'Integration', document);
151
+ const created = await findOne(this.prisma, 'Integration', { _id: insertedId });
152
+ if (!created) {
153
+ console.error('[IntegrationRepositoryDocumentDB] Integration not found after insert', {
154
+ insertedId: fromObjectId(insertedId),
155
+ userId,
156
+ config,
157
+ });
158
+ throw new Error(
159
+ 'Failed to create integration: Document not found after insert. ' +
160
+ 'This indicates a database consistency issue.'
161
+ );
162
+ }
163
+ return this._mapIntegration(created);
164
+ }
165
+
166
+ async findIntegrationByUserId(userId) {
167
+ const objectId = toObjectId(userId);
168
+ if (!objectId) return null;
169
+ const doc = await findOne(this.prisma, 'Integration', { userId: objectId });
170
+ return doc ? this._mapIntegration(doc) : null;
171
+ }
172
+
173
+ async updateIntegrationConfig(integrationId, config) {
174
+ if (config === null || config === undefined) {
175
+ throw new Error('Config parameter is required');
176
+ }
177
+ const objectId = toObjectId(integrationId);
178
+ if (!objectId) {
179
+ throw new Error(`Integration with id ${integrationId} not found`);
180
+ }
181
+ await updateOne(
182
+ this.prisma,
183
+ 'Integration',
184
+ { _id: objectId },
185
+ {
186
+ $set: {
187
+ config,
188
+ updatedAt: new Date(),
189
+ },
190
+ }
191
+ );
192
+ const updated = await findOne(this.prisma, 'Integration', { _id: objectId });
193
+ if (!updated) {
194
+ console.error('[IntegrationRepositoryDocumentDB] Integration not found after update', {
195
+ integrationId: fromObjectId(objectId),
196
+ config,
197
+ });
198
+ throw new Error(
199
+ 'Failed to update integration: Document not found after update. ' +
200
+ 'This indicates a database consistency issue.'
201
+ );
202
+ }
203
+ return this._mapIntegration(updated);
204
+ }
205
+
206
+ /**
207
+ * Atomically merge a patch into the existing config with a per-key
208
+ * $set (config.<k> for each patch key), then re-read to shape the
209
+ * return value — DocumentDB's raw update command doesn't return the
210
+ * post-update document directly.
211
+ *
212
+ * @param {string} integrationId - Integration ID
213
+ * @param {Object} patch - Keys to merge into the existing config
214
+ * @returns {Promise<Object>} Updated integration object
215
+ */
216
+ async patchIntegrationConfig(integrationId, patch) {
217
+ validateConfigPatch(patch);
218
+ const objectId = toObjectId(integrationId);
219
+ if (!objectId) {
220
+ throw new Error(`Integration with id ${integrationId} not found`);
221
+ }
222
+
223
+ const $set = { updatedAt: new Date() };
224
+ for (const [key, value] of Object.entries(patch)) {
225
+ $set[`config.${key}`] = value;
226
+ }
227
+
228
+ const result = await updateOne(
229
+ this.prisma,
230
+ 'Integration',
231
+ { _id: objectId },
232
+ { $set }
233
+ );
234
+ if (result.writeErrors?.length) {
235
+ throw new Error(
236
+ `Failed to patch integration config: ${result.writeErrors[0].errmsg}`
237
+ );
238
+ }
239
+ if (!result.n) {
240
+ throw new Error(`Integration with id ${integrationId} not found`);
241
+ }
242
+
243
+ const updated = await findOne(this.prisma, 'Integration', { _id: objectId });
244
+ if (!updated) {
245
+ console.error('[IntegrationRepositoryDocumentDB] Integration not found after update', {
246
+ integrationId: fromObjectId(objectId),
247
+ patch,
248
+ });
249
+ throw new Error(
250
+ 'Failed to update integration: Document not found after update. ' +
251
+ 'This indicates a database consistency issue.'
252
+ );
253
+ }
254
+ return this._mapIntegration(updated);
255
+ }
256
+
257
+ _mapIntegration(doc) {
258
+ const messages = this._extractMessages(doc);
259
+ return {
260
+ id: fromObjectId(doc?._id),
261
+ entitiesIds: (doc?.entityIds || []).map((value) => fromObjectId(value)),
262
+ userId: fromObjectId(doc?.userId),
263
+ config: doc?.config ?? null,
264
+ version: doc?.version ?? null,
265
+ status: doc?.status ?? null,
266
+ messages,
267
+ createdAt: doc?.createdAt ?? null,
268
+ };
269
+ }
270
+
271
+ _extractMessages(doc) {
272
+ const base = doc?.messages && typeof doc.messages === 'object' ? doc.messages : {};
273
+ return {
274
+ errors: base.errors ?? doc?.errors ?? [],
275
+ warnings: base.warnings ?? doc?.warnings ?? [],
276
+ info: base.info ?? doc?.info ?? [],
277
+ logs: base.logs ?? doc?.logs ?? [],
278
+ };
279
+ }
280
+ }
281
+
282
+ module.exports = { IntegrationRepositoryDocumentDB };
283
+
284
+