@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,385 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ IntegrationRepositoryInterface,
4
+ } = require('./integration-repository-interface');
5
+
6
+ /**
7
+ * PostgreSQL Integration Repository Adapter
8
+ * Handles integration persistence using Prisma with PostgreSQL
9
+ *
10
+ * PostgreSQL-specific characteristics:
11
+ * - Uses nested relations for foreign keys (user, entities)
12
+ * - Uses Int IDs with autoincrement
13
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
14
+ * - All returned IDs are converted to strings for application layer consistency
15
+ * - Implicit join tables for many-to-many relationships (_EntityToIntegration)
16
+ * - Uses connect/disconnect syntax for relations
17
+ */
18
+ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
19
+ constructor() {
20
+ super();
21
+ this.prisma = prisma;
22
+ }
23
+
24
+ /**
25
+ * Convert string ID to integer for PostgreSQL queries
26
+ * @private
27
+ * @param {string|number|null|undefined} id - ID to convert
28
+ * @returns {number|null|undefined} Integer ID or null/undefined
29
+ * @throws {Error} If ID cannot be converted to integer
30
+ */
31
+ _convertId(id) {
32
+ if (id === null || id === undefined) return id;
33
+ const parsed = parseInt(id, 10);
34
+ if (isNaN(parsed)) {
35
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
36
+ }
37
+ return parsed;
38
+ }
39
+
40
+ /**
41
+ * Convert integration object IDs to strings
42
+ * @private
43
+ * @param {Object|null} integration - Integration object from database
44
+ * @returns {Object|null} Integration with string IDs
45
+ */
46
+ _convertIntegrationIds(integration) {
47
+ if (!integration) return integration;
48
+ return {
49
+ ...integration,
50
+ id: integration.id?.toString(),
51
+ userId: integration.userId?.toString(),
52
+ entities: integration.entities?.map(e => ({
53
+ ...e,
54
+ id: e.id?.toString(),
55
+ userId: e.userId?.toString(),
56
+ credentialId: e.credentialId?.toString()
57
+ }))
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Find all integrations for a user
63
+ *
64
+ * @param {string} userId - User ID (string from application layer)
65
+ * @returns {Promise<Array>} Array of integration objects with string IDs
66
+ */
67
+ async findIntegrationsByUserId(userId) {
68
+ const intUserId = this._convertId(userId);
69
+ const integrations = await this.prisma.integration.findMany({
70
+ where: { userId: intUserId },
71
+ include: {
72
+ entities: true,
73
+ },
74
+ });
75
+
76
+ // Map to domain objects with string IDs
77
+ return integrations.map((integration) => {
78
+ const converted = this._convertIntegrationIds(integration);
79
+ return {
80
+ id: converted.id,
81
+ entitiesIds: converted.entities.map((e) => e.id),
82
+ userId: converted.userId,
83
+ config: converted.config,
84
+ version: converted.version,
85
+ status: converted.status,
86
+ messages: converted.messages,
87
+ };
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Delete integration by ID
93
+ *
94
+ * @param {string} integrationId - Integration ID (string from application layer)
95
+ * @returns {Promise<Object>} Deletion result
96
+ */
97
+ async deleteIntegrationById(integrationId) {
98
+ const intId = this._convertId(integrationId);
99
+ await this.prisma.integration.delete({
100
+ where: { id: intId },
101
+ });
102
+
103
+ // Return Mongoose-compatible result
104
+ return { acknowledged: true, deletedCount: 1 };
105
+ }
106
+
107
+ /**
108
+ * Find integration by name
109
+ *
110
+ * @param {string} name - Integration type name
111
+ * @returns {Promise<Object>} Integration object with string IDs
112
+ */
113
+ async findIntegrationByName(name) {
114
+ const integration = await this.prisma.integration.findFirst({
115
+ where: {
116
+ config: {
117
+ path: ['type'],
118
+ equals: name,
119
+ },
120
+ },
121
+ include: {
122
+ entities: true,
123
+ },
124
+ });
125
+
126
+ if (!integration) {
127
+ throw new Error(`Integration with name ${name} not found`);
128
+ }
129
+
130
+ const converted = this._convertIntegrationIds(integration);
131
+ return {
132
+ id: converted.id,
133
+ entitiesIds: converted.entities.map((e) => e.id),
134
+ userId: converted.userId,
135
+ config: converted.config,
136
+ version: converted.version,
137
+ status: converted.status,
138
+ messages: converted.messages,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Find integration by ID
144
+ *
145
+ * @param {string} id - Integration ID (string from application layer)
146
+ * @returns {Promise<Object>} Integration object with string IDs
147
+ */
148
+ async findIntegrationById(id) {
149
+ const intId = this._convertId(id);
150
+ const integration = await this.prisma.integration.findUnique({
151
+ where: { id: intId },
152
+ include: {
153
+ entities: true,
154
+ },
155
+ });
156
+
157
+ if (!integration) {
158
+ throw new Error(`Integration with id ${id} not found`);
159
+ }
160
+
161
+ const converted = this._convertIntegrationIds(integration);
162
+ return {
163
+ id: converted.id,
164
+ entitiesIds: converted.entities.map((e) => e.id),
165
+ userId: converted.userId,
166
+ config: converted.config,
167
+ version: converted.version,
168
+ status: converted.status,
169
+ messages: converted.messages,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Update integration status
175
+ *
176
+ * @param {string} integrationId - Integration ID (string from application layer)
177
+ * @param {string} status - New status
178
+ * @returns {Promise<boolean>} Success indicator
179
+ */
180
+ async updateIntegrationStatus(integrationId, status) {
181
+ const intId = this._convertId(integrationId);
182
+ await this.prisma.integration.update({
183
+ where: { id: intId },
184
+ data: { status },
185
+ });
186
+
187
+ return true; // Mongoose compatibility
188
+ }
189
+
190
+ /**
191
+ * Update integration messages
192
+ *
193
+ * @param {string} integrationId - Integration ID (string from application layer)
194
+ * @param {string} messageType - Type of message (errors, warnings, info, logs)
195
+ * @param {string} messageTitle - Message title
196
+ * @param {string} messageBody - Message body
197
+ * @param {Date} messageTimestamp - Message timestamp
198
+ * @returns {Promise<boolean>} Success indicator
199
+ */
200
+ async updateIntegrationMessages(
201
+ integrationId,
202
+ messageType,
203
+ messageTitle,
204
+ messageBody,
205
+ messageTimestamp
206
+ ) {
207
+ const intId = this._convertId(integrationId);
208
+
209
+ // Get current integration
210
+ const integration = await this.prisma.integration.findUnique({
211
+ where: { id: intId },
212
+ });
213
+
214
+ if (!integration) {
215
+ throw new Error(`Integration ${integrationId} not found`);
216
+ }
217
+
218
+ // Parse existing messages (JSON field)
219
+ const messages = integration.messages || {};
220
+ const messageArray = Array.isArray(messages[messageType])
221
+ ? messages[messageType]
222
+ : [];
223
+
224
+ // Add new message
225
+ messageArray.push({
226
+ title: messageTitle,
227
+ message: messageBody,
228
+ timestamp: messageTimestamp,
229
+ });
230
+
231
+ // Update messages
232
+ await this.prisma.integration.update({
233
+ where: { id: intId },
234
+ data: {
235
+ [messageType]: messageArray,
236
+ },
237
+ });
238
+
239
+ return true; // Mongoose compatibility
240
+ }
241
+
242
+ /**
243
+ * Create a new integration
244
+ *
245
+ * PostgreSQL-specific: Uses nested relations with connect syntax
246
+ *
247
+ * @param {Array<string>} entities - Array of entity IDs (strings from application layer)
248
+ * @param {string} userId - User ID (string from application layer)
249
+ * @param {Object} config - Integration configuration
250
+ * @returns {Promise<Object>} Created integration object with string IDs
251
+ */
252
+ async createIntegration(entities, userId, config) {
253
+ const data = {
254
+ config,
255
+ version: '0.0.0',
256
+ };
257
+
258
+ // PostgreSQL: use nested relations with ID conversion
259
+ if (userId) {
260
+ data.user = { connect: { id: this._convertId(userId) } };
261
+ }
262
+ if (entities && entities.length > 0) {
263
+ data.entities = {
264
+ connect: entities.map((id) => ({ id: this._convertId(id) })),
265
+ };
266
+ }
267
+
268
+ const integration = await this.prisma.integration.create({
269
+ data,
270
+ include: {
271
+ entities: true,
272
+ },
273
+ });
274
+
275
+ const converted = this._convertIntegrationIds(integration);
276
+ return {
277
+ id: converted.id,
278
+ entitiesIds: converted.entities.map((e) => e.id),
279
+ userId: converted.userId,
280
+ config: converted.config,
281
+ version: converted.version,
282
+ status: converted.status,
283
+ messages: converted.messages,
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Find integration by user ID (returns single integration)
289
+ *
290
+ * @param {string} userId - User ID (string from application layer)
291
+ * @returns {Promise<Object|null>} Integration object with string IDs or null
292
+ */
293
+ async findIntegrationByUserId(userId) {
294
+ const intUserId = this._convertId(userId);
295
+ const integration = await this.prisma.integration.findFirst({
296
+ where: { userId: intUserId },
297
+ include: {
298
+ entities: true,
299
+ },
300
+ });
301
+
302
+ if (!integration) {
303
+ return null;
304
+ }
305
+
306
+ const converted = this._convertIntegrationIds(integration);
307
+ return {
308
+ id: converted.id,
309
+ entitiesIds: converted.entities.map((e) => e.id),
310
+ userId: converted.userId,
311
+ config: converted.config,
312
+ version: converted.version,
313
+ status: converted.status,
314
+ messages: converted.messages,
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Update integration configuration
320
+ *
321
+ * @param {string} integrationId - Integration ID (string from application layer)
322
+ * @param {Object} config - Updated configuration object
323
+ * @returns {Promise<Object>} Updated integration object with string IDs
324
+ */
325
+ async updateIntegrationConfig(integrationId, config) {
326
+ if (config === null || config === undefined) {
327
+ throw new Error('Config parameter is required');
328
+ }
329
+
330
+ const intId = this._convertId(integrationId);
331
+ const integration = await this.prisma.integration.update({
332
+ where: { id: intId },
333
+ data: { config },
334
+ include: {
335
+ entities: true,
336
+ },
337
+ });
338
+
339
+ const converted = this._convertIntegrationIds(integration);
340
+ return {
341
+ id: converted.id,
342
+ entitiesIds: converted.entities.map((e) => e.id),
343
+ userId: converted.userId,
344
+ config: converted.config,
345
+ version: converted.version,
346
+ status: converted.status,
347
+ messages: converted.messages,
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Find all integrations whose entity set includes the given entity ID.
353
+ *
354
+ * @param {string|number} entityId - Entity ID (string from application layer)
355
+ * @returns {Promise<Array>} Array of integration objects with string IDs (possibly empty)
356
+ */
357
+ async findIntegrationsByEntityId(entityId) {
358
+ const intEntityId = this._convertId(entityId);
359
+ const integrations = await this.prisma.integration.findMany({
360
+ where: {
361
+ entities: {
362
+ some: { id: intEntityId },
363
+ },
364
+ },
365
+ include: {
366
+ entities: true,
367
+ },
368
+ });
369
+
370
+ return integrations.map((integration) => {
371
+ const converted = this._convertIntegrationIds(integration);
372
+ return {
373
+ id: converted.id,
374
+ entitiesIds: converted.entities.map((e) => e.id),
375
+ userId: converted.userId,
376
+ config: converted.config,
377
+ version: converted.version,
378
+ status: converted.status,
379
+ messages: converted.messages,
380
+ };
381
+ });
382
+ }
383
+ }
384
+
385
+ module.exports = { IntegrationRepositoryPostgres };
@@ -0,0 +1,311 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ toObjectId,
4
+ fromObjectId,
5
+ findMany,
6
+ findOne,
7
+ insertOne,
8
+ updateOne,
9
+ deleteOne,
10
+ } = require('../../database/documentdb-utils');
11
+ const {
12
+ ProcessRepositoryInterface,
13
+ } = require('./process-repository-interface');
14
+ const {
15
+ DocumentDBEncryptionService,
16
+ } = require('../../database/documentdb-encryption-service');
17
+ const { validateOps } = require('./process-update-ops-shared');
18
+
19
+ class ProcessRepositoryDocumentDB extends ProcessRepositoryInterface {
20
+ constructor() {
21
+ super();
22
+ this.prisma = prisma;
23
+ this.encryptionService = new DocumentDBEncryptionService();
24
+ }
25
+
26
+ async create(processData) {
27
+ const now = new Date();
28
+ const plainDocument = {
29
+ userId: toObjectId(processData.userId),
30
+ integrationId: toObjectId(processData.integrationId),
31
+ name: processData.name,
32
+ type: processData.type,
33
+ state: processData.state || 'INITIALIZING',
34
+ context: processData.context || {},
35
+ results: processData.results || {},
36
+ childProcesses: (processData.childProcesses || [])
37
+ .map((id) => toObjectId(id))
38
+ .filter(Boolean),
39
+ parentProcessId: processData.parentProcessId
40
+ ? toObjectId(processData.parentProcessId)
41
+ : null,
42
+ createdAt: now,
43
+ updatedAt: now,
44
+ };
45
+
46
+ const encryptedDocument = await this.encryptionService.encryptFields(
47
+ 'Process',
48
+ plainDocument
49
+ );
50
+
51
+ const insertedId = await insertOne(
52
+ this.prisma,
53
+ 'Process',
54
+ encryptedDocument
55
+ );
56
+
57
+ const created = await findOne(this.prisma, 'Process', {
58
+ _id: insertedId,
59
+ });
60
+ if (!created) {
61
+ console.error(
62
+ '[ProcessRepositoryDocumentDB] Process not found after insert',
63
+ {
64
+ insertedId: fromObjectId(insertedId),
65
+ processData: {
66
+ userId: processData.userId,
67
+ integrationId: processData.integrationId,
68
+ name: processData.name,
69
+ type: processData.type,
70
+ },
71
+ }
72
+ );
73
+ throw new Error(
74
+ 'Failed to create process: Document not found after insert. ' +
75
+ 'This indicates a database consistency issue.'
76
+ );
77
+ }
78
+ const decryptedProcess = await this.encryptionService.decryptFields(
79
+ 'Process',
80
+ created
81
+ );
82
+ return this._mapProcess(decryptedProcess);
83
+ }
84
+
85
+ async findById(processId) {
86
+ const objectId = toObjectId(processId);
87
+ if (!objectId) return null;
88
+ const doc = await findOne(this.prisma, 'Process', { _id: objectId });
89
+ if (!doc) return null;
90
+
91
+ const decryptedProcess = await this.encryptionService.decryptFields(
92
+ 'Process',
93
+ doc
94
+ );
95
+ return this._mapProcess(decryptedProcess);
96
+ }
97
+
98
+ async update(processId, updates) {
99
+ const objectId = toObjectId(processId);
100
+ if (!objectId) return null;
101
+
102
+ const existing = await findOne(this.prisma, 'Process', {
103
+ _id: objectId,
104
+ });
105
+ if (!existing) return null;
106
+
107
+ const updatePayload = {};
108
+ if (updates.state !== undefined) updatePayload.state = updates.state;
109
+ if (updates.context !== undefined)
110
+ updatePayload.context = updates.context;
111
+ if (updates.results !== undefined)
112
+ updatePayload.results = updates.results;
113
+ if (updates.childProcesses !== undefined) {
114
+ updatePayload.childProcesses = (updates.childProcesses || [])
115
+ .map((id) => toObjectId(id))
116
+ .filter(Boolean);
117
+ }
118
+ if (updates.parentProcessId !== undefined) {
119
+ updatePayload.parentProcessId = updates.parentProcessId
120
+ ? toObjectId(updates.parentProcessId)
121
+ : null;
122
+ }
123
+ updatePayload.updatedAt = new Date();
124
+
125
+ const encryptedUpdate = await this.encryptionService.encryptFields(
126
+ 'Process',
127
+ updatePayload
128
+ );
129
+
130
+ await updateOne(
131
+ this.prisma,
132
+ 'Process',
133
+ { _id: objectId },
134
+ { $set: encryptedUpdate }
135
+ );
136
+
137
+ const updated = await findOne(this.prisma, 'Process', {
138
+ _id: objectId,
139
+ });
140
+ if (!updated) {
141
+ console.error(
142
+ '[ProcessRepositoryDocumentDB] Process not found after update',
143
+ {
144
+ processId: fromObjectId(objectId),
145
+ }
146
+ );
147
+ throw new Error(
148
+ 'Failed to update process: Document not found after update. ' +
149
+ 'This indicates a database consistency issue.'
150
+ );
151
+ }
152
+ const decryptedProcess = await this.encryptionService.decryptFields(
153
+ 'Process',
154
+ updated
155
+ );
156
+ return this._mapProcess(decryptedProcess);
157
+ }
158
+
159
+ /**
160
+ * Atomic process update — race-safe counterpart to `update()`.
161
+ *
162
+ * Uses DocumentDB's native $inc / $set / $push operators (Mongo-wire
163
+ * compatible) via findAndModify so increments, sets, and pushes land
164
+ * in one server-side write. Contention on the same document
165
+ * serializes at the DB level.
166
+ *
167
+ * DocumentDB compatibility notes:
168
+ * - $inc: supported since v3.6.
169
+ * - $set with dot-path: supported.
170
+ * - $push with $each + negative $slice: supported since v4.0.
171
+ * Clusters still on v3.6 must upgrade before using pushSlice.
172
+ *
173
+ * Process documents have no encrypted fields today; if that changes,
174
+ * the set-by-path payload here MUST route through
175
+ * `encryptionService.encryptFields` for any affected paths.
176
+ *
177
+ * @param {string} processId
178
+ * @param {import('./process-repository-interface').ProcessUpdateOps} ops
179
+ * @returns {Promise<Object|null>}
180
+ */
181
+ async applyProcessUpdate(processId, ops) {
182
+ const normalized = validateOps(ops);
183
+ const objectId = toObjectId(processId);
184
+
185
+ const update = {};
186
+ const $set = {};
187
+
188
+ if (Object.keys(normalized.increment).length > 0) {
189
+ update.$inc = { ...normalized.increment };
190
+ }
191
+ for (const [path, value] of Object.entries(normalized.set)) {
192
+ $set[path] = value;
193
+ }
194
+ if (normalized.newState !== null) {
195
+ $set.state = normalized.newState;
196
+ }
197
+ $set.updatedAt = new Date();
198
+ update.$set = $set;
199
+
200
+ if (Object.keys(normalized.pushSlice).length > 0) {
201
+ update.$push = {};
202
+ for (const [path, spec] of Object.entries(normalized.pushSlice)) {
203
+ update.$push[path] = {
204
+ $each: spec.values,
205
+ $slice: -spec.keepLast,
206
+ };
207
+ }
208
+ }
209
+
210
+ const result = await this.prisma.$runCommandRaw({
211
+ findAndModify: 'Process',
212
+ query: { _id: objectId },
213
+ update,
214
+ new: true,
215
+ });
216
+
217
+ const doc = result && result.value;
218
+ if (!doc) return null;
219
+ const decrypted = await this.encryptionService.decryptFields(
220
+ 'Process',
221
+ doc
222
+ );
223
+ return this._mapProcess(decrypted);
224
+ }
225
+
226
+ async findByIntegrationAndType(integrationId, type) {
227
+ const integrationObjectId = toObjectId(integrationId);
228
+ const filter = {
229
+ integrationId: integrationObjectId,
230
+ type,
231
+ };
232
+ const docs = await findMany(this.prisma, 'Process', filter, {
233
+ sort: { createdAt: -1 },
234
+ });
235
+
236
+ const decryptedDocs = await Promise.all(
237
+ docs.map((doc) =>
238
+ this.encryptionService.decryptFields('Process', doc)
239
+ )
240
+ );
241
+
242
+ return decryptedDocs.map((doc) => this._mapProcess(doc));
243
+ }
244
+
245
+ async findActiveProcesses(
246
+ integrationId,
247
+ excludeStates = ['COMPLETED', 'ERROR']
248
+ ) {
249
+ const integrationObjectId = toObjectId(integrationId);
250
+ const filter = {
251
+ integrationId: integrationObjectId,
252
+ state: { $nin: excludeStates },
253
+ };
254
+ const docs = await findMany(this.prisma, 'Process', filter, {
255
+ sort: { createdAt: -1 },
256
+ });
257
+
258
+ const decryptedDocs = await Promise.all(
259
+ docs.map((doc) =>
260
+ this.encryptionService.decryptFields('Process', doc)
261
+ )
262
+ );
263
+
264
+ return decryptedDocs.map((doc) => this._mapProcess(doc));
265
+ }
266
+
267
+ async findByName(name) {
268
+ const doc = await findOne(
269
+ this.prisma,
270
+ 'Process',
271
+ { name },
272
+ { sort: { createdAt: -1 } }
273
+ );
274
+ if (!doc) return null;
275
+
276
+ const decryptedProcess = await this.encryptionService.decryptFields(
277
+ 'Process',
278
+ doc
279
+ );
280
+ return this._mapProcess(decryptedProcess);
281
+ }
282
+
283
+ async deleteById(processId) {
284
+ const objectId = toObjectId(processId);
285
+ if (!objectId) return;
286
+ await deleteOne(this.prisma, 'Process', { _id: objectId });
287
+ }
288
+
289
+ _mapProcess(doc) {
290
+ return {
291
+ id: fromObjectId(doc?._id),
292
+ userId: fromObjectId(doc?.userId),
293
+ integrationId: fromObjectId(doc?.integrationId),
294
+ name: doc?.name ?? null,
295
+ type: doc?.type ?? null,
296
+ state: doc?.state ?? null,
297
+ context: doc?.context ?? {},
298
+ results: doc?.results ?? {},
299
+ childProcesses: (doc?.childProcesses || []).map((id) =>
300
+ fromObjectId(id)
301
+ ),
302
+ parentProcessId: doc?.parentProcessId
303
+ ? fromObjectId(doc.parentProcessId)
304
+ : null,
305
+ createdAt: doc?.createdAt ? new Date(doc.createdAt) : null,
306
+ updatedAt: doc?.updatedAt ? new Date(doc.updatedAt) : null,
307
+ };
308
+ }
309
+ }
310
+
311
+ module.exports = { ProcessRepositoryDocumentDB };