@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
@@ -1,6 +1,5 @@
1
1
  const { RequiredPropertyError } = require('../errors');
2
- const { get, getAndVerifyType } = require('../assertions');
3
- const { ModuleManager } = require('../module-plugin');
2
+ const { get } = require('../assertions');
4
3
 
5
4
  class Options {
6
5
  constructor(params) {
@@ -18,7 +17,7 @@ class Options {
18
17
  }
19
18
 
20
19
  this.display = {};
21
- this.display.name = get(params.display, 'name');
20
+ this.display.name = get(params.display, 'label');
22
21
  this.display.description = get(params.display, 'description');
23
22
  this.display.detailsUrl = get(params.display, 'detailsUrl');
24
23
  this.display.icon = get(params.display, 'icon');
@@ -26,7 +25,7 @@ class Options {
26
25
 
27
26
  get() {
28
27
  return {
29
- type: this.module.getName(),
28
+ type: this.module.definition.getName(),
30
29
 
31
30
  // Flag for if the User can configure any settings
32
31
  hasUserConfig: this.hasUserConfig,
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared validation for IntegrationRepository.patchIntegrationConfig().
3
+ *
4
+ * A patch is an atomic shallow merge into Integration.config: every key in
5
+ * the patch is written, every other existing key is left untouched. This
6
+ * contract is enforced here, before any adapter emits a query, so a bug fix
7
+ * (or a tighter rule) fixes all three backends in one place.
8
+ *
9
+ * Keys may not contain '.' or start with '$' — both are reserved by the
10
+ * MongoDB/DocumentDB update-command syntax the mongo and documentdb adapters
11
+ * use to address `config.<key>` paths. A value of `null` is allowed and sets
12
+ * the key to null — the only way to clear a config field over the PATCH HTTP
13
+ * endpoint (it sets the value, it does not remove the key). `undefined` is
14
+ * rejected: it can't arrive over JSON and signals a programming error, not an
15
+ * intent. Removing a key entirely still requires a full replace via updateConfig.
16
+ */
17
+ function validateConfigPatch(patch) {
18
+ if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
19
+ throw new Error('patchIntegrationConfig: patch must be a non-null object');
20
+ }
21
+
22
+ const keys = Object.keys(patch);
23
+ if (keys.length === 0) {
24
+ throw new Error(
25
+ 'patchIntegrationConfig: patch must contain at least one key'
26
+ );
27
+ }
28
+
29
+ for (const key of keys) {
30
+ if (key.includes('.') || key.startsWith('$')) {
31
+ throw new Error(
32
+ `patchIntegrationConfig: patch key '${key}' cannot contain '.' or start with '$'`
33
+ );
34
+ }
35
+ if (patch[key] === undefined) {
36
+ throw new Error(
37
+ `patchIntegrationConfig: patch['${key}'] cannot be undefined`
38
+ );
39
+ }
40
+ }
41
+ }
42
+
43
+ module.exports = { validateConfigPatch };
@@ -0,0 +1,280 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ toObjectId,
4
+ fromObjectId,
5
+ findMany,
6
+ findOne,
7
+ insertOne,
8
+ updateOne,
9
+ deleteOne,
10
+ deleteMany,
11
+ } = require('../../database/documentdb-utils');
12
+ const {
13
+ IntegrationMappingRepositoryInterface,
14
+ } = require('./integration-mapping-repository-interface');
15
+ const {
16
+ DocumentDBEncryptionService,
17
+ } = require('../../database/documentdb-encryption-service');
18
+ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositoryInterface {
19
+ constructor() {
20
+ super();
21
+ this.prisma = prisma;
22
+ this.encryptionService = new DocumentDBEncryptionService();
23
+ }
24
+
25
+ async findMappingBy(integrationId, sourceId) {
26
+ const filter = this._compositeFilter(integrationId, sourceId);
27
+ const doc = await findOne(this.prisma, 'IntegrationMapping', filter);
28
+ if (!doc) return null;
29
+
30
+ const decryptedMapping = await this.encryptionService.decryptFields(
31
+ 'IntegrationMapping',
32
+ doc
33
+ );
34
+ return this._mapMapping(decryptedMapping);
35
+ }
36
+
37
+ async upsertMapping(integrationId, sourceId, mapping) {
38
+ const filter = this._compositeFilter(integrationId, sourceId);
39
+ const existing = await findOne(
40
+ this.prisma,
41
+ 'IntegrationMapping',
42
+ filter
43
+ );
44
+ const now = new Date();
45
+
46
+ if (existing) {
47
+ const decryptedExisting =
48
+ await this.encryptionService.decryptFields(
49
+ 'IntegrationMapping',
50
+ existing
51
+ );
52
+
53
+ const updateDocument = {
54
+ mapping,
55
+ updatedAt: now,
56
+ };
57
+
58
+ const encryptedUpdate = await this.encryptionService.encryptFields(
59
+ 'IntegrationMapping',
60
+ { mapping: updateDocument.mapping }
61
+ );
62
+
63
+ await updateOne(
64
+ this.prisma,
65
+ 'IntegrationMapping',
66
+ { _id: existing._id },
67
+ {
68
+ $set: {
69
+ mapping: encryptedUpdate.mapping,
70
+ updatedAt: updateDocument.updatedAt,
71
+ },
72
+ }
73
+ );
74
+
75
+ const updated = await findOne(this.prisma, 'IntegrationMapping', {
76
+ _id: existing._id,
77
+ });
78
+ if (!updated) {
79
+ console.error(
80
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after update',
81
+ {
82
+ mappingId: fromObjectId(existing._id),
83
+ integrationId,
84
+ sourceId,
85
+ }
86
+ );
87
+ throw new Error(
88
+ 'Failed to update mapping: Document not found after update. ' +
89
+ 'This indicates a database consistency issue.'
90
+ );
91
+ }
92
+ const decryptedMapping = await this.encryptionService.decryptFields(
93
+ 'IntegrationMapping',
94
+ updated
95
+ );
96
+ return this._mapMapping(decryptedMapping);
97
+ }
98
+
99
+ const plainDocument = {
100
+ integrationId: integrationId,
101
+ sourceId:
102
+ sourceId === null || sourceId === undefined
103
+ ? null
104
+ : String(sourceId),
105
+ mapping,
106
+ createdAt: now,
107
+ updatedAt: now,
108
+ };
109
+
110
+ const encryptedDocument = await this.encryptionService.encryptFields(
111
+ 'IntegrationMapping',
112
+ plainDocument
113
+ );
114
+
115
+ const insertedId = await insertOne(
116
+ this.prisma,
117
+ 'IntegrationMapping',
118
+ encryptedDocument
119
+ );
120
+
121
+ const created = await findOne(this.prisma, 'IntegrationMapping', {
122
+ _id: insertedId,
123
+ });
124
+ if (!created) {
125
+ console.error(
126
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after insert',
127
+ {
128
+ insertedId: fromObjectId(insertedId),
129
+ integrationId,
130
+ sourceId,
131
+ }
132
+ );
133
+ throw new Error(
134
+ 'Failed to create mapping: Document not found after insert. ' +
135
+ 'This indicates a database consistency issue.'
136
+ );
137
+ }
138
+ const decryptedMapping = await this.encryptionService.decryptFields(
139
+ 'IntegrationMapping',
140
+ created
141
+ );
142
+ return this._mapMapping(decryptedMapping);
143
+ }
144
+
145
+ async findMappingsByIntegration(integrationId) {
146
+ const filter = {};
147
+ if (integrationId) filter.integrationId = integrationId;
148
+ const docs = await findMany(this.prisma, 'IntegrationMapping', filter);
149
+
150
+ const decryptedDocs = await Promise.all(
151
+ docs.map((doc) =>
152
+ this.encryptionService.decryptFields('IntegrationMapping', doc)
153
+ )
154
+ );
155
+
156
+ return decryptedDocs.map((doc) => this._mapMapping(doc));
157
+ }
158
+
159
+ async deleteMapping(integrationId, sourceId) {
160
+ const filter = this._compositeFilter(integrationId, sourceId);
161
+ const result = await deleteOne(
162
+ this.prisma,
163
+ 'IntegrationMapping',
164
+ filter
165
+ );
166
+ const deleted = result?.n ?? 0;
167
+ return { acknowledged: true, deletedCount: deleted };
168
+ }
169
+
170
+ async deleteMappingsByIntegration(integrationId) {
171
+ if (!integrationId) {
172
+ return { acknowledged: true, deletedCount: 0 };
173
+ }
174
+ const result = await deleteMany(this.prisma, 'IntegrationMapping', {
175
+ integrationId: integrationId,
176
+ });
177
+ const deleted = result?.n ?? 0;
178
+ return { acknowledged: true, deletedCount: deleted };
179
+ }
180
+
181
+ async findMappingById(id) {
182
+ const objectId = toObjectId(id);
183
+ if (!objectId) return null;
184
+ const doc = await findOne(this.prisma, 'IntegrationMapping', {
185
+ _id: objectId,
186
+ });
187
+ if (!doc) return null;
188
+
189
+ const decryptedMapping = await this.encryptionService.decryptFields(
190
+ 'IntegrationMapping',
191
+ doc
192
+ );
193
+ return this._mapMapping(decryptedMapping);
194
+ }
195
+
196
+ async updateMapping(id, updates) {
197
+ const objectId = toObjectId(id);
198
+ if (!objectId) return null;
199
+
200
+ const existing = await findOne(this.prisma, 'IntegrationMapping', {
201
+ _id: objectId,
202
+ });
203
+ if (!existing) return null;
204
+
205
+ const decryptedExisting = await this.encryptionService.decryptFields(
206
+ 'IntegrationMapping',
207
+ existing
208
+ );
209
+
210
+ const mergedMapping =
211
+ updates.mapping !== undefined
212
+ ? updates.mapping
213
+ : decryptedExisting.mapping;
214
+
215
+ const updateDocument = {
216
+ ...updates,
217
+ updatedAt: new Date(),
218
+ };
219
+
220
+ if (mergedMapping !== undefined) {
221
+ const encryptedUpdate = await this.encryptionService.encryptFields(
222
+ 'IntegrationMapping',
223
+ { mapping: mergedMapping }
224
+ );
225
+ updateDocument.mapping = encryptedUpdate.mapping;
226
+ }
227
+
228
+ await updateOne(
229
+ this.prisma,
230
+ 'IntegrationMapping',
231
+ { _id: objectId },
232
+ {
233
+ $set: updateDocument,
234
+ }
235
+ );
236
+
237
+ const updated = await findOne(this.prisma, 'IntegrationMapping', {
238
+ _id: objectId,
239
+ });
240
+ if (!updated) {
241
+ console.error(
242
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after update',
243
+ {
244
+ mappingId: fromObjectId(objectId),
245
+ }
246
+ );
247
+ throw new Error(
248
+ 'Failed to update mapping: Document not found after update. ' +
249
+ 'This indicates a database consistency issue.'
250
+ );
251
+ }
252
+ const decryptedMapping = await this.encryptionService.decryptFields(
253
+ 'IntegrationMapping',
254
+ updated
255
+ );
256
+ return this._mapMapping(decryptedMapping);
257
+ }
258
+
259
+ _compositeFilter(integrationId, sourceId) {
260
+ const filter = {};
261
+ if (integrationId) filter.integrationId = integrationId;
262
+ if (sourceId !== undefined) {
263
+ filter.sourceId = sourceId === null ? null : String(sourceId);
264
+ }
265
+ return filter;
266
+ }
267
+
268
+ _mapMapping(doc) {
269
+ return {
270
+ id: fromObjectId(doc?._id),
271
+ integrationId: doc?.integrationId ?? null,
272
+ sourceId: doc?.sourceId ?? null,
273
+ mapping: doc?.mapping ?? null,
274
+ createdAt: doc?.createdAt,
275
+ updatedAt: doc?.updatedAt,
276
+ };
277
+ }
278
+ }
279
+
280
+ module.exports = { IntegrationMappingRepositoryDocumentDB };
@@ -0,0 +1,57 @@
1
+ const {
2
+ IntegrationMappingRepositoryMongo,
3
+ } = require('./integration-mapping-repository-mongo');
4
+ const {
5
+ IntegrationMappingRepositoryPostgres,
6
+ } = require('./integration-mapping-repository-postgres');
7
+ const {
8
+ IntegrationMappingRepositoryDocumentDB,
9
+ } = require('./integration-mapping-repository-documentdb');
10
+ const config = require('../../database/config');
11
+
12
+ /**
13
+ * Integration Mapping Repository Factory
14
+ * Creates the appropriate repository adapter based on database type
15
+ *
16
+ * Database-specific implementations:
17
+ * - MongoDB: Uses String IDs (ObjectId), no conversion needed
18
+ * - PostgreSQL: Uses Int IDs, converts String ↔ Int
19
+ *
20
+ * All repository methods return String IDs regardless of database type,
21
+ * ensuring application layer consistency.
22
+ *
23
+ * Usage:
24
+ * ```javascript
25
+ * const repository = createIntegrationMappingRepository();
26
+ * const mapping = await repository.findMappingBy(integrationId, sourceId);
27
+ * ```
28
+ *
29
+ * @returns {IntegrationMappingRepositoryInterface} Configured repository adapter
30
+ */
31
+ function createIntegrationMappingRepository() {
32
+ const dbType = config.DB_TYPE;
33
+
34
+ switch (dbType) {
35
+ case 'mongodb':
36
+ return new IntegrationMappingRepositoryMongo();
37
+
38
+ case 'postgresql':
39
+ return new IntegrationMappingRepositoryPostgres();
40
+
41
+ case 'documentdb':
42
+ return new IntegrationMappingRepositoryDocumentDB();
43
+
44
+ default:
45
+ throw new Error(
46
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
47
+ );
48
+ }
49
+ }
50
+
51
+ module.exports = {
52
+ createIntegrationMappingRepository,
53
+ // Export adapters for direct testing
54
+ IntegrationMappingRepositoryMongo,
55
+ IntegrationMappingRepositoryPostgres,
56
+ IntegrationMappingRepositoryDocumentDB,
57
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Integration Mapping Repository Interface
3
+ * Abstract base class defining the contract for integration mapping persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * Note: Currently, IntegrationMapping model has identical structure across MongoDB and PostgreSQL,
11
+ * so IntegrationMappingRepository serves both. This interface exists for consistency and
12
+ * future-proofing if database-specific implementations become needed.
13
+ *
14
+ * @abstract
15
+ */
16
+ class IntegrationMappingRepositoryInterface {
17
+ /**
18
+ * Find mapping by integration ID and source ID
19
+ *
20
+ * @param {string|number} integrationId - The integration ID
21
+ * @param {string} sourceId - The source ID for lookup
22
+ * @returns {Promise<Object|null>} The mapping object or null
23
+ * @abstract
24
+ */
25
+ async findMappingBy(integrationId, sourceId) {
26
+ throw new Error('Method findMappingBy must be implemented by subclass');
27
+ }
28
+
29
+ /**
30
+ * Create or update a mapping
31
+ *
32
+ * @param {string|number} integrationId - The integration ID
33
+ * @param {string} sourceId - The source ID for lookup
34
+ * @param {Object} mapping - The mapping data
35
+ * @returns {Promise<Object>} The created or updated mapping document
36
+ * @abstract
37
+ */
38
+ async upsertMapping(integrationId, sourceId, mapping) {
39
+ throw new Error('Method upsertMapping must be implemented by subclass');
40
+ }
41
+
42
+ /**
43
+ * Find all mappings for an integration
44
+ *
45
+ * @param {string|number} integrationId - The integration ID
46
+ * @returns {Promise<Array>} Array of mapping objects
47
+ * @abstract
48
+ */
49
+ async findMappingsByIntegration(integrationId) {
50
+ throw new Error(
51
+ 'Method findMappingsByIntegration must be implemented by subclass'
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Delete a specific mapping
57
+ *
58
+ * @param {string|number} integrationId - The integration ID
59
+ * @param {string} sourceId - The source ID
60
+ * @returns {Promise<Object>} Deletion result
61
+ * @abstract
62
+ */
63
+ async deleteMapping(integrationId, sourceId) {
64
+ throw new Error('Method deleteMapping must be implemented by subclass');
65
+ }
66
+
67
+ /**
68
+ * Delete all mappings for an integration
69
+ *
70
+ * @param {string|number} integrationId - The integration ID
71
+ * @returns {Promise<Object>} Deletion result
72
+ * @abstract
73
+ */
74
+ async deleteMappingsByIntegration(integrationId) {
75
+ throw new Error(
76
+ 'Method deleteMappingsByIntegration must be implemented by subclass'
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Find mapping by ID
82
+ *
83
+ * @param {string|number} id - The mapping ID
84
+ * @returns {Promise<Object|null>} The mapping object or null
85
+ * @abstract
86
+ */
87
+ async findMappingById(id) {
88
+ throw new Error(
89
+ 'Method findMappingById must be implemented by subclass'
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Update a mapping by ID
95
+ *
96
+ * @param {string|number} id - The mapping ID
97
+ * @param {Object} updates - Fields to update
98
+ * @returns {Promise<Object>} Updated mapping object
99
+ * @abstract
100
+ */
101
+ async updateMapping(id, updates) {
102
+ throw new Error('Method updateMapping must be implemented by subclass');
103
+ }
104
+ }
105
+
106
+ module.exports = { IntegrationMappingRepositoryInterface };
@@ -0,0 +1,161 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ IntegrationMappingRepositoryInterface,
4
+ } = require('./integration-mapping-repository-interface');
5
+
6
+ /**
7
+ * MongoDB Integration Mapping Repository Adapter
8
+ * Handles persistence of integration mappings used for data transformation
9
+ *
10
+ * MongoDB-specific characteristics:
11
+ * - Uses String IDs (ObjectId)
12
+ * - No ID conversion needed (IDs are already strings)
13
+ * - mapping data stored in JSON field
14
+ */
15
+ class IntegrationMappingRepositoryMongo extends IntegrationMappingRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Convert any value to string (handles null/undefined)
23
+ * @private
24
+ * @param {*} value - Value to convert
25
+ * @returns {string|null|undefined} String value or null/undefined
26
+ */
27
+ _toString(value) {
28
+ if (value === null || value === undefined) return value;
29
+ return String(value);
30
+ }
31
+
32
+ /**
33
+ * Find mapping by integration ID and source ID
34
+ * Replaces: IntegrationMapping.findBy(integrationId, sourceId)
35
+ *
36
+ * @param {string} integrationId - The integration ID
37
+ * @param {string} sourceId - The source ID for lookup
38
+ * @returns {Promise<Object|null>} The mapping object with string IDs or null
39
+ */
40
+ async findMappingBy(integrationId, sourceId) {
41
+ return await this.prisma.integrationMapping.findFirst({
42
+ where: {
43
+ integrationId,
44
+ sourceId: this._toString(sourceId),
45
+ },
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Create or update a mapping
51
+ * Replaces: IntegrationMapping.upsert(integrationId, sourceId, mapping)
52
+ *
53
+ * @param {string} integrationId - The integration ID
54
+ * @param {string} sourceId - The source ID for lookup
55
+ * @param {Object} mapping - The mapping data
56
+ * @returns {Promise<Object>} The created or updated mapping document with string IDs
57
+ */
58
+ async upsertMapping(integrationId, sourceId, mapping) {
59
+ return await this.prisma.integrationMapping.upsert({
60
+ where: {
61
+ integrationId_sourceId: {
62
+ integrationId,
63
+ sourceId: this._toString(sourceId),
64
+ },
65
+ },
66
+ update: {
67
+ mapping,
68
+ },
69
+ create: {
70
+ integrationId,
71
+ sourceId: this._toString(sourceId),
72
+ mapping,
73
+ },
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Find all mappings for an integration
79
+ * Replaces: IntegrationMapping.find({ integration: integrationId })
80
+ *
81
+ * @param {string} integrationId - The integration ID
82
+ * @returns {Promise<Array>} Array of mapping documents with string IDs
83
+ */
84
+ async findMappingsByIntegration(integrationId) {
85
+ return await this.prisma.integrationMapping.findMany({
86
+ where: { integrationId },
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Delete a mapping by integration and source ID
92
+ * Replaces: IntegrationMapping.deleteOne({ integration, sourceId })
93
+ *
94
+ * @param {string} integrationId - The integration ID
95
+ * @param {string} sourceId - The source ID
96
+ * @returns {Promise<Object>} The deletion result
97
+ */
98
+ async deleteMapping(integrationId, sourceId) {
99
+ try {
100
+ await this.prisma.integrationMapping.delete({
101
+ where: {
102
+ integrationId_sourceId: {
103
+ integrationId,
104
+ sourceId: this._toString(sourceId),
105
+ },
106
+ },
107
+ });
108
+ return { acknowledged: true, deletedCount: 1 };
109
+ } catch (error) {
110
+ if (error.code === 'P2025') {
111
+ // Record not found
112
+ return { acknowledged: true, deletedCount: 0 };
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Delete all mappings for an integration
120
+ * Replaces: IntegrationMapping.deleteMany({ integration: integrationId })
121
+ *
122
+ * @param {string} integrationId - The integration ID
123
+ * @returns {Promise<Object>} The deletion result
124
+ */
125
+ async deleteMappingsByIntegration(integrationId) {
126
+ const result = await this.prisma.integrationMapping.deleteMany({
127
+ where: { integrationId },
128
+ });
129
+
130
+ return {
131
+ acknowledged: true,
132
+ deletedCount: result.count,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Find mapping by ID
138
+ * @param {string} id - Mapping ID
139
+ * @returns {Promise<Object|null>} Mapping object with string IDs or null
140
+ */
141
+ async findMappingById(id) {
142
+ return await this.prisma.integrationMapping.findUnique({
143
+ where: { id },
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Update mapping by ID
149
+ * @param {string} id - Mapping ID
150
+ * @param {Object} updates - Fields to update
151
+ * @returns {Promise<Object>} Updated mapping object with string IDs
152
+ */
153
+ async updateMapping(id, updates) {
154
+ return await this.prisma.integrationMapping.update({
155
+ where: { id },
156
+ data: updates,
157
+ });
158
+ }
159
+ }
160
+
161
+ module.exports = { IntegrationMappingRepositoryMongo };