@friggframework/core 2.0.0-next.12 → 2.0.0-next.121

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 (410) hide show
  1. package/.eslintrc.json +18 -1
  2. package/CLAUDE.md +830 -0
  3. package/README.md +996 -50
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
  9. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
  10. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  12. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  13. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  14. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  15. package/application/commands/README.md +451 -0
  16. package/application/commands/admin-script-commands.js +359 -0
  17. package/application/commands/credential-commands.js +262 -0
  18. package/application/commands/entity-commands.js +294 -0
  19. package/application/commands/integration-commands.js +419 -0
  20. package/application/commands/integration-mapping-commands.js +25 -0
  21. package/application/commands/process-commands.js +135 -0
  22. package/application/commands/report-commands.js +188 -0
  23. package/application/commands/scheduler-commands.js +263 -0
  24. package/application/commands/usage-commands.js +56 -0
  25. package/application/commands/user-commands.js +312 -0
  26. package/application/index.js +83 -0
  27. package/artifacts/repositories/artifact-repository-factory.js +19 -0
  28. package/artifacts/repositories/artifact-repository-interface.js +27 -0
  29. package/artifacts/repositories/artifact-repository-local.js +42 -0
  30. package/artifacts/repositories/artifact-repository-s3.js +61 -0
  31. package/assertions/index.js +0 -3
  32. package/core/CLAUDE.md +715 -0
  33. package/core/Worker.js +55 -24
  34. package/core/create-handler.js +121 -38
  35. package/core/index.js +8 -1
  36. package/core/invocation-scope.js +225 -0
  37. package/core/parameters-to-env.js +257 -0
  38. package/core/secrets-to-env.js +7 -6
  39. package/core/ssm-preload.mjs +34 -0
  40. package/credential/repositories/credential-active-type.js +32 -0
  41. package/credential/repositories/credential-repository-documentdb.js +355 -0
  42. package/credential/repositories/credential-repository-factory.js +54 -0
  43. package/credential/repositories/credential-repository-interface.js +113 -0
  44. package/credential/repositories/credential-repository-mongo.js +294 -0
  45. package/credential/repositories/credential-repository-postgres.js +312 -0
  46. package/credential/repositories/credential-repository.js +300 -0
  47. package/credential/use-cases/get-credential-for-user.js +25 -0
  48. package/credential/use-cases/update-authentication-status.js +15 -0
  49. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  50. package/database/adapters/lambda-invoker.js +97 -0
  51. package/database/config.js +147 -0
  52. package/database/documentdb-encryption-service.js +334 -0
  53. package/database/documentdb-utils.js +196 -0
  54. package/database/encryption/README.md +863 -0
  55. package/database/encryption/documentdb-encryption-service.md +3575 -0
  56. package/database/encryption/encryption-schema-registry.js +404 -0
  57. package/database/encryption/field-encryption-service.js +254 -0
  58. package/database/encryption/integration-mapping-encryption.js +97 -0
  59. package/database/encryption/logger.js +82 -0
  60. package/database/encryption/prisma-encryption-extension.js +241 -0
  61. package/database/index.js +21 -21
  62. package/database/prisma.js +182 -0
  63. package/database/repositories/health-check-repository-documentdb.js +138 -0
  64. package/database/repositories/health-check-repository-factory.js +48 -0
  65. package/database/repositories/health-check-repository-interface.js +82 -0
  66. package/database/repositories/health-check-repository-mongodb.js +89 -0
  67. package/database/repositories/health-check-repository-postgres.js +82 -0
  68. package/database/repositories/migration-status-repository-s3.js +137 -0
  69. package/database/use-cases/check-database-health-use-case.js +29 -0
  70. package/database/use-cases/check-database-state-use-case.js +81 -0
  71. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  72. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  73. package/database/use-cases/get-migration-status-use-case.js +93 -0
  74. package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
  75. package/database/use-cases/run-database-migration-use-case.js +139 -0
  76. package/database/use-cases/test-encryption-use-case.js +253 -0
  77. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  78. package/database/utils/mongodb-collection-utils.js +94 -0
  79. package/database/utils/mongodb-schema-init.js +108 -0
  80. package/database/utils/prisma-runner.js +491 -0
  81. package/database/utils/prisma-schema-parser.js +182 -0
  82. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  83. package/encrypt/Cryptor.js +34 -168
  84. package/encrypt/index.js +1 -2
  85. package/errors/client-safe-error.js +26 -0
  86. package/errors/fetch-error.js +59 -57
  87. package/errors/index.js +6 -0
  88. package/errors/mapping-already-exists-error.js +14 -0
  89. package/generated/prisma-mongodb/client.d.ts +1 -0
  90. package/generated/prisma-mongodb/client.js +5 -0
  91. package/generated/prisma-mongodb/default.d.ts +1 -0
  92. package/generated/prisma-mongodb/default.js +5 -0
  93. package/generated/prisma-mongodb/edge.d.ts +1 -0
  94. package/generated/prisma-mongodb/edge.js +386 -0
  95. package/generated/prisma-mongodb/index-browser.js +368 -0
  96. package/generated/prisma-mongodb/index.d.ts +27065 -0
  97. package/generated/prisma-mongodb/index.js +411 -0
  98. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  99. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  100. package/generated/prisma-mongodb/package.json +183 -0
  101. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  102. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  103. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  104. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  105. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  106. package/generated/prisma-mongodb/runtime/library.js +147 -0
  107. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  108. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  109. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  110. package/generated/prisma-mongodb/schema.prisma +455 -0
  111. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  112. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  113. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  114. package/generated/prisma-mongodb/wasm.js +393 -0
  115. package/generated/prisma-postgresql/client.d.ts +1 -0
  116. package/generated/prisma-postgresql/client.js +5 -0
  117. package/generated/prisma-postgresql/default.d.ts +1 -0
  118. package/generated/prisma-postgresql/default.js +5 -0
  119. package/generated/prisma-postgresql/edge.d.ts +1 -0
  120. package/generated/prisma-postgresql/edge.js +407 -0
  121. package/generated/prisma-postgresql/index-browser.js +389 -0
  122. package/generated/prisma-postgresql/index.d.ts +29904 -0
  123. package/generated/prisma-postgresql/index.js +432 -0
  124. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  125. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  126. package/generated/prisma-postgresql/package.json +183 -0
  127. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  128. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  129. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  130. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  131. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  132. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  133. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  134. package/generated/prisma-postgresql/runtime/library.js +147 -0
  135. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  136. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  137. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  138. package/generated/prisma-postgresql/schema.prisma +436 -0
  139. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  140. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  141. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  142. package/generated/prisma-postgresql/wasm.js +414 -0
  143. package/handlers/WEBHOOKS.md +682 -0
  144. package/handlers/app-definition-loader.js +103 -0
  145. package/handlers/app-handler-helpers.js +20 -6
  146. package/handlers/backend-utils.js +272 -46
  147. package/handlers/database-migration-handler.js +236 -0
  148. package/handlers/integration-event-dispatcher.js +68 -0
  149. package/handlers/middleware/admin-auth.js +73 -0
  150. package/handlers/routers/HEALTHCHECK.md +342 -0
  151. package/handlers/routers/auth.js +4 -15
  152. package/handlers/routers/db-migration.handler.js +29 -0
  153. package/handlers/routers/db-migration.js +329 -0
  154. package/handlers/routers/health.js +518 -0
  155. package/handlers/routers/integration-defined-routers.js +85 -10
  156. package/handlers/routers/integration-webhook-routers.js +87 -0
  157. package/handlers/routers/user.js +27 -5
  158. package/handlers/routers/websocket.js +31 -9
  159. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  160. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  161. package/handlers/workers/db-migration.js +438 -0
  162. package/handlers/workers/dlq-processor.js +89 -0
  163. package/handlers/workers/integration-defined-workers.js +13 -7
  164. package/index.js +123 -47
  165. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +238 -0
  166. package/infrastructure/scheduler/index.js +33 -0
  167. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  168. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  169. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  170. package/integrations/EXTENSIONS.md +240 -0
  171. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  172. package/integrations/extension.js +254 -0
  173. package/integrations/index.js +20 -10
  174. package/integrations/integration-base.js +777 -74
  175. package/integrations/integration-router.js +399 -180
  176. package/integrations/options.js +1 -1
  177. package/integrations/repositories/config-patch-shared.js +43 -0
  178. package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
  179. package/integrations/repositories/integration-mapping-query.js +193 -0
  180. package/integrations/repositories/integration-mapping-repository-documentdb.js +406 -0
  181. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  182. package/integrations/repositories/integration-mapping-repository-interface.js +183 -0
  183. package/integrations/repositories/integration-mapping-repository-mongo.js +284 -0
  184. package/integrations/repositories/integration-mapping-repository-postgres.js +403 -0
  185. package/integrations/repositories/integration-mapping-repository.js +185 -0
  186. package/integrations/repositories/integration-repository-documentdb.js +354 -0
  187. package/integrations/repositories/integration-repository-factory.js +51 -0
  188. package/integrations/repositories/integration-repository-interface.js +219 -0
  189. package/integrations/repositories/integration-repository-mongo.js +502 -0
  190. package/integrations/repositories/integration-repository-postgres.js +550 -0
  191. package/integrations/repositories/process-repository-documentdb.js +311 -0
  192. package/integrations/repositories/process-repository-factory.js +53 -0
  193. package/integrations/repositories/process-repository-interface.js +136 -0
  194. package/integrations/repositories/process-repository-mongo.js +262 -0
  195. package/integrations/repositories/process-repository-postgres.js +380 -0
  196. package/integrations/repositories/process-update-ops-shared.js +112 -0
  197. package/integrations/repositories/report-id.js +13 -0
  198. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  199. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  200. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  201. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  202. package/integrations/use-cases/create-integration.js +253 -0
  203. package/integrations/use-cases/create-process.js +130 -0
  204. package/integrations/use-cases/delete-integration-for-user.js +130 -0
  205. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  206. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  207. package/integrations/use-cases/get-integration-for-user.js +78 -0
  208. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  209. package/integrations/use-cases/get-integration-instance.js +83 -0
  210. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  211. package/integrations/use-cases/get-possible-integrations.js +27 -0
  212. package/integrations/use-cases/get-process.js +89 -0
  213. package/integrations/use-cases/index.js +19 -0
  214. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  215. package/integrations/use-cases/load-integration-context.js +71 -0
  216. package/integrations/use-cases/patch-integration-config.js +39 -0
  217. package/integrations/use-cases/process-errors.js +28 -0
  218. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  219. package/integrations/use-cases/update-integration-config.js +32 -0
  220. package/integrations/use-cases/update-integration-messages.js +44 -0
  221. package/integrations/use-cases/update-integration-status.js +32 -0
  222. package/integrations/use-cases/update-integration.js +92 -0
  223. package/integrations/use-cases/update-process-metrics.js +222 -0
  224. package/integrations/use-cases/update-process-state.js +163 -0
  225. package/integrations/utils/assert-entity-ids-input.js +42 -0
  226. package/integrations/utils/map-integration-dto.js +37 -0
  227. package/jest-global-setup-noop.js +3 -0
  228. package/jest-global-teardown-noop.js +3 -0
  229. package/jest.config.js +2 -0
  230. package/logs/.eslintrc.json +26 -1
  231. package/logs/__fixtures__/events.js +82 -0
  232. package/logs/__fixtures__/matchers.js +45 -0
  233. package/logs/__fixtures__/secrets.js +59 -0
  234. package/logs/__fixtures__/vectors.js +329 -0
  235. package/logs/__fixtures__/with-env.js +45 -0
  236. package/logs/context.js +109 -0
  237. package/logs/contract-keys.js +67 -0
  238. package/logs/debug-shims.js +46 -0
  239. package/logs/denied-keys.js +21 -0
  240. package/logs/index.js +14 -3
  241. package/logs/jest-logger-setup.js +25 -0
  242. package/logs/levels.js +71 -0
  243. package/logs/logger-runtime.js +137 -0
  244. package/logs/logger.js +134 -46
  245. package/logs/record.js +276 -0
  246. package/logs/redact.js +328 -0
  247. package/logs/serialize.js +329 -0
  248. package/logs/sinks.js +93 -0
  249. package/logs/summarize-event.js +163 -0
  250. package/{module-plugin → modules}/index.js +0 -10
  251. package/modules/module-factory.js +56 -0
  252. package/modules/module.js +320 -0
  253. package/modules/repositories/module-repository-documentdb.js +350 -0
  254. package/modules/repositories/module-repository-factory.js +40 -0
  255. package/modules/repositories/module-repository-interface.js +145 -0
  256. package/modules/repositories/module-repository-mongo.js +436 -0
  257. package/modules/repositories/module-repository-postgres.js +481 -0
  258. package/modules/repositories/module-repository.js +369 -0
  259. package/modules/requester/api-key.js +52 -0
  260. package/modules/requester/oauth-2.js +561 -0
  261. package/modules/requester/requester.js +605 -0
  262. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  263. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  264. package/modules/tests/doubles/test-module-factory.js +16 -0
  265. package/modules/tests/doubles/test-module-repository.js +39 -0
  266. package/modules/use-cases/get-entities-for-user.js +32 -0
  267. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  268. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  269. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  270. package/modules/use-cases/get-module.js +74 -0
  271. package/modules/use-cases/process-authorization-callback.js +294 -0
  272. package/modules/use-cases/refresh-entity-options.js +72 -0
  273. package/modules/use-cases/test-module-auth.js +72 -0
  274. package/modules/utils/map-module-dto.js +20 -0
  275. package/modules/utils/to-credential-id.js +14 -0
  276. package/package.json +92 -52
  277. package/prisma-mongodb/schema.prisma +455 -0
  278. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  279. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  280. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  281. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  282. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  283. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  284. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  285. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  286. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  287. package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
  288. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  289. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  290. package/prisma-postgresql/schema.prisma +436 -0
  291. package/queues/queue-delivery.js +41 -0
  292. package/queues/queuer-util.js +89 -21
  293. package/reporting/README.md +154 -0
  294. package/reporting/builtin-reports.js +6 -0
  295. package/reporting/index.js +13 -0
  296. package/reporting/report-base.js +49 -0
  297. package/reporting/reports/integrations-report.js +221 -0
  298. package/syncs/manager.js +468 -443
  299. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  300. package/syncs/repositories/sync-repository-factory.js +43 -0
  301. package/syncs/repositories/sync-repository-interface.js +109 -0
  302. package/syncs/repositories/sync-repository-mongo.js +239 -0
  303. package/syncs/repositories/sync-repository-postgres.js +319 -0
  304. package/syncs/sync.js +0 -1
  305. package/telemetry/README.md +331 -0
  306. package/telemetry/bind-telemetry-context.js +77 -0
  307. package/telemetry/canonical-counters.js +52 -0
  308. package/telemetry/exporters.js +85 -0
  309. package/telemetry/index.js +26 -0
  310. package/telemetry/instrument-handler.js +104 -0
  311. package/telemetry/no-op-telemetry.js +71 -0
  312. package/telemetry/north-star.js +103 -0
  313. package/telemetry/otel-telemetry.js +232 -0
  314. package/telemetry/plugin-subscribers.js +77 -0
  315. package/telemetry/telemetry-config.js +120 -0
  316. package/telemetry/telemetry-context.js +51 -0
  317. package/telemetry/telemetry-event-bus.js +58 -0
  318. package/telemetry/telemetry-runtime.js +164 -0
  319. package/telemetry/telemetry-service.js +52 -0
  320. package/telemetry/usage-rollup-subscriber.js +116 -0
  321. package/token/repositories/token-repository-documentdb.js +137 -0
  322. package/token/repositories/token-repository-factory.js +40 -0
  323. package/token/repositories/token-repository-interface.js +131 -0
  324. package/token/repositories/token-repository-mongo.js +219 -0
  325. package/token/repositories/token-repository-postgres.js +264 -0
  326. package/token/repositories/token-repository.js +219 -0
  327. package/types/associations/index.d.ts +0 -17
  328. package/types/core/index.d.ts +12 -4
  329. package/types/database/index.d.ts +10 -2
  330. package/types/encrypt/index.d.ts +5 -3
  331. package/types/errors/index.d.ts +24 -10
  332. package/types/integrations/index.d.ts +3 -8
  333. package/types/logs/index.d.ts +61 -2
  334. package/types/module-plugin/index.d.ts +20 -69
  335. package/types/syncs/index.d.ts +0 -17
  336. package/usage/README.md +54 -0
  337. package/usage/index.js +17 -0
  338. package/usage/repositories/usage-repository-documentdb.js +194 -0
  339. package/usage/repositories/usage-repository-factory.js +25 -0
  340. package/usage/repositories/usage-repository-interface.js +37 -0
  341. package/usage/repositories/usage-repository-prisma.js +146 -0
  342. package/usage/tracked-metrics.js +38 -0
  343. package/usage/usage-windows.js +24 -0
  344. package/user/repositories/user-repository-documentdb.js +426 -0
  345. package/user/repositories/user-repository-factory.js +52 -0
  346. package/user/repositories/user-repository-interface.js +214 -0
  347. package/user/repositories/user-repository-mongo.js +323 -0
  348. package/user/repositories/user-repository-postgres.js +377 -0
  349. package/user/tests/doubles/test-user-repository.js +72 -0
  350. package/user/use-cases/authenticate-user.js +143 -0
  351. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  352. package/user/use-cases/create-individual-user.js +61 -0
  353. package/user/use-cases/create-organization-user.js +47 -0
  354. package/user/use-cases/create-token-for-user-id.js +30 -0
  355. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  356. package/user/use-cases/get-user-from-bearer-token.js +82 -0
  357. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  358. package/user/use-cases/login-user.js +122 -0
  359. package/user/user.js +125 -0
  360. package/utils/backend-path.js +38 -0
  361. package/utils/index.js +6 -0
  362. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  363. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  364. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  365. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  366. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  367. package/websocket/repositories/websocket-connection-repository.js +161 -0
  368. package/assertions/is-equal.js +0 -17
  369. package/associations/model.js +0 -54
  370. package/database/models/IndividualUser.js +0 -76
  371. package/database/models/OrganizationUser.js +0 -29
  372. package/database/models/State.js +0 -9
  373. package/database/models/Token.js +0 -70
  374. package/database/models/UserModel.js +0 -7
  375. package/database/models/WebsocketConnection.js +0 -49
  376. package/database/mongo.js +0 -45
  377. package/database/mongoose.js +0 -5
  378. package/encrypt/Cryptor.test.js +0 -32
  379. package/encrypt/encrypt.js +0 -132
  380. package/encrypt/encrypt.test.js +0 -1069
  381. package/encrypt/test-encrypt.js +0 -107
  382. package/errors/base-error.test.js +0 -32
  383. package/errors/fetch-error.test.js +0 -79
  384. package/errors/halt-error.test.js +0 -11
  385. package/errors/validation-errors.test.js +0 -120
  386. package/handlers/routers/middleware/loadUser.js +0 -15
  387. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  388. package/integrations/create-frigg-backend.js +0 -31
  389. package/integrations/integration-factory.js +0 -251
  390. package/integrations/integration-mapping.js +0 -43
  391. package/integrations/integration-model.js +0 -46
  392. package/integrations/integration-user.js +0 -144
  393. package/integrations/test/integration-base.test.js +0 -144
  394. package/lambda/TimeoutCatcher.test.js +0 -68
  395. package/logs/logger.test.js +0 -76
  396. package/module-plugin/auther.js +0 -393
  397. package/module-plugin/credential.js +0 -22
  398. package/module-plugin/entity-manager.js +0 -70
  399. package/module-plugin/entity.js +0 -46
  400. package/module-plugin/manager.js +0 -169
  401. package/module-plugin/module-factory.js +0 -61
  402. package/module-plugin/requester/api-key.js +0 -36
  403. package/module-plugin/requester/oauth-2.js +0 -219
  404. package/module-plugin/requester/requester.js +0 -165
  405. package/module-plugin/requester/requester.test.js +0 -28
  406. package/module-plugin/test/auther.test.js +0 -97
  407. package/syncs/model.js +0 -62
  408. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  409. /package/{module-plugin → modules}/requester/basic.js +0 -0
  410. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,284 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { MappingAlreadyExistsError } = require('../../errors');
3
+ const {
4
+ assertMappingWrittenUnencrypted,
5
+ decryptQueriedMappings,
6
+ } = require('../../database/encryption/integration-mapping-encryption');
7
+ const {
8
+ IntegrationMappingRepositoryInterface,
9
+ } = require('./integration-mapping-repository-interface');
10
+ const { validateMappingQuery } = require('./integration-mapping-query');
11
+ const {
12
+ buildMappingQueryStages,
13
+ } = require('./integration-mapping-query-pipeline');
14
+
15
+ const OBJECT_ID_REGEX = /^[0-9a-fA-F]{24}$/;
16
+
17
+ const fromRawDate = (raw) => new Date(raw.$date);
18
+
19
+ function strictObjectId(id) {
20
+ if (typeof id !== 'string' || !OBJECT_ID_REGEX.test(id)) {
21
+ throw new TypeError(`Invalid ID: ${id} is not an ObjectId`);
22
+ }
23
+ return id;
24
+ }
25
+
26
+ /**
27
+ * MongoDB Integration Mapping Repository Adapter
28
+ * Handles persistence of integration mappings used for data transformation
29
+ *
30
+ * MongoDB-specific characteristics:
31
+ * - Uses String IDs (ObjectId)
32
+ * - No ID conversion needed (IDs are already strings)
33
+ * - mapping data stored in JSON field
34
+ */
35
+ class IntegrationMappingRepositoryMongo extends IntegrationMappingRepositoryInterface {
36
+ constructor() {
37
+ super();
38
+ this.prisma = prisma;
39
+ }
40
+
41
+ /**
42
+ * Convert any value to string (handles null/undefined)
43
+ * @private
44
+ * @param {*} value - Value to convert
45
+ * @returns {string|null|undefined} String value or null/undefined
46
+ */
47
+ _toString(value) {
48
+ if (value === null || value === undefined) return value;
49
+ return String(value);
50
+ }
51
+
52
+ /**
53
+ * Find mapping by integration ID and source ID
54
+ * Replaces: IntegrationMapping.findBy(integrationId, sourceId)
55
+ *
56
+ * @param {string} integrationId - The integration ID
57
+ * @param {string} sourceId - The source ID for lookup
58
+ * @returns {Promise<Object|null>} The mapping object with string IDs or null
59
+ */
60
+ async findMappingBy(integrationId, sourceId) {
61
+ return await this.prisma.integrationMapping.findFirst({
62
+ where: {
63
+ integrationId,
64
+ sourceId: this._toString(sourceId),
65
+ },
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Create or update a mapping
71
+ * Replaces: IntegrationMapping.upsert(integrationId, sourceId, mapping)
72
+ *
73
+ * @param {string} integrationId - The integration ID
74
+ * @param {string} sourceId - The source ID for lookup
75
+ * @param {Object} mapping - The mapping data
76
+ * @returns {Promise<Object>} The created or updated mapping document with string IDs
77
+ */
78
+ async upsertMapping(integrationId, sourceId, mapping) {
79
+ return await this.prisma.integrationMapping.upsert({
80
+ where: {
81
+ integrationId_sourceId: {
82
+ integrationId,
83
+ sourceId: this._toString(sourceId),
84
+ },
85
+ },
86
+ update: {
87
+ mapping,
88
+ },
89
+ create: {
90
+ integrationId,
91
+ sourceId: this._toString(sourceId),
92
+ mapping,
93
+ },
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Insert a mapping; fails if (integrationId, sourceId) already exists
99
+ *
100
+ * @param {string} integrationId - The integration ID
101
+ * @param {string} sourceId - The source ID
102
+ * @param {Object} mapping - The mapping data
103
+ * @returns {Promise<Object>} The created mapping document
104
+ * @throws {MappingAlreadyExistsError} If the pair already exists
105
+ */
106
+ async createMapping(integrationId, sourceId, mapping) {
107
+ try {
108
+ return await this.prisma.integrationMapping.create({
109
+ data: {
110
+ integrationId,
111
+ sourceId: this._toString(sourceId),
112
+ mapping,
113
+ },
114
+ });
115
+ } catch (error) {
116
+ if (error.code === 'P2002') {
117
+ throw new MappingAlreadyExistsError(integrationId, sourceId, {
118
+ cause: error,
119
+ });
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Find all mappings for an integration
127
+ * Replaces: IntegrationMapping.find({ integration: integrationId })
128
+ *
129
+ * @param {string} integrationId - The integration ID
130
+ * @returns {Promise<Array>} Array of mapping documents with string IDs
131
+ */
132
+ async findMappingsByIntegration(integrationId) {
133
+ return await this.prisma.integrationMapping.findMany({
134
+ where: { integrationId },
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Delete a mapping by integration and source ID
140
+ * Replaces: IntegrationMapping.deleteOne({ integration, sourceId })
141
+ *
142
+ * @param {string} integrationId - The integration ID
143
+ * @param {string} sourceId - The source ID
144
+ * @returns {Promise<Object>} The deletion result
145
+ */
146
+ async deleteMapping(integrationId, sourceId) {
147
+ try {
148
+ await this.prisma.integrationMapping.delete({
149
+ where: {
150
+ integrationId_sourceId: {
151
+ integrationId,
152
+ sourceId: this._toString(sourceId),
153
+ },
154
+ },
155
+ });
156
+ return { acknowledged: true, deletedCount: 1 };
157
+ } catch (error) {
158
+ if (error.code === 'P2025') {
159
+ // Record not found
160
+ return { acknowledged: true, deletedCount: 0 };
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Delete all mappings for an integration
168
+ * Replaces: IntegrationMapping.deleteMany({ integration: integrationId })
169
+ *
170
+ * @param {string} integrationId - The integration ID
171
+ * @returns {Promise<Object>} The deletion result
172
+ */
173
+ async deleteMappingsByIntegration(integrationId) {
174
+ const result = await this.prisma.integrationMapping.deleteMany({
175
+ where: { integrationId },
176
+ });
177
+
178
+ return {
179
+ acknowledged: true,
180
+ deletedCount: result.count,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Count mappings grouped by integration id for a bounded id set.
186
+ *
187
+ * @param {Array<string>} ids - Integration ids
188
+ * @returns {Promise<Map<string, number>>} integrationId (string) → count
189
+ */
190
+ async countByIntegrationIds(ids = []) {
191
+ const counts = new Map();
192
+ if (!ids || ids.length === 0) return counts;
193
+
194
+ const groups = await this.prisma.integrationMapping.groupBy({
195
+ by: ['integrationId'],
196
+ where: { integrationId: { in: ids } },
197
+ _count: { _all: true },
198
+ });
199
+
200
+ for (const group of groups) {
201
+ counts.set(String(group.integrationId), group._count._all);
202
+ }
203
+ return counts;
204
+ }
205
+
206
+ /**
207
+ * @param {string} integrationId
208
+ * @param {Object} query - See IntegrationMappingRepositoryInterface.queryMappings
209
+ * @returns {Promise<{mappings: Array<Object>, total: number}>}
210
+ */
211
+ async queryMappings(integrationId, query) {
212
+ const validated = validateMappingQuery(query);
213
+ const objectId = strictObjectId(integrationId);
214
+ assertMappingWrittenUnencrypted();
215
+ const { match, page } = buildMappingQueryStages(
216
+ { $oid: objectId },
217
+ validated
218
+ );
219
+
220
+ const result = await this.prisma.$runCommandRaw({
221
+ aggregate: 'IntegrationMapping',
222
+ pipeline: [
223
+ match,
224
+ {
225
+ $facet: {
226
+ mappings: page,
227
+ total: [{ $count: 'total' }],
228
+ },
229
+ },
230
+ ],
231
+ cursor: {},
232
+ allowDiskUse: true,
233
+ });
234
+ const [{ mappings, total }] = result.cursor.firstBatch;
235
+
236
+ return {
237
+ mappings: await decryptQueriedMappings(
238
+ mappings.map((doc) => this._fromRawMapping(doc))
239
+ ),
240
+ total: total[0]?.total ?? 0,
241
+ };
242
+ }
243
+
244
+ /**
245
+ * A raw aggregate document, in the shape findMappingsByIntegration returns.
246
+ * @private
247
+ */
248
+ _fromRawMapping(doc) {
249
+ return {
250
+ id: doc._id.$oid,
251
+ integrationId: doc.integrationId.$oid,
252
+ sourceId: doc.sourceId ?? null,
253
+ mapping: doc.mapping ?? null,
254
+ createdAt: fromRawDate(doc.createdAt),
255
+ updatedAt: fromRawDate(doc.updatedAt),
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Find mapping by ID
261
+ * @param {string} id - Mapping ID
262
+ * @returns {Promise<Object|null>} Mapping object with string IDs or null
263
+ */
264
+ async findMappingById(id) {
265
+ return await this.prisma.integrationMapping.findUnique({
266
+ where: { id },
267
+ });
268
+ }
269
+
270
+ /**
271
+ * Update mapping by ID
272
+ * @param {string} id - Mapping ID
273
+ * @param {Object} updates - Fields to update
274
+ * @returns {Promise<Object>} Updated mapping object with string IDs
275
+ */
276
+ async updateMapping(id, updates) {
277
+ return await this.prisma.integrationMapping.update({
278
+ where: { id },
279
+ data: updates,
280
+ });
281
+ }
282
+ }
283
+
284
+ module.exports = { IntegrationMappingRepositoryMongo };
@@ -0,0 +1,403 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { MappingAlreadyExistsError } = require('../../errors');
3
+ const {
4
+ assertMappingWrittenUnencrypted,
5
+ decryptQueriedMappings,
6
+ } = require('../../database/encryption/integration-mapping-encryption');
7
+ const {
8
+ IntegrationMappingRepositoryInterface,
9
+ } = require('./integration-mapping-repository-interface');
10
+ const { strictIntId } = require('./report-id');
11
+ const { validateMappingQuery } = require('./integration-mapping-query');
12
+
13
+ const COLUMNS = { mapping: '"mapping"', sourceId: '"sourceId"' };
14
+ const SQL_DIRECTIONS = { asc: 'ASC', desc: 'DESC' };
15
+
16
+ const jsonPathOperand = (column, path) => ({
17
+ json: `${column} #> ${path}::text[]`,
18
+ text: `${column} #>> ${path}::text[]`,
19
+ });
20
+ const jsonType = (json) => `COALESCE(jsonb_typeof(${json}), 'null')`;
21
+
22
+ const CONDITION_SQL = {
23
+ exists: ({ json }) => `${jsonType(json)} <> 'null'`,
24
+ notExists: ({ json }) => `${jsonType(json)} = 'null'`,
25
+ in: ({ json, text, value }) =>
26
+ `(jsonb_typeof(${json}) = 'string' AND ${text} = ANY(${value}::text[]))`,
27
+ notStartsWith: ({ text, value }) =>
28
+ `(${text} IS NULL OR NOT starts_with(${text}, ${value}::text))`,
29
+ };
30
+
31
+ /**
32
+ * PostgreSQL Integration Mapping Repository Adapter
33
+ * Handles persistence of integration mappings used for data transformation
34
+ *
35
+ * PostgreSQL-specific characteristics:
36
+ * - Uses Int IDs with autoincrement
37
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
38
+ * - All returned IDs are converted to strings for application layer consistency
39
+ */
40
+ class IntegrationMappingRepositoryPostgres extends IntegrationMappingRepositoryInterface {
41
+ constructor() {
42
+ super();
43
+ this.prisma = prisma;
44
+ }
45
+
46
+ /**
47
+ * Convert string ID to integer for PostgreSQL queries
48
+ * @private
49
+ * @param {string|number|null|undefined} id - ID to convert
50
+ * @returns {number|null|undefined} Integer ID or null/undefined
51
+ * @throws {Error} If ID cannot be converted to integer
52
+ */
53
+ _stringToInt(id) {
54
+ if (id === null || id === undefined) return id;
55
+ const parsed = parseInt(id, 10);
56
+ if (isNaN(parsed)) {
57
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
58
+ }
59
+ return parsed;
60
+ }
61
+
62
+ /**
63
+ * Convert any value to string (handles null/undefined)
64
+ * @private
65
+ * @param {*} value - Value to convert
66
+ * @returns {string|null|undefined} String value or null/undefined
67
+ */
68
+ _toString(value) {
69
+ if (value === null || value === undefined) return value;
70
+ return String(value);
71
+ }
72
+
73
+ /**
74
+ * Convert integer to string for application layer
75
+ * @private
76
+ * @param {number|null|undefined} id - Integer ID from database
77
+ * @returns {string|null|undefined} String ID or null/undefined
78
+ */
79
+ _intToString(id) {
80
+ if (id === null || id === undefined) return id;
81
+ return id.toString();
82
+ }
83
+
84
+ /**
85
+ * Legacy alias for _stringToInt (for backward compatibility)
86
+ * @private
87
+ */
88
+ _convertId(id) {
89
+ return this._stringToInt(id);
90
+ }
91
+
92
+ /**
93
+ * Convert mapping object IDs to strings
94
+ * @private
95
+ * @param {Object|null} mapping - Mapping object from database
96
+ * @returns {Object|null} Mapping with string IDs
97
+ */
98
+ _convertMappingIds(mapping) {
99
+ if (!mapping) return mapping;
100
+ return {
101
+ ...mapping,
102
+ id: this._intToString(mapping.id),
103
+ integrationId: this._intToString(mapping.integrationId),
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Find mapping by integration ID and source ID
109
+ * Replaces: IntegrationMapping.findBy(integrationId, sourceId)
110
+ *
111
+ * @param {string} integrationId - The integration ID (string from application layer)
112
+ * @param {string} sourceId - The source ID for lookup
113
+ * @returns {Promise<Object|null>} The mapping object with string IDs or null
114
+ */
115
+ async findMappingBy(integrationId, sourceId) {
116
+ const mapping = await this.prisma.integrationMapping.findFirst({
117
+ where: {
118
+ integrationId: this._stringToInt(integrationId),
119
+ sourceId: this._toString(sourceId),
120
+ },
121
+ });
122
+ return this._convertMappingIds(mapping);
123
+ }
124
+
125
+ /**
126
+ * Create or update a mapping
127
+ * Replaces: IntegrationMapping.upsert(integrationId, sourceId, mapping)
128
+ *
129
+ * @param {string} integrationId - The integration ID (string from application layer)
130
+ * @param {string} sourceId - The source ID for lookup
131
+ * @param {Object} mapping - The mapping data
132
+ * @returns {Promise<Object>} The created or updated mapping document with string IDs
133
+ */
134
+ async upsertMapping(integrationId, sourceId, mapping) {
135
+ const result = await this.prisma.integrationMapping.upsert({
136
+ where: {
137
+ integrationId_sourceId: {
138
+ integrationId: this._stringToInt(integrationId),
139
+ sourceId: this._toString(sourceId),
140
+ },
141
+ },
142
+ update: {
143
+ mapping,
144
+ },
145
+ create: {
146
+ integrationId: this._stringToInt(integrationId),
147
+ sourceId: this._toString(sourceId),
148
+ mapping,
149
+ },
150
+ });
151
+ return this._convertMappingIds(result);
152
+ }
153
+
154
+ /**
155
+ * Insert a mapping; fails if (integrationId, sourceId) already exists
156
+ *
157
+ * @param {string} integrationId - The integration ID
158
+ * @param {string} sourceId - The source ID
159
+ * @param {Object} mapping - The mapping data
160
+ * @returns {Promise<Object>} The created mapping document
161
+ * @throws {MappingAlreadyExistsError} If the pair already exists
162
+ */
163
+ async createMapping(integrationId, sourceId, mapping) {
164
+ try {
165
+ const created = await this.prisma.integrationMapping.create({
166
+ data: {
167
+ integrationId: this._stringToInt(integrationId),
168
+ sourceId: this._toString(sourceId),
169
+ mapping,
170
+ },
171
+ });
172
+ return this._convertMappingIds(created);
173
+ } catch (error) {
174
+ if (error.code === 'P2002') {
175
+ throw new MappingAlreadyExistsError(integrationId, sourceId, {
176
+ cause: error,
177
+ });
178
+ }
179
+ throw error;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Find all mappings for an integration
185
+ * Replaces: IntegrationMapping.find({ integration: integrationId })
186
+ *
187
+ * @param {string} integrationId - The integration ID (string from application layer)
188
+ * @returns {Promise<Array>} Array of mapping documents with string IDs
189
+ */
190
+ async findMappingsByIntegration(integrationId) {
191
+ const intIntegrationId = this._convertId(integrationId);
192
+ const mappings = await this.prisma.integrationMapping.findMany({
193
+ where: { integrationId: intIntegrationId },
194
+ });
195
+ return mappings.map((m) => this._convertMappingIds(m));
196
+ }
197
+
198
+ /**
199
+ * Delete a mapping by integration and source ID
200
+ * Replaces: IntegrationMapping.deleteOne({ integration, sourceId })
201
+ *
202
+ * @param {string} integrationId - The integration ID (string from application layer)
203
+ * @param {string} sourceId - The source ID
204
+ * @returns {Promise<Object>} The deletion result
205
+ */
206
+ async deleteMapping(integrationId, sourceId) {
207
+ try {
208
+ await this.prisma.integrationMapping.delete({
209
+ where: {
210
+ integrationId_sourceId: {
211
+ integrationId: this._stringToInt(integrationId),
212
+ sourceId: this._toString(sourceId),
213
+ },
214
+ },
215
+ });
216
+ return { acknowledged: true, deletedCount: 1 };
217
+ } catch (error) {
218
+ if (error.code === 'P2025') {
219
+ // Record not found
220
+ return { acknowledged: true, deletedCount: 0 };
221
+ }
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Delete all mappings for an integration
228
+ * Replaces: IntegrationMapping.deleteMany({ integration: integrationId })
229
+ *
230
+ * @param {string} integrationId - The integration ID (string from application layer)
231
+ * @returns {Promise<Object>} The deletion result
232
+ */
233
+ async deleteMappingsByIntegration(integrationId) {
234
+ const intIntegrationId = this._convertId(integrationId);
235
+ const result = await this.prisma.integrationMapping.deleteMany({
236
+ where: { integrationId: intIntegrationId },
237
+ });
238
+
239
+ return {
240
+ acknowledged: true,
241
+ deletedCount: result.count,
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Count mappings grouped by integration id for a bounded id set.
247
+ *
248
+ * @param {Array<string|number>} ids - Integration ids
249
+ * @returns {Promise<Map<string, number>>} integrationId (string) → count
250
+ */
251
+ async countByIntegrationIds(ids = []) {
252
+ const counts = new Map();
253
+ if (!ids || ids.length === 0) return counts;
254
+
255
+ // Strict (matches findAllForReport): reject partially-numeric ids instead of coercing.
256
+ const intIds = ids.map((id) => strictIntId(id));
257
+ const groups = await this.prisma.integrationMapping.groupBy({
258
+ by: ['integrationId'],
259
+ where: { integrationId: { in: intIds } },
260
+ _count: { _all: true },
261
+ });
262
+
263
+ for (const group of groups) {
264
+ counts.set(
265
+ this._intToString(group.integrationId),
266
+ group._count._all
267
+ );
268
+ }
269
+ return counts;
270
+ }
271
+
272
+ /**
273
+ * @param {string} integrationId
274
+ * @param {Object} query - See IntegrationMappingRepositoryInterface.queryMappings
275
+ * @returns {Promise<{mappings: Array<Object>, total: number}>}
276
+ */
277
+ async queryMappings(integrationId, query) {
278
+ const { where, orderBy, skip, take, omit } =
279
+ validateMappingQuery(query);
280
+ const intIntegrationId = strictIntId(integrationId);
281
+ assertMappingWrittenUnencrypted();
282
+
283
+ const params = [];
284
+ const bind = (v) => {
285
+ params.push(v);
286
+ return `$${params.length}`;
287
+ };
288
+
289
+ const whereSql = [
290
+ `"integrationId" = ${bind(intIntegrationId)}::int`,
291
+ `jsonb_typeof("mapping") = 'object'`,
292
+ ...where.map((entry) => this._whereEntrySql(entry, bind)),
293
+ ].join(' AND ');
294
+ const orderSql = orderBy ? this._orderSql(orderBy, bind) : `"id" ASC`;
295
+ const mappingSql =
296
+ omit.length > 0
297
+ ? `"mapping" - ${bind(omit)}::text[] AS "mapping"`
298
+ : `"mapping"`;
299
+
300
+ const sql = `
301
+ WITH "matched" AS (
302
+ SELECT "id", "integrationId", "sourceId", "mapping", "createdAt", "updatedAt"
303
+ FROM "IntegrationMapping"
304
+ WHERE ${whereSql}
305
+ ),
306
+ "page" AS (
307
+ SELECT * FROM "matched"
308
+ ORDER BY ${orderSql}
309
+ OFFSET ${bind(skip)}::bigint
310
+ LIMIT ${bind(take)}::int
311
+ )
312
+ SELECT "id", "integrationId", "sourceId", ${mappingSql}, "createdAt", "updatedAt",
313
+ (SELECT COUNT(*)::int FROM "matched") AS "__total"
314
+ FROM (VALUES (1)) AS "one"
315
+ LEFT JOIN "page" ON true
316
+ ORDER BY ${orderSql}
317
+ `;
318
+ const rows = await this.prisma.$queryRawUnsafe(sql, ...params);
319
+ const mappings = await decryptQueriedMappings(
320
+ rows
321
+ .filter((row) => row.id !== null)
322
+ .map(({ __total, ...row }) => this._convertMappingIds(row))
323
+ );
324
+
325
+ return { mappings, total: rows[0].__total };
326
+ }
327
+
328
+ /**
329
+ * One where entry: a condition, or an anyOf group as a parenthesized OR.
330
+ * @private
331
+ */
332
+ _whereEntrySql(entry, bind) {
333
+ if (!entry.anyOf) return this._conditionSql(entry, bind);
334
+ const alternatives = entry.anyOf.map((condition) =>
335
+ this._conditionSql(condition, bind)
336
+ );
337
+ return `(${alternatives.join(' OR ')})`;
338
+ }
339
+
340
+ /**
341
+ * SQL predicate for one validated queryMappings condition. `exists`
342
+ * treats JSON null as absent, and `notExists` is its exact negation.
343
+ * @private
344
+ */
345
+ _conditionSql({ field, path, op, value }, bind) {
346
+ const column = COLUMNS[field];
347
+ const operand = path
348
+ ? jsonPathOperand(column, bind(path))
349
+ : { text: column };
350
+ return CONDITION_SQL[op]({
351
+ ...operand,
352
+ value: value === undefined ? undefined : bind(value),
353
+ });
354
+ }
355
+
356
+ /**
357
+ * NULLIF folds JSON null into SQL NULL, so both sort after every value.
358
+ * @private
359
+ */
360
+ _orderSql({ path, direction }, bind) {
361
+ const { json } = jsonPathOperand(COLUMNS.mapping, bind(path));
362
+ const value = `NULLIF(${json}, 'null'::jsonb)`;
363
+ const sqlDirection = SQL_DIRECTIONS[direction];
364
+ return `${value} ${sqlDirection} NULLS LAST, "id" ${sqlDirection}`;
365
+ }
366
+
367
+ /**
368
+ * Find mapping by ID
369
+ * @param {string} id - Mapping ID (string from application layer)
370
+ * @returns {Promise<Object|null>} Mapping object with string IDs or null
371
+ */
372
+ async findMappingById(id) {
373
+ const intId = this._convertId(id);
374
+ const mapping = await this.prisma.integrationMapping.findUnique({
375
+ where: { id: intId },
376
+ });
377
+ return this._convertMappingIds(mapping);
378
+ }
379
+
380
+ /**
381
+ * Update mapping by ID
382
+ * @param {string} id - Mapping ID (string from application layer)
383
+ * @param {Object} updates - Fields to update (with string IDs from application layer)
384
+ * @returns {Promise<Object>} Updated mapping object with string IDs
385
+ */
386
+ async updateMapping(id, updates) {
387
+ const intId = this._convertId(id);
388
+
389
+ // Convert integrationId if present in updates
390
+ const data = { ...updates };
391
+ if (data.integrationId !== undefined) {
392
+ data.integrationId = this._convertId(data.integrationId);
393
+ }
394
+
395
+ const mapping = await this.prisma.integrationMapping.update({
396
+ where: { id: intId },
397
+ data,
398
+ });
399
+ return this._convertMappingIds(mapping);
400
+ }
401
+ }
402
+
403
+ module.exports = { IntegrationMappingRepositoryPostgres };