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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/admin-scripts/index.js +52 -0
  4. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +166 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +228 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +268 -0
  9. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  10. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  11. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  12. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  13. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  14. package/application/commands/README.md +451 -0
  15. package/application/commands/admin-script-commands.js +358 -0
  16. package/application/commands/credential-commands.js +245 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +343 -0
  19. package/application/commands/process-commands.js +135 -0
  20. package/application/commands/scheduler-commands.js +263 -0
  21. package/application/commands/user-commands.js +312 -0
  22. package/application/index.js +82 -0
  23. package/assertions/index.js +0 -3
  24. package/core/CLAUDE.md +690 -0
  25. package/core/Worker.js +60 -24
  26. package/core/create-handler.js +84 -6
  27. package/credential/repositories/credential-repository-documentdb.js +304 -0
  28. package/credential/repositories/credential-repository-factory.js +54 -0
  29. package/credential/repositories/credential-repository-interface.js +98 -0
  30. package/credential/repositories/credential-repository-mongo.js +269 -0
  31. package/credential/repositories/credential-repository-postgres.js +287 -0
  32. package/credential/repositories/credential-repository.js +300 -0
  33. package/credential/use-cases/get-credential-for-user.js +25 -0
  34. package/credential/use-cases/update-authentication-status.js +15 -0
  35. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  36. package/database/adapters/lambda-invoker.js +97 -0
  37. package/database/config.js +154 -0
  38. package/database/documentdb-encryption-service.js +330 -0
  39. package/database/documentdb-utils.js +136 -0
  40. package/database/encryption/README.md +839 -0
  41. package/database/encryption/documentdb-encryption-service.md +3575 -0
  42. package/database/encryption/encryption-schema-registry.js +401 -0
  43. package/database/encryption/field-encryption-service.js +254 -0
  44. package/database/encryption/logger.js +79 -0
  45. package/database/encryption/prisma-encryption-extension.js +230 -0
  46. package/database/index.js +21 -21
  47. package/database/prisma.js +182 -0
  48. package/database/repositories/health-check-repository-documentdb.js +138 -0
  49. package/database/repositories/health-check-repository-factory.js +48 -0
  50. package/database/repositories/health-check-repository-interface.js +82 -0
  51. package/database/repositories/health-check-repository-mongodb.js +89 -0
  52. package/database/repositories/health-check-repository-postgres.js +82 -0
  53. package/database/repositories/migration-status-repository-s3.js +137 -0
  54. package/database/use-cases/check-database-health-use-case.js +29 -0
  55. package/database/use-cases/check-database-state-use-case.js +81 -0
  56. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  57. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  58. package/database/use-cases/get-migration-status-use-case.js +93 -0
  59. package/database/use-cases/run-database-migration-use-case.js +139 -0
  60. package/database/use-cases/test-encryption-use-case.js +253 -0
  61. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  62. package/database/utils/mongodb-collection-utils.js +94 -0
  63. package/database/utils/mongodb-schema-init.js +108 -0
  64. package/database/utils/prisma-runner.js +477 -0
  65. package/database/utils/prisma-schema-parser.js +182 -0
  66. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  67. package/encrypt/Cryptor.js +34 -168
  68. package/encrypt/index.js +1 -2
  69. package/errors/client-safe-error.js +26 -0
  70. package/errors/fetch-error.js +15 -7
  71. package/errors/index.js +2 -0
  72. package/generated/prisma-mongodb/client.d.ts +1 -0
  73. package/generated/prisma-mongodb/client.js +5 -0
  74. package/generated/prisma-mongodb/default.d.ts +1 -0
  75. package/generated/prisma-mongodb/default.js +5 -0
  76. package/generated/prisma-mongodb/edge.d.ts +1 -0
  77. package/generated/prisma-mongodb/edge.js +374 -0
  78. package/generated/prisma-mongodb/index-browser.js +356 -0
  79. package/generated/prisma-mongodb/index.d.ts +25667 -0
  80. package/generated/prisma-mongodb/index.js +399 -0
  81. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  82. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  83. package/generated/prisma-mongodb/package.json +183 -0
  84. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  85. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  86. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  87. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  88. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  89. package/generated/prisma-mongodb/runtime/library.js +147 -0
  90. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  91. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  92. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  93. package/generated/prisma-mongodb/schema.prisma +432 -0
  94. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  95. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  96. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  97. package/generated/prisma-mongodb/wasm.js +381 -0
  98. package/generated/prisma-postgresql/client.d.ts +1 -0
  99. package/generated/prisma-postgresql/client.js +5 -0
  100. package/generated/prisma-postgresql/default.d.ts +1 -0
  101. package/generated/prisma-postgresql/default.js +5 -0
  102. package/generated/prisma-postgresql/edge.d.ts +1 -0
  103. package/generated/prisma-postgresql/edge.js +395 -0
  104. package/generated/prisma-postgresql/index-browser.js +377 -0
  105. package/generated/prisma-postgresql/index.d.ts +28455 -0
  106. package/generated/prisma-postgresql/index.js +420 -0
  107. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  108. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  109. package/generated/prisma-postgresql/package.json +183 -0
  110. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  111. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  112. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  113. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  114. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  115. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  116. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  117. package/generated/prisma-postgresql/runtime/library.js +147 -0
  118. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  119. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  120. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  121. package/generated/prisma-postgresql/schema.prisma +414 -0
  122. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  123. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  124. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  125. package/generated/prisma-postgresql/wasm.js +402 -0
  126. package/handlers/WEBHOOKS.md +653 -0
  127. package/handlers/app-definition-loader.js +43 -0
  128. package/handlers/app-handler-helpers.js +57 -0
  129. package/handlers/backend-utils.js +316 -0
  130. package/handlers/database-migration-handler.js +227 -0
  131. package/handlers/integration-event-dispatcher.js +54 -0
  132. package/handlers/middleware/admin-auth.js +73 -0
  133. package/handlers/routers/HEALTHCHECK.md +342 -0
  134. package/handlers/routers/auth.js +15 -0
  135. package/handlers/routers/db-migration.handler.js +29 -0
  136. package/handlers/routers/db-migration.js +311 -0
  137. package/handlers/routers/health.js +518 -0
  138. package/handlers/routers/integration-defined-routers.js +117 -0
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +63 -0
  142. package/handlers/routers/websocket.js +57 -0
  143. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  144. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  145. package/handlers/workers/db-migration.js +352 -0
  146. package/handlers/workers/dlq-processor.js +63 -0
  147. package/handlers/workers/integration-defined-workers.js +30 -0
  148. package/index.js +111 -50
  149. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  150. package/infrastructure/scheduler/index.js +33 -0
  151. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  152. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  153. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  154. package/integrations/EXTENSIONS.md +240 -0
  155. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  156. package/integrations/extension.js +254 -0
  157. package/integrations/index.js +20 -10
  158. package/integrations/integration-base.js +619 -72
  159. package/integrations/integration-router.js +460 -218
  160. package/integrations/options.js +3 -4
  161. package/integrations/repositories/config-patch-shared.js +43 -0
  162. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  163. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  164. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  165. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  166. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  167. package/integrations/repositories/integration-mapping-repository.js +156 -0
  168. package/integrations/repositories/integration-repository-documentdb.js +284 -0
  169. package/integrations/repositories/integration-repository-factory.js +51 -0
  170. package/integrations/repositories/integration-repository-interface.js +173 -0
  171. package/integrations/repositories/integration-repository-mongo.js +414 -0
  172. package/integrations/repositories/integration-repository-postgres.js +452 -0
  173. package/integrations/repositories/process-repository-documentdb.js +311 -0
  174. package/integrations/repositories/process-repository-factory.js +53 -0
  175. package/integrations/repositories/process-repository-interface.js +136 -0
  176. package/integrations/repositories/process-repository-mongo.js +262 -0
  177. package/integrations/repositories/process-repository-postgres.js +380 -0
  178. package/integrations/repositories/process-update-ops-shared.js +112 -0
  179. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  180. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  181. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  182. package/integrations/use-cases/create-integration.js +215 -0
  183. package/integrations/use-cases/create-process.js +130 -0
  184. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  185. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  186. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  187. package/integrations/use-cases/get-integration-for-user.js +78 -0
  188. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  189. package/integrations/use-cases/get-integration-instance.js +83 -0
  190. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  191. package/integrations/use-cases/get-possible-integrations.js +27 -0
  192. package/integrations/use-cases/get-process.js +89 -0
  193. package/integrations/use-cases/index.js +19 -0
  194. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  195. package/integrations/use-cases/load-integration-context.js +71 -0
  196. package/integrations/use-cases/patch-integration-config.js +39 -0
  197. package/integrations/use-cases/process-errors.js +28 -0
  198. package/integrations/use-cases/update-integration-config.js +32 -0
  199. package/integrations/use-cases/update-integration-messages.js +44 -0
  200. package/integrations/use-cases/update-integration-status.js +32 -0
  201. package/integrations/use-cases/update-integration.js +92 -0
  202. package/integrations/use-cases/update-process-metrics.js +216 -0
  203. package/integrations/use-cases/update-process-state.js +163 -0
  204. package/integrations/utils/map-integration-dto.js +37 -0
  205. package/jest-global-setup-noop.js +3 -0
  206. package/jest-global-teardown-noop.js +3 -0
  207. package/logs/logger.js +0 -4
  208. package/{module-plugin → modules}/index.js +0 -10
  209. package/modules/module-factory.js +56 -0
  210. package/modules/module.js +274 -0
  211. package/modules/repositories/module-repository-documentdb.js +350 -0
  212. package/modules/repositories/module-repository-factory.js +40 -0
  213. package/modules/repositories/module-repository-interface.js +145 -0
  214. package/modules/repositories/module-repository-mongo.js +436 -0
  215. package/modules/repositories/module-repository-postgres.js +481 -0
  216. package/modules/repositories/module-repository.js +369 -0
  217. package/modules/requester/api-key.js +52 -0
  218. package/modules/requester/oauth-2.js +396 -0
  219. package/modules/requester/requester.js +285 -0
  220. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  221. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  222. package/modules/tests/doubles/test-module-factory.js +16 -0
  223. package/modules/tests/doubles/test-module-repository.js +39 -0
  224. package/modules/use-cases/get-entities-for-user.js +32 -0
  225. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  226. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  227. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  228. package/modules/use-cases/get-module.js +74 -0
  229. package/modules/use-cases/process-authorization-callback.js +243 -0
  230. package/modules/use-cases/refresh-entity-options.js +72 -0
  231. package/modules/use-cases/test-module-auth.js +72 -0
  232. package/modules/utils/map-module-dto.js +18 -0
  233. package/package.json +82 -50
  234. package/prisma-mongodb/schema.prisma +432 -0
  235. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  236. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  237. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  238. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  239. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  240. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  241. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  242. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  243. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  244. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  245. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  246. package/prisma-postgresql/schema.prisma +414 -0
  247. package/queues/queuer-util.js +103 -21
  248. package/reporting/README.md +86 -0
  249. package/reporting/index.js +17 -0
  250. package/reporting/reporting-router.js +77 -0
  251. package/reporting/repositories/reporting-repository-documentdb.js +127 -0
  252. package/reporting/repositories/reporting-repository-factory.js +35 -0
  253. package/reporting/repositories/reporting-repository-interface.js +16 -0
  254. package/reporting/repositories/reporting-repository-mongo.js +54 -0
  255. package/reporting/repositories/reporting-repository-postgres.js +70 -0
  256. package/reporting/use-cases/index.js +6 -0
  257. package/reporting/use-cases/list-integrations-report.js +142 -0
  258. package/syncs/manager.js +468 -443
  259. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  260. package/syncs/repositories/sync-repository-factory.js +43 -0
  261. package/syncs/repositories/sync-repository-interface.js +109 -0
  262. package/syncs/repositories/sync-repository-mongo.js +239 -0
  263. package/syncs/repositories/sync-repository-postgres.js +319 -0
  264. package/syncs/sync.js +0 -1
  265. package/token/repositories/token-repository-documentdb.js +137 -0
  266. package/token/repositories/token-repository-factory.js +40 -0
  267. package/token/repositories/token-repository-interface.js +131 -0
  268. package/token/repositories/token-repository-mongo.js +219 -0
  269. package/token/repositories/token-repository-postgres.js +264 -0
  270. package/token/repositories/token-repository.js +219 -0
  271. package/types/associations/index.d.ts +0 -17
  272. package/types/core/index.d.ts +12 -4
  273. package/types/database/index.d.ts +10 -2
  274. package/types/encrypt/index.d.ts +5 -3
  275. package/types/integrations/index.d.ts +3 -8
  276. package/types/module-plugin/index.d.ts +17 -69
  277. package/types/syncs/index.d.ts +0 -17
  278. package/user/repositories/user-repository-documentdb.js +458 -0
  279. package/user/repositories/user-repository-factory.js +52 -0
  280. package/user/repositories/user-repository-interface.js +214 -0
  281. package/user/repositories/user-repository-mongo.js +323 -0
  282. package/user/repositories/user-repository-postgres.js +377 -0
  283. package/user/tests/doubles/test-user-repository.js +72 -0
  284. package/user/use-cases/authenticate-user.js +127 -0
  285. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  286. package/user/use-cases/create-individual-user.js +61 -0
  287. package/user/use-cases/create-organization-user.js +47 -0
  288. package/user/use-cases/create-token-for-user-id.js +30 -0
  289. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  290. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  291. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  292. package/user/use-cases/login-user.js +122 -0
  293. package/user/user.js +125 -0
  294. package/utils/backend-path.js +38 -0
  295. package/utils/index.js +6 -0
  296. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  297. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  298. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  299. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  300. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  301. package/websocket/repositories/websocket-connection-repository.js +161 -0
  302. package/assertions/is-equal.js +0 -17
  303. package/associations/model.js +0 -54
  304. package/database/models/IndividualUser.js +0 -76
  305. package/database/models/OrganizationUser.js +0 -29
  306. package/database/models/State.js +0 -9
  307. package/database/models/Token.js +0 -70
  308. package/database/models/UserModel.js +0 -7
  309. package/database/models/WebsocketConnection.js +0 -49
  310. package/database/mongo.js +0 -45
  311. package/database/mongoose.js +0 -5
  312. package/encrypt/Cryptor.test.js +0 -32
  313. package/encrypt/encrypt.js +0 -132
  314. package/encrypt/encrypt.test.js +0 -1069
  315. package/encrypt/test-encrypt.js +0 -107
  316. package/errors/base-error.test.js +0 -32
  317. package/errors/fetch-error.test.js +0 -79
  318. package/errors/halt-error.test.js +0 -11
  319. package/errors/validation-errors.test.js +0 -120
  320. package/integrations/create-frigg-backend.js +0 -31
  321. package/integrations/integration-factory.js +0 -251
  322. package/integrations/integration-mapping.js +0 -43
  323. package/integrations/integration-model.js +0 -46
  324. package/integrations/integration-user.js +0 -144
  325. package/integrations/test/integration-base.test.js +0 -144
  326. package/lambda/TimeoutCatcher.test.js +0 -68
  327. package/logs/logger.test.js +0 -76
  328. package/module-plugin/auther.js +0 -350
  329. package/module-plugin/credential.js +0 -22
  330. package/module-plugin/entity-manager.js +0 -70
  331. package/module-plugin/entity.js +0 -46
  332. package/module-plugin/manager.js +0 -169
  333. package/module-plugin/module-factory.js +0 -61
  334. package/module-plugin/requester/api-key.js +0 -36
  335. package/module-plugin/requester/oauth-2.js +0 -219
  336. package/module-plugin/requester/requester.js +0 -165
  337. package/module-plugin/requester/requester.test.js +0 -28
  338. package/module-plugin/test/auther.test.js +0 -97
  339. package/syncs/model.js +0 -62
  340. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  341. /package/{module-plugin → modules}/requester/basic.js +0 -0
  342. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,262 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { ProcessRepositoryInterface } = require('./process-repository-interface');
3
+ const { validateOps } = require('./process-update-ops-shared');
4
+
5
+ /**
6
+ * MongoDB Process Repository Adapter
7
+ * Handles process persistence using Prisma with MongoDB
8
+ *
9
+ * MongoDB-specific characteristics:
10
+ * - Uses scalar fields for relations (userId, integrationId)
11
+ * - IDs are strings with @db.ObjectId
12
+ * - JSON fields for flexible context and results storage
13
+ * - Array field for childProcesses references
14
+ *
15
+ * Design Philosophy:
16
+ * - Generic Process model supports any type of long-running operation
17
+ * - Context and results stored as JSON for maximum flexibility
18
+ * - Integration-specific logic lives in use cases and services
19
+ */
20
+ class ProcessRepositoryMongo extends ProcessRepositoryInterface {
21
+ constructor() {
22
+ super();
23
+ this.prisma = prisma;
24
+ }
25
+
26
+ /**
27
+ * Create a new process record
28
+ * @param {Object} processData - Process data to create
29
+ * @returns {Promise<Object>} Created process record
30
+ */
31
+ async create(processData) {
32
+ const process = await this.prisma.process.create({
33
+ data: {
34
+ userId: processData.userId,
35
+ integrationId: processData.integrationId,
36
+ name: processData.name,
37
+ type: processData.type,
38
+ state: processData.state || 'INITIALIZING',
39
+ context: processData.context || {},
40
+ results: processData.results || {},
41
+ childProcesses: processData.childProcesses || [],
42
+ parentProcessId: processData.parentProcessId || null,
43
+ },
44
+ });
45
+
46
+ return this._toPlainObject(process);
47
+ }
48
+
49
+ /**
50
+ * Find a process by ID
51
+ * @param {string} processId - Process ID to find
52
+ * @returns {Promise<Object|null>} Process record or null if not found
53
+ */
54
+ async findById(processId) {
55
+ const process = await this.prisma.process.findUnique({
56
+ where: { id: processId },
57
+ });
58
+
59
+ return process ? this._toPlainObject(process) : null;
60
+ }
61
+
62
+ /**
63
+ * Update a process record
64
+ * @param {string} processId - Process ID to update
65
+ * @param {Object} updates - Fields to update
66
+ * @returns {Promise<Object>} Updated process record
67
+ */
68
+ async update(processId, updates) {
69
+ // Prepare update data, excluding undefined values
70
+ const updateData = {};
71
+
72
+ if (updates.state !== undefined) {
73
+ updateData.state = updates.state;
74
+ }
75
+ if (updates.context !== undefined) {
76
+ updateData.context = updates.context;
77
+ }
78
+ if (updates.results !== undefined) {
79
+ updateData.results = updates.results;
80
+ }
81
+ if (updates.childProcesses !== undefined) {
82
+ updateData.childProcesses = updates.childProcesses;
83
+ }
84
+ if (updates.parentProcessId !== undefined) {
85
+ updateData.parentProcessId = updates.parentProcessId;
86
+ }
87
+
88
+ const process = await this.prisma.process.update({
89
+ where: { id: processId },
90
+ data: updateData,
91
+ });
92
+
93
+ return this._toPlainObject(process);
94
+ }
95
+
96
+ /**
97
+ * Atomic process update — race-safe counterpart to `update()`.
98
+ *
99
+ * Uses `findAndModify` via `$runCommandRaw` so increments, sets, and
100
+ * pushes land in one server-side write. Contention on the same
101
+ * document serializes at the MongoDB level; no Node-side read-
102
+ * modify-write. Returns the post-update document.
103
+ *
104
+ * @param {string} processId
105
+ * @param {import('./process-repository-interface').ProcessUpdateOps} ops
106
+ * @returns {Promise<Object|null>}
107
+ */
108
+ async applyProcessUpdate(processId, ops) {
109
+ const normalized = validateOps(ops);
110
+
111
+ const update = {};
112
+ const $set = {};
113
+
114
+ if (Object.keys(normalized.increment).length > 0) {
115
+ update.$inc = { ...normalized.increment };
116
+ }
117
+ for (const [path, value] of Object.entries(normalized.set)) {
118
+ $set[path] = value;
119
+ }
120
+ if (normalized.newState !== null) {
121
+ $set.state = normalized.newState;
122
+ }
123
+ $set.updatedAt = new Date();
124
+ update.$set = $set;
125
+
126
+ if (Object.keys(normalized.pushSlice).length > 0) {
127
+ update.$push = {};
128
+ for (const [path, spec] of Object.entries(normalized.pushSlice)) {
129
+ update.$push[path] = {
130
+ $each: spec.values,
131
+ $slice: -spec.keepLast,
132
+ };
133
+ }
134
+ }
135
+
136
+ const result = await this.prisma.$runCommandRaw({
137
+ findAndModify: 'Process',
138
+ query: { _id: { $oid: processId } },
139
+ update,
140
+ new: true,
141
+ });
142
+
143
+ const doc = result && result.value;
144
+ if (!doc) return null;
145
+ return this._toPlainObject(this._hydrateRawMongoDoc(doc));
146
+ }
147
+
148
+ /**
149
+ * Shape a raw Mongo document (as returned by $runCommandRaw) to match
150
+ * Prisma's `findUnique` output so the existing `_toPlainObject` works
151
+ * without modification. EJSON round-trips give us `{$oid, $date}` wrappers
152
+ * that need unwrapping.
153
+ * @private
154
+ */
155
+ _hydrateRawMongoDoc(doc) {
156
+ const hydrated = { ...doc };
157
+ if (doc._id) hydrated.id = doc._id.$oid ?? doc._id;
158
+ for (const field of ['createdAt', 'updatedAt']) {
159
+ const raw = doc[field];
160
+ if (raw && typeof raw === 'object' && raw.$date) {
161
+ hydrated[field] = new Date(raw.$date);
162
+ }
163
+ }
164
+ return hydrated;
165
+ }
166
+
167
+ /**
168
+ * Find processes by integration and type
169
+ * @param {string} integrationId - Integration ID
170
+ * @param {string} type - Process type
171
+ * @returns {Promise<Array>} Array of process records
172
+ */
173
+ async findByIntegrationAndType(integrationId, type) {
174
+ const processes = await this.prisma.process.findMany({
175
+ where: {
176
+ integrationId,
177
+ type,
178
+ },
179
+ orderBy: {
180
+ createdAt: 'desc',
181
+ },
182
+ });
183
+
184
+ return processes.map((p) => this._toPlainObject(p));
185
+ }
186
+
187
+ /**
188
+ * Find active processes (not in excluded states)
189
+ * @param {string} integrationId - Integration ID
190
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
191
+ * @returns {Promise<Array>} Array of active process records
192
+ */
193
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
194
+ const processes = await this.prisma.process.findMany({
195
+ where: {
196
+ integrationId,
197
+ state: {
198
+ notIn: excludeStates,
199
+ },
200
+ },
201
+ orderBy: {
202
+ createdAt: 'desc',
203
+ },
204
+ });
205
+
206
+ return processes.map((p) => this._toPlainObject(p));
207
+ }
208
+
209
+ /**
210
+ * Find a process by name (most recent)
211
+ * @param {string} name - Process name
212
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
213
+ */
214
+ async findByName(name) {
215
+ const process = await this.prisma.process.findFirst({
216
+ where: { name },
217
+ orderBy: {
218
+ createdAt: 'desc',
219
+ },
220
+ });
221
+
222
+ return process ? this._toPlainObject(process) : null;
223
+ }
224
+
225
+ /**
226
+ * Delete a process by ID
227
+ * @param {string} processId - Process ID to delete
228
+ * @returns {Promise<void>}
229
+ */
230
+ async deleteById(processId) {
231
+ await this.prisma.process.delete({
232
+ where: { id: processId },
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Convert Prisma model to plain JavaScript object
238
+ * Ensures consistent API across repository implementations
239
+ * @private
240
+ * @param {Object} process - Prisma process model
241
+ * @returns {Object} Plain process object
242
+ */
243
+ _toPlainObject(process) {
244
+ return {
245
+ id: process.id,
246
+ userId: process.userId,
247
+ integrationId: process.integrationId,
248
+ name: process.name,
249
+ type: process.type,
250
+ state: process.state,
251
+ context: process.context,
252
+ results: process.results,
253
+ childProcesses: process.childProcesses,
254
+ parentProcessId: process.parentProcessId,
255
+ createdAt: process.createdAt,
256
+ updatedAt: process.updatedAt,
257
+ };
258
+ }
259
+ }
260
+
261
+ module.exports = { ProcessRepositoryMongo };
262
+
@@ -0,0 +1,380 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ ProcessRepositoryInterface,
4
+ } = require('./process-repository-interface');
5
+ const { validateOps, splitPath } = require('./process-update-ops-shared');
6
+
7
+ /**
8
+ * PostgreSQL Process Repository Adapter
9
+ * Handles process persistence using Prisma with PostgreSQL
10
+ *
11
+ * PostgreSQL-specific characteristics:
12
+ * - Uses foreign key constraints for relations
13
+ * - JSONB type for context and results (efficient querying)
14
+ * - Array type for childProcesses references
15
+ * - Transactional support available if needed
16
+ *
17
+ * Design Philosophy:
18
+ * - Same interface as MongoDB repository
19
+ * - Prisma abstracts away most database-specific details
20
+ * - Minor differences in JSON handling internally managed by Prisma
21
+ */
22
+ class ProcessRepositoryPostgres extends ProcessRepositoryInterface {
23
+ constructor() {
24
+ super();
25
+ this.prisma = prisma;
26
+ }
27
+
28
+ /**
29
+ * Convert string ID to integer for PostgreSQL queries
30
+ * @private
31
+ * @param {string|number|null|undefined} id - ID to convert
32
+ * @returns {number|null|undefined} Integer ID or null/undefined
33
+ * @throws {Error} If ID cannot be converted to integer
34
+ */
35
+ _convertId(id) {
36
+ if (id === null || id === undefined) return id;
37
+ const parsed = parseInt(id, 10);
38
+ if (isNaN(parsed)) {
39
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
40
+ }
41
+ return parsed;
42
+ }
43
+
44
+ /**
45
+ * Create a new process record
46
+ * @param {Object} processData - Process data to create
47
+ * @returns {Promise<Object>} Created process record
48
+ */
49
+ async create(processData) {
50
+ const process = await this.prisma.process.create({
51
+ data: {
52
+ userId: this._convertId(processData.userId),
53
+ integrationId: this._convertId(processData.integrationId),
54
+ name: processData.name,
55
+ type: processData.type,
56
+ state: processData.state || 'INITIALIZING',
57
+ context: processData.context || {},
58
+ results: processData.results || {},
59
+ parentProcessId: this._convertId(processData.parentProcessId),
60
+ },
61
+ });
62
+
63
+ return this._toPlainObject(process);
64
+ }
65
+
66
+ /**
67
+ * Find a process by ID
68
+ * @param {string} processId - Process ID to find
69
+ * @returns {Promise<Object|null>} Process record or null if not found
70
+ */
71
+ async findById(processId) {
72
+ const process = await this.prisma.process.findUnique({
73
+ where: { id: this._convertId(processId) },
74
+ });
75
+
76
+ return process ? this._toPlainObject(process) : null;
77
+ }
78
+
79
+ /**
80
+ * Update a process record
81
+ * @param {string} processId - Process ID to update
82
+ * @param {Object} updates - Fields to update
83
+ * @returns {Promise<Object>} Updated process record
84
+ */
85
+ async update(processId, updates) {
86
+ // Prepare update data, excluding undefined values
87
+ const updateData = {};
88
+
89
+ if (updates.state !== undefined) {
90
+ updateData.state = updates.state;
91
+ }
92
+ if (updates.context !== undefined) {
93
+ updateData.context = updates.context;
94
+ }
95
+ if (updates.results !== undefined) {
96
+ updateData.results = updates.results;
97
+ }
98
+ if (updates.parentProcessId !== undefined) {
99
+ updateData.parentProcessId = this._convertId(
100
+ updates.parentProcessId
101
+ );
102
+ }
103
+
104
+ const process = await this.prisma.process.update({
105
+ where: { id: this._convertId(processId) },
106
+ data: updateData,
107
+ });
108
+
109
+ return this._toPlainObject(process);
110
+ }
111
+
112
+ /**
113
+ * Atomic process update — race-safe counterpart to `update()`.
114
+ *
115
+ * Compiles the `ProcessUpdateOps` into ONE `UPDATE "Process" ...
116
+ * RETURNING *` statement with nested `jsonb_set` calls for every
117
+ * context/results mutation. Postgres applies row-level locking
118
+ * during UPDATE, so concurrent callers on the same row serialize at
119
+ * the DB without any read-modify-write in Node.
120
+ *
121
+ * Path segments have been regex-validated upstream (see
122
+ * process-update-ops-shared.js); they are embedded directly into
123
+ * the SQL string. All values go through positional parameters.
124
+ *
125
+ * @param {string} processId
126
+ * @param {ProcessUpdateOps} ops
127
+ * @returns {Promise<Object|null>}
128
+ */
129
+ async applyProcessUpdate(processId, ops) {
130
+ const normalized = validateOps(ops);
131
+ const id = this._convertId(processId);
132
+
133
+ // Build the SQL expression for each JSON column. We start each
134
+ // column's expression from the column itself and wrap it in
135
+ // jsonb_set(...) calls — one wrap per operation targeting that
136
+ // column. If no op targets a column, we omit that SET clause so
137
+ // we don't issue a pointless self-assignment.
138
+ const params = [];
139
+ /** @type {(v:unknown)=>string} positional placeholder, 1-indexed */
140
+ const bind = (v) => {
141
+ params.push(v);
142
+ return `$${params.length}`;
143
+ };
144
+
145
+ const columnExpressions = this._buildColumnExpressions(
146
+ normalized,
147
+ bind
148
+ );
149
+ const setClauses = [];
150
+ for (const [column, expr] of Object.entries(columnExpressions)) {
151
+ setClauses.push(`"${column}" = ${expr}`);
152
+ }
153
+ if (normalized.newState !== null) {
154
+ setClauses.push(`"state" = ${bind(normalized.newState)}`);
155
+ }
156
+ setClauses.push(`"updatedAt" = NOW()`);
157
+
158
+ const idPlaceholder = bind(id);
159
+ const sql = `
160
+ UPDATE "Process"
161
+ SET ${setClauses.join(', ')}
162
+ WHERE "id" = ${idPlaceholder}
163
+ RETURNING *
164
+ `;
165
+
166
+ const rows = await this.prisma.$queryRawUnsafe(sql, ...params);
167
+ if (!rows || rows.length === 0) return null;
168
+ return this._toPlainObject(rows[0]);
169
+ }
170
+
171
+ /**
172
+ * Returns a map of column → SQL expression with all jsonb_set wraps
173
+ * applied. Used only by applyProcessUpdate.
174
+ * @private
175
+ */
176
+ _buildColumnExpressions(ops, bind) {
177
+ const byColumn = { context: null, results: null };
178
+
179
+ // Seed with the column itself (wrapped with COALESCE so that
180
+ // a NULL column doesn't break jsonb_set).
181
+ const seed = (col) =>
182
+ byColumn[col] ??
183
+ (byColumn[col] = `COALESCE("${col}", '{}'::jsonb)`);
184
+
185
+ /**
186
+ * Postgres `jsonb_set(target, path, value, create_missing=true)`
187
+ * only creates the LEAF segment if missing — intermediate segments
188
+ * that don't exist as objects cause the call to return `target`
189
+ * unchanged (silent no-op). For a path like `context.a.b.c` on a
190
+ * doc where `context.a` is missing, we'd bail on the write.
191
+ *
192
+ * This helper wraps `prev` in a chain of `jsonb_set` calls that
193
+ * ensure each intermediate prefix path is an object, preserving
194
+ * its contents if it's already present:
195
+ *
196
+ * ensureParents(prev, ['a','b','c'])
197
+ * ⇒ jsonb_set(
198
+ * jsonb_set(prev, '{a}', COALESCE(prev#>'{a}', '{}'::jsonb), true),
199
+ * '{a,b}', COALESCE(${that}#>'{a,b}', '{}'::jsonb), true)
200
+ *
201
+ * The caller then wraps this result with its own `jsonb_set` for
202
+ * the leaf segment. Depth-1 paths skip this entirely (no parents
203
+ * to synthesize).
204
+ */
205
+ const ensureParents = (prevExpr, segments) => {
206
+ let cur = prevExpr;
207
+ for (let i = 1; i < segments.length; i++) {
208
+ const parentPath = `'{${segments.slice(0, i).join(',')}}'`;
209
+ cur = `jsonb_set(${cur}, ${parentPath}, COALESCE(${cur} #> ${parentPath}, '{}'::jsonb), true)`;
210
+ }
211
+ return cur;
212
+ };
213
+
214
+ const wrapIncrement = (col, segments, delta) => {
215
+ const textPath = `'{${segments.join(',')}}'`;
216
+ const jsonbPath = `'{${segments.join(',')}}'`;
217
+ const prev = seed(col);
218
+ const guarded = ensureParents(prev, segments);
219
+ const nextValue = `to_jsonb(COALESCE((${guarded} #>> ${textPath})::numeric, 0) + ${bind(delta)})`;
220
+ byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${nextValue}, true)`;
221
+ };
222
+
223
+ const wrapSet = (col, segments, value) => {
224
+ const jsonbPath = `'{${segments.join(',')}}'`;
225
+ const prev = seed(col);
226
+ const guarded = ensureParents(prev, segments);
227
+ // $n::jsonb — values are serialized to JSON by Prisma when
228
+ // passed as a parameter, then cast back into jsonb.
229
+ byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${bind(JSON.stringify(value))}::jsonb, true)`;
230
+ };
231
+
232
+ const wrapPushSlice = (col, segments, spec) => {
233
+ const jsonbPath = `'{${segments.join(',')}}'`;
234
+ const prev = seed(col);
235
+ const guarded = ensureParents(prev, segments);
236
+ // Construct the sliced array in a CTE to evaluate `${newArr}`
237
+ // exactly ONCE (vs. the inline form that Postgres would still
238
+ // execute correctly but expand three times). Order is
239
+ // explicitly preserved by `jsonb_agg(... ORDER BY idx)`;
240
+ // without the ORDER BY, aggregate order is implementation-
241
+ // defined even with WITH ORDINALITY.
242
+ const sliced = `(
243
+ WITH combined AS (
244
+ SELECT COALESCE((${guarded} #> ${jsonbPath}), '[]'::jsonb) || ${bind(JSON.stringify(spec.values))}::jsonb AS arr
245
+ )
246
+ SELECT COALESCE(jsonb_agg(elem ORDER BY idx), '[]'::jsonb)
247
+ FROM combined,
248
+ jsonb_array_elements((SELECT arr FROM combined)) WITH ORDINALITY AS t(elem, idx)
249
+ WHERE idx > GREATEST(0, jsonb_array_length((SELECT arr FROM combined)) - ${bind(spec.keepLast)})
250
+ )`;
251
+ byColumn[col] = `jsonb_set(${guarded}, ${jsonbPath}, ${sliced}, true)`;
252
+ };
253
+
254
+ for (const [path, delta] of Object.entries(ops.increment)) {
255
+ const { column, segments } = splitPath(path);
256
+ wrapIncrement(column, segments, delta);
257
+ }
258
+ for (const [path, value] of Object.entries(ops.set)) {
259
+ const { column, segments } = splitPath(path);
260
+ wrapSet(column, segments, value);
261
+ }
262
+ for (const [path, spec] of Object.entries(ops.pushSlice)) {
263
+ const { column, segments } = splitPath(path);
264
+ wrapPushSlice(column, segments, spec);
265
+ }
266
+
267
+ const result = {};
268
+ for (const [col, expr] of Object.entries(byColumn)) {
269
+ if (expr !== null) result[col] = expr;
270
+ }
271
+ return result;
272
+ }
273
+
274
+ /**
275
+ * Find processes by integration and type
276
+ * @param {string} integrationId - Integration ID
277
+ * @param {string} type - Process type
278
+ * @returns {Promise<Array>} Array of process records
279
+ */
280
+ async findByIntegrationAndType(integrationId, type) {
281
+ const processes = await this.prisma.process.findMany({
282
+ where: {
283
+ integrationId: this._convertId(integrationId),
284
+ type,
285
+ },
286
+ orderBy: {
287
+ createdAt: 'desc',
288
+ },
289
+ });
290
+
291
+ return processes.map((p) => this._toPlainObject(p));
292
+ }
293
+
294
+ /**
295
+ * Find active processes (not in excluded states)
296
+ * @param {string} integrationId - Integration ID
297
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
298
+ * @returns {Promise<Array>} Array of active process records
299
+ */
300
+ async findActiveProcesses(
301
+ integrationId,
302
+ excludeStates = ['COMPLETED', 'ERROR']
303
+ ) {
304
+ const processes = await this.prisma.process.findMany({
305
+ where: {
306
+ integrationId: this._convertId(integrationId),
307
+ state: {
308
+ notIn: excludeStates,
309
+ },
310
+ },
311
+ orderBy: {
312
+ createdAt: 'desc',
313
+ },
314
+ });
315
+
316
+ return processes.map((p) => this._toPlainObject(p));
317
+ }
318
+
319
+ /**
320
+ * Find a process by name (most recent)
321
+ * @param {string} name - Process name
322
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
323
+ */
324
+ async findByName(name) {
325
+ const process = await this.prisma.process.findFirst({
326
+ where: { name },
327
+ orderBy: {
328
+ createdAt: 'desc',
329
+ },
330
+ });
331
+
332
+ return process ? this._toPlainObject(process) : null;
333
+ }
334
+
335
+ /**
336
+ * Delete a process by ID
337
+ * @param {string} processId - Process ID to delete
338
+ * @returns {Promise<void>}
339
+ */
340
+ async deleteById(processId) {
341
+ await this.prisma.process.delete({
342
+ where: { id: this._convertId(processId) },
343
+ });
344
+ }
345
+
346
+ /**
347
+ * Convert Prisma model to plain JavaScript object
348
+ * Ensures consistent API across repository implementations
349
+ * @private
350
+ * @param {Object} process - Prisma process model
351
+ * @returns {Object} Plain process object
352
+ */
353
+ _toPlainObject(process) {
354
+ return {
355
+ id: String(process.id),
356
+ userId: String(process.userId),
357
+ integrationId: String(process.integrationId),
358
+ name: process.name,
359
+ type: process.type,
360
+ state: process.state,
361
+ context: process.context,
362
+ results: process.results,
363
+ childProcesses: Array.isArray(process.childProcesses)
364
+ ? process.childProcesses.length > 0 &&
365
+ typeof process.childProcesses[0] === 'object' &&
366
+ process.childProcesses[0] !== null
367
+ ? process.childProcesses.map((child) => String(child.id))
368
+ : process.childProcesses
369
+ : [],
370
+ parentProcessId:
371
+ process.parentProcessId !== null
372
+ ? String(process.parentProcessId)
373
+ : null,
374
+ createdAt: process.createdAt,
375
+ updatedAt: process.updatedAt,
376
+ };
377
+ }
378
+ }
379
+
380
+ module.exports = { ProcessRepositoryPostgres };