@friggframework/core 2.0.0-next.8 → 2.0.0-next.81

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 (305) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +79 -8
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +268 -0
  30. package/database/encryption/field-encryption-service.js +226 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +222 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  69. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  72. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  73. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  74. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  75. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  76. package/generated/prisma-mongodb/runtime/library.js +146 -0
  77. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  78. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  79. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  80. package/generated/prisma-mongodb/schema.prisma +368 -0
  81. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  82. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  84. package/generated/prisma-mongodb/wasm.js +342 -0
  85. package/generated/prisma-postgresql/client.d.ts +1 -0
  86. package/generated/prisma-postgresql/client.js +4 -0
  87. package/generated/prisma-postgresql/default.d.ts +1 -0
  88. package/generated/prisma-postgresql/default.js +4 -0
  89. package/generated/prisma-postgresql/edge.d.ts +1 -0
  90. package/generated/prisma-postgresql/edge.js +357 -0
  91. package/generated/prisma-postgresql/index-browser.js +339 -0
  92. package/generated/prisma-postgresql/index.d.ts +25135 -0
  93. package/generated/prisma-postgresql/index.js +382 -0
  94. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  95. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  96. package/generated/prisma-postgresql/package.json +183 -0
  97. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  98. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  99. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  100. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  101. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  102. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  103. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  104. package/generated/prisma-postgresql/runtime/library.js +146 -0
  105. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  106. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  107. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  108. package/generated/prisma-postgresql/schema.prisma +351 -0
  109. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  110. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  111. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  112. package/generated/prisma-postgresql/wasm.js +364 -0
  113. package/handlers/WEBHOOKS.md +653 -0
  114. package/handlers/app-definition-loader.js +38 -0
  115. package/handlers/app-handler-helpers.js +57 -0
  116. package/handlers/backend-utils.js +262 -0
  117. package/handlers/database-migration-handler.js +227 -0
  118. package/handlers/integration-event-dispatcher.js +54 -0
  119. package/handlers/routers/HEALTHCHECK.md +342 -0
  120. package/handlers/routers/auth.js +15 -0
  121. package/handlers/routers/db-migration.handler.js +29 -0
  122. package/handlers/routers/db-migration.js +326 -0
  123. package/handlers/routers/health.js +516 -0
  124. package/handlers/routers/integration-defined-routers.js +45 -0
  125. package/handlers/routers/integration-webhook-routers.js +67 -0
  126. package/handlers/routers/user.js +63 -0
  127. package/handlers/routers/websocket.js +57 -0
  128. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  129. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  130. package/handlers/workers/db-migration.js +352 -0
  131. package/handlers/workers/dlq-processor.js +63 -0
  132. package/handlers/workers/integration-defined-workers.js +23 -0
  133. package/index.js +82 -46
  134. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  135. package/infrastructure/scheduler/index.js +33 -0
  136. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  137. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  138. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  139. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  140. package/integrations/index.js +12 -10
  141. package/integrations/integration-base.js +364 -55
  142. package/integrations/integration-router.js +376 -179
  143. package/integrations/options.js +1 -1
  144. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  145. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  146. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  147. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  148. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  149. package/integrations/repositories/integration-mapping-repository.js +156 -0
  150. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  151. package/integrations/repositories/integration-repository-factory.js +51 -0
  152. package/integrations/repositories/integration-repository-interface.js +144 -0
  153. package/integrations/repositories/integration-repository-mongo.js +330 -0
  154. package/integrations/repositories/integration-repository-postgres.js +385 -0
  155. package/integrations/repositories/process-repository-documentdb.js +311 -0
  156. package/integrations/repositories/process-repository-factory.js +53 -0
  157. package/integrations/repositories/process-repository-interface.js +136 -0
  158. package/integrations/repositories/process-repository-mongo.js +262 -0
  159. package/integrations/repositories/process-repository-postgres.js +380 -0
  160. package/integrations/repositories/process-update-ops-shared.js +112 -0
  161. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  162. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  163. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  164. package/integrations/use-cases/create-integration.js +83 -0
  165. package/integrations/use-cases/create-process.js +128 -0
  166. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  167. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  168. package/integrations/use-cases/get-integration-for-user.js +78 -0
  169. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  170. package/integrations/use-cases/get-integration-instance.js +83 -0
  171. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  172. package/integrations/use-cases/get-possible-integrations.js +27 -0
  173. package/integrations/use-cases/get-process.js +87 -0
  174. package/integrations/use-cases/index.js +19 -0
  175. package/integrations/use-cases/load-integration-context.js +71 -0
  176. package/integrations/use-cases/update-integration-messages.js +44 -0
  177. package/integrations/use-cases/update-integration-status.js +32 -0
  178. package/integrations/use-cases/update-integration.js +92 -0
  179. package/integrations/use-cases/update-process-metrics.js +205 -0
  180. package/integrations/use-cases/update-process-state.js +158 -0
  181. package/integrations/utils/map-integration-dto.js +37 -0
  182. package/jest-global-setup-noop.js +3 -0
  183. package/jest-global-teardown-noop.js +3 -0
  184. package/logs/logger.js +0 -4
  185. package/{module-plugin → modules}/index.js +0 -10
  186. package/modules/module-factory.js +56 -0
  187. package/modules/module.js +258 -0
  188. package/modules/repositories/module-repository-documentdb.js +335 -0
  189. package/modules/repositories/module-repository-factory.js +40 -0
  190. package/modules/repositories/module-repository-interface.js +129 -0
  191. package/modules/repositories/module-repository-mongo.js +408 -0
  192. package/modules/repositories/module-repository-postgres.js +453 -0
  193. package/modules/repositories/module-repository.js +345 -0
  194. package/modules/requester/api-key.js +52 -0
  195. package/modules/requester/oauth-2.js +396 -0
  196. package/modules/requester/requester.js +275 -0
  197. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  198. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  199. package/modules/tests/doubles/test-module-factory.js +16 -0
  200. package/modules/tests/doubles/test-module-repository.js +39 -0
  201. package/modules/use-cases/get-entities-for-user.js +32 -0
  202. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  203. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  204. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  205. package/modules/use-cases/get-module.js +74 -0
  206. package/modules/use-cases/process-authorization-callback.js +177 -0
  207. package/modules/use-cases/refresh-entity-options.js +72 -0
  208. package/modules/use-cases/test-module-auth.js +72 -0
  209. package/modules/utils/map-module-dto.js +18 -0
  210. package/package.json +82 -50
  211. package/prisma-mongodb/schema.prisma +368 -0
  212. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  213. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  214. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  215. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  216. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  217. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  218. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  219. package/prisma-postgresql/schema.prisma +351 -0
  220. package/queues/queuer-util.js +103 -21
  221. package/syncs/manager.js +468 -443
  222. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  223. package/syncs/repositories/sync-repository-factory.js +43 -0
  224. package/syncs/repositories/sync-repository-interface.js +109 -0
  225. package/syncs/repositories/sync-repository-mongo.js +239 -0
  226. package/syncs/repositories/sync-repository-postgres.js +319 -0
  227. package/syncs/sync.js +0 -1
  228. package/token/repositories/token-repository-documentdb.js +137 -0
  229. package/token/repositories/token-repository-factory.js +40 -0
  230. package/token/repositories/token-repository-interface.js +131 -0
  231. package/token/repositories/token-repository-mongo.js +219 -0
  232. package/token/repositories/token-repository-postgres.js +264 -0
  233. package/token/repositories/token-repository.js +219 -0
  234. package/types/associations/index.d.ts +0 -17
  235. package/types/core/index.d.ts +12 -4
  236. package/types/database/index.d.ts +10 -2
  237. package/types/encrypt/index.d.ts +5 -3
  238. package/types/integrations/index.d.ts +3 -8
  239. package/types/module-plugin/index.d.ts +17 -69
  240. package/types/syncs/index.d.ts +0 -17
  241. package/user/repositories/user-repository-documentdb.js +441 -0
  242. package/user/repositories/user-repository-factory.js +52 -0
  243. package/user/repositories/user-repository-interface.js +201 -0
  244. package/user/repositories/user-repository-mongo.js +308 -0
  245. package/user/repositories/user-repository-postgres.js +360 -0
  246. package/user/tests/doubles/test-user-repository.js +72 -0
  247. package/user/use-cases/authenticate-user.js +127 -0
  248. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  249. package/user/use-cases/create-individual-user.js +61 -0
  250. package/user/use-cases/create-organization-user.js +47 -0
  251. package/user/use-cases/create-token-for-user-id.js +30 -0
  252. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  253. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  254. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  255. package/user/use-cases/login-user.js +122 -0
  256. package/user/user.js +125 -0
  257. package/utils/backend-path.js +38 -0
  258. package/utils/index.js +6 -0
  259. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  260. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  261. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  262. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  263. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  264. package/websocket/repositories/websocket-connection-repository.js +161 -0
  265. package/assertions/is-equal.js +0 -17
  266. package/associations/model.js +0 -54
  267. package/database/models/IndividualUser.js +0 -76
  268. package/database/models/OrganizationUser.js +0 -29
  269. package/database/models/State.js +0 -9
  270. package/database/models/Token.js +0 -70
  271. package/database/models/UserModel.js +0 -7
  272. package/database/models/WebsocketConnection.js +0 -49
  273. package/database/mongo.js +0 -45
  274. package/database/mongoose.js +0 -5
  275. package/encrypt/Cryptor.test.js +0 -32
  276. package/encrypt/encrypt.js +0 -132
  277. package/encrypt/encrypt.test.js +0 -1069
  278. package/encrypt/test-encrypt.js +0 -107
  279. package/errors/base-error.test.js +0 -32
  280. package/errors/fetch-error.test.js +0 -79
  281. package/errors/halt-error.test.js +0 -11
  282. package/errors/validation-errors.test.js +0 -120
  283. package/integrations/create-frigg-backend.js +0 -31
  284. package/integrations/integration-factory.js +0 -251
  285. package/integrations/integration-mapping.js +0 -43
  286. package/integrations/integration-model.js +0 -46
  287. package/integrations/integration-user.js +0 -144
  288. package/integrations/test/integration-base.test.js +0 -144
  289. package/lambda/TimeoutCatcher.test.js +0 -68
  290. package/logs/logger.test.js +0 -76
  291. package/module-plugin/auther.js +0 -393
  292. package/module-plugin/credential.js +0 -22
  293. package/module-plugin/entity-manager.js +0 -70
  294. package/module-plugin/entity.js +0 -46
  295. package/module-plugin/manager.js +0 -169
  296. package/module-plugin/module-factory.js +0 -61
  297. package/module-plugin/requester/api-key.js +0 -36
  298. package/module-plugin/requester/oauth-2.js +0 -219
  299. package/module-plugin/requester/requester.js +0 -165
  300. package/module-plugin/requester/requester.test.js +0 -28
  301. package/module-plugin/test/auther.test.js +0 -97
  302. package/syncs/model.js +0 -62
  303. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  304. /package/{module-plugin → modules}/requester/basic.js +0 -0
  305. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,53 @@
1
+ const { ProcessRepositoryMongo } = require('./process-repository-mongo');
2
+ const { ProcessRepositoryPostgres } = require('./process-repository-postgres');
3
+ const {
4
+ ProcessRepositoryDocumentDB,
5
+ } = require('./process-repository-documentdb');
6
+ const config = require('../../database/config');
7
+
8
+ /**
9
+ * Process Repository Factory
10
+ * Creates the appropriate repository adapter based on database type
11
+ *
12
+ * This implements the Factory pattern for Hexagonal Architecture:
13
+ * - Reads database type from app definition (backend/index.js)
14
+ * - Returns correct adapter (MongoDB or PostgreSQL)
15
+ * - Provides clear error for unsupported databases
16
+ *
17
+ * Usage:
18
+ * ```javascript
19
+ * const repository = createProcessRepository();
20
+ * await repository.create({ userId, integrationId, name, type, state });
21
+ * ```
22
+ *
23
+ * @returns {ProcessRepositoryInterface} Configured repository adapter
24
+ * @throws {Error} If database type is not supported
25
+ */
26
+ function createProcessRepository() {
27
+ const dbType = config.DB_TYPE;
28
+
29
+ switch (dbType) {
30
+ case 'mongodb':
31
+ return new ProcessRepositoryMongo();
32
+
33
+ case 'postgresql':
34
+ return new ProcessRepositoryPostgres();
35
+
36
+ case 'documentdb':
37
+ return new ProcessRepositoryDocumentDB();
38
+
39
+ default:
40
+ throw new Error(
41
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
42
+ );
43
+ }
44
+ }
45
+
46
+ module.exports = {
47
+ createProcessRepository,
48
+ // Export adapters for direct testing
49
+ ProcessRepositoryMongo,
50
+ ProcessRepositoryPostgres,
51
+ ProcessRepositoryDocumentDB,
52
+ };
53
+
@@ -0,0 +1,136 @@
1
+ /**
2
+ * ProcessRepository Interface
3
+ *
4
+ * Defines the contract for Process data access operations.
5
+ * Implementations must provide concrete methods for all operations.
6
+ *
7
+ * This interface supports the Hexagonal Architecture pattern by:
8
+ * - Defining clear boundaries between domain logic and data access
9
+ * - Allowing multiple implementations (MongoDB, PostgreSQL, in-memory)
10
+ * - Enabling dependency injection and testability
11
+ */
12
+ class ProcessRepositoryInterface {
13
+ /**
14
+ * Create a new process record
15
+ * @param {Object} processData - Process data to create
16
+ * @param {string} processData.userId - User ID
17
+ * @param {string} processData.integrationId - Integration ID
18
+ * @param {string} processData.name - Process name
19
+ * @param {string} processData.type - Process type
20
+ * @param {string} processData.state - Initial state
21
+ * @param {Object} [processData.context] - Process context
22
+ * @param {Object} [processData.results] - Process results
23
+ * @param {string[]} [processData.childProcesses] - Child process IDs
24
+ * @param {string} [processData.parentProcessId] - Parent process ID
25
+ * @returns {Promise<Object>} Created process record
26
+ */
27
+ async create(processData) {
28
+ throw new Error('Method create() must be implemented');
29
+ }
30
+
31
+ /**
32
+ * Find a process by ID
33
+ * @param {string} processId - Process ID to find
34
+ * @returns {Promise<Object|null>} Process record or null if not found
35
+ */
36
+ async findById(processId) {
37
+ throw new Error('Method findById() must be implemented');
38
+ }
39
+
40
+ /**
41
+ * Update a process record
42
+ * @param {string} processId - Process ID to update
43
+ * @param {Object} updates - Fields to update
44
+ * @returns {Promise<Object>} Updated process record
45
+ */
46
+ async update(processId, updates) {
47
+ throw new Error('Method update() must be implemented');
48
+ }
49
+
50
+ /**
51
+ * Apply atomic mutations to a process record.
52
+ *
53
+ * Race-safe counterpart to `update()`. Where `update()` takes full
54
+ * JSON blobs and does read-modify-write at the ORM layer (clobber-
55
+ * prone under concurrent writers), `applyProcessUpdate()` describes
56
+ * the intent declaratively and each backend uses its native atomic
57
+ * primitive:
58
+ * - PostgreSQL: `jsonb_set` chain inside a single UPDATE ... RETURNING
59
+ * - MongoDB: `$inc` / `$set` / `$push` via findAndModify
60
+ * - DocumentDB: same operator set as MongoDB (with version caveats)
61
+ *
62
+ * All paths are dot-delimited and rooted in `context` or `results`
63
+ * (e.g. `context.processedRecords`,
64
+ * `results.aggregateData.totalSynced`). Paths MUST match
65
+ * `^(context|results)(\\.[a-zA-Z_][a-zA-Z0-9_]*)+$` — validated by
66
+ * each adapter before any SQL/command generation.
67
+ *
68
+ * Intended primary callers: UpdateProcessMetrics and
69
+ * UpdateProcessState. Other callers can use this directly when they
70
+ * need race-free cumulative updates.
71
+ *
72
+ * @typedef {Object} ProcessUpdateOps
73
+ * @property {Object.<string, number>} [increment] - Atomic numeric
74
+ * increments keyed by dot-path. e.g.
75
+ * `{ 'context.processedRecords': 1, 'results.aggregateData.totalSynced': 1 }`
76
+ * @property {Object.<string, *>} [set] - Atomic whole-subtree set
77
+ * keyed by dot-path. Replaces the value at the path (NOT deep
78
+ * merge). e.g. `{ 'context.fetchDone': true }`
79
+ * @property {Object.<string, {values: Array, keepLast: number}>} [pushSlice]
80
+ * Atomic array push with bounded retention (sliding window of the
81
+ * last `keepLast` items). Keys are dot-paths pointing to arrays.
82
+ * e.g. `{ 'results.aggregateData.errors': { values: [err], keepLast: 100 } }`
83
+ * @property {string} [newState] - Top-level `state` column update.
84
+ * Written alongside the JSON mutations in the same UPDATE so state
85
+ * + counters move together.
86
+ *
87
+ * @param {string} processId - Process ID to update
88
+ * @param {ProcessUpdateOps} ops - Atomic operations to apply
89
+ * @returns {Promise<Object|null>} Updated process record (post-
90
+ * mutation) or null if the process does not exist.
91
+ */
92
+ async applyProcessUpdate(processId, ops) {
93
+ throw new Error('Method applyProcessUpdate() must be implemented');
94
+ }
95
+
96
+ /**
97
+ * Find processes by integration and type
98
+ * @param {string} integrationId - Integration ID
99
+ * @param {string} type - Process type
100
+ * @returns {Promise<Array>} Array of process records
101
+ */
102
+ async findByIntegrationAndType(integrationId, type) {
103
+ throw new Error('Method findByIntegrationAndType() must be implemented');
104
+ }
105
+
106
+ /**
107
+ * Find active processes (not in excluded states)
108
+ * @param {string} integrationId - Integration ID
109
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
110
+ * @returns {Promise<Array>} Array of active process records
111
+ */
112
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
113
+ throw new Error('Method findActiveProcesses() must be implemented');
114
+ }
115
+
116
+ /**
117
+ * Find a process by name (most recent)
118
+ * @param {string} name - Process name
119
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
120
+ */
121
+ async findByName(name) {
122
+ throw new Error('Method findByName() must be implemented');
123
+ }
124
+
125
+ /**
126
+ * Delete a process by ID
127
+ * @param {string} processId - Process ID to delete
128
+ * @returns {Promise<void>}
129
+ */
130
+ async deleteById(processId) {
131
+ throw new Error('Method deleteById() must be implemented');
132
+ }
133
+ }
134
+
135
+ module.exports = { ProcessRepositoryInterface };
136
+
@@ -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
+