@friggframework/core 2.0.0-next.5 → 2.0.0-next.51

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 (267) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +179 -0
  7. package/application/commands/user-commands.js +213 -0
  8. package/application/index.js +69 -0
  9. package/core/CLAUDE.md +690 -0
  10. package/core/Worker.js +8 -21
  11. package/core/create-handler.js +2 -7
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +307 -0
  15. package/credential/repositories/credential-repository-postgres.js +313 -0
  16. package/credential/repositories/credential-repository.js +302 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  20. package/database/adapters/lambda-invoker.js +97 -0
  21. package/database/config.js +154 -0
  22. package/database/encryption/README.md +684 -0
  23. package/database/encryption/encryption-schema-registry.js +141 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/logger.js +79 -0
  26. package/database/encryption/prisma-encryption-extension.js +222 -0
  27. package/database/index.js +25 -12
  28. package/database/models/WebsocketConnection.js +16 -10
  29. package/database/models/readme.md +1 -0
  30. package/database/prisma.js +222 -0
  31. package/database/repositories/health-check-repository-factory.js +43 -0
  32. package/database/repositories/health-check-repository-interface.js +87 -0
  33. package/database/repositories/health-check-repository-mongodb.js +91 -0
  34. package/database/repositories/health-check-repository-postgres.js +82 -0
  35. package/database/repositories/health-check-repository.js +108 -0
  36. package/database/repositories/migration-status-repository-s3.js +137 -0
  37. package/database/use-cases/check-database-health-use-case.js +29 -0
  38. package/database/use-cases/check-database-state-use-case.js +81 -0
  39. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  40. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  41. package/database/use-cases/get-migration-status-use-case.js +93 -0
  42. package/database/use-cases/run-database-migration-use-case.js +137 -0
  43. package/database/use-cases/test-encryption-use-case.js +253 -0
  44. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  45. package/database/utils/mongodb-collection-utils.js +91 -0
  46. package/database/utils/mongodb-schema-init.js +106 -0
  47. package/database/utils/prisma-runner.js +400 -0
  48. package/database/utils/prisma-schema-parser.js +182 -0
  49. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  50. package/encrypt/Cryptor.js +34 -168
  51. package/encrypt/index.js +1 -2
  52. package/encrypt/test-encrypt.js +0 -2
  53. package/generated/prisma-mongodb/client.d.ts +1 -0
  54. package/generated/prisma-mongodb/client.js +4 -0
  55. package/generated/prisma-mongodb/default.d.ts +1 -0
  56. package/generated/prisma-mongodb/default.js +4 -0
  57. package/generated/prisma-mongodb/edge.d.ts +1 -0
  58. package/generated/prisma-mongodb/edge.js +334 -0
  59. package/generated/prisma-mongodb/index-browser.js +316 -0
  60. package/generated/prisma-mongodb/index.d.ts +22898 -0
  61. package/generated/prisma-mongodb/index.js +359 -0
  62. package/generated/prisma-mongodb/package.json +183 -0
  63. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  64. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  65. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  66. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  67. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  68. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  69. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  70. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  71. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  72. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  73. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  74. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  75. package/generated/prisma-mongodb/schema.prisma +362 -0
  76. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  77. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  78. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  79. package/generated/prisma-mongodb/wasm.js +341 -0
  80. package/generated/prisma-postgresql/client.d.ts +1 -0
  81. package/generated/prisma-postgresql/client.js +4 -0
  82. package/generated/prisma-postgresql/default.d.ts +1 -0
  83. package/generated/prisma-postgresql/default.js +4 -0
  84. package/generated/prisma-postgresql/edge.d.ts +1 -0
  85. package/generated/prisma-postgresql/edge.js +356 -0
  86. package/generated/prisma-postgresql/index-browser.js +338 -0
  87. package/generated/prisma-postgresql/index.d.ts +25072 -0
  88. package/generated/prisma-postgresql/index.js +381 -0
  89. package/generated/prisma-postgresql/package.json +183 -0
  90. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  91. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  92. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  93. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  94. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  95. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  96. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  97. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  98. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  99. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  100. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  101. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  102. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  103. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  104. package/generated/prisma-postgresql/schema.prisma +345 -0
  105. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  106. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  107. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  108. package/generated/prisma-postgresql/wasm.js +363 -0
  109. package/handlers/WEBHOOKS.md +653 -0
  110. package/handlers/app-definition-loader.js +38 -0
  111. package/handlers/app-handler-helpers.js +56 -0
  112. package/handlers/backend-utils.js +180 -0
  113. package/handlers/database-migration-handler.js +227 -0
  114. package/handlers/integration-event-dispatcher.js +54 -0
  115. package/handlers/routers/HEALTHCHECK.md +342 -0
  116. package/handlers/routers/auth.js +15 -0
  117. package/handlers/routers/db-migration.handler.js +29 -0
  118. package/handlers/routers/db-migration.js +256 -0
  119. package/handlers/routers/health.js +519 -0
  120. package/handlers/routers/integration-defined-routers.js +45 -0
  121. package/handlers/routers/integration-webhook-routers.js +67 -0
  122. package/handlers/routers/user.js +63 -0
  123. package/handlers/routers/websocket.js +57 -0
  124. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  125. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  126. package/handlers/workers/db-migration.js +352 -0
  127. package/handlers/workers/integration-defined-workers.js +27 -0
  128. package/index.js +77 -22
  129. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  130. package/integrations/index.js +12 -10
  131. package/integrations/integration-base.js +296 -54
  132. package/integrations/integration-router.js +381 -182
  133. package/integrations/options.js +1 -1
  134. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  135. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  136. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  137. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  138. package/integrations/repositories/integration-mapping-repository.js +156 -0
  139. package/integrations/repositories/integration-repository-factory.js +44 -0
  140. package/integrations/repositories/integration-repository-interface.js +127 -0
  141. package/integrations/repositories/integration-repository-mongo.js +303 -0
  142. package/integrations/repositories/integration-repository-postgres.js +352 -0
  143. package/integrations/repositories/process-repository-factory.js +46 -0
  144. package/integrations/repositories/process-repository-interface.js +90 -0
  145. package/integrations/repositories/process-repository-mongo.js +190 -0
  146. package/integrations/repositories/process-repository-postgres.js +217 -0
  147. package/integrations/tests/doubles/dummy-integration-class.js +83 -0
  148. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  149. package/integrations/use-cases/create-integration.js +83 -0
  150. package/integrations/use-cases/create-process.js +128 -0
  151. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  152. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  153. package/integrations/use-cases/get-integration-for-user.js +78 -0
  154. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  155. package/integrations/use-cases/get-integration-instance.js +83 -0
  156. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  157. package/integrations/use-cases/get-possible-integrations.js +27 -0
  158. package/integrations/use-cases/get-process.js +87 -0
  159. package/integrations/use-cases/index.js +19 -0
  160. package/integrations/use-cases/load-integration-context.js +71 -0
  161. package/integrations/use-cases/update-integration-messages.js +44 -0
  162. package/integrations/use-cases/update-integration-status.js +32 -0
  163. package/integrations/use-cases/update-integration.js +93 -0
  164. package/integrations/use-cases/update-process-metrics.js +201 -0
  165. package/integrations/use-cases/update-process-state.js +119 -0
  166. package/integrations/utils/map-integration-dto.js +37 -0
  167. package/jest-global-setup-noop.js +3 -0
  168. package/jest-global-teardown-noop.js +3 -0
  169. package/logs/logger.js +0 -4
  170. package/{module-plugin → modules}/entity.js +1 -1
  171. package/{module-plugin → modules}/index.js +0 -8
  172. package/modules/module-factory.js +56 -0
  173. package/modules/module.js +221 -0
  174. package/modules/repositories/module-repository-factory.js +33 -0
  175. package/modules/repositories/module-repository-interface.js +129 -0
  176. package/modules/repositories/module-repository-mongo.js +377 -0
  177. package/modules/repositories/module-repository-postgres.js +426 -0
  178. package/modules/repositories/module-repository.js +316 -0
  179. package/{module-plugin → modules}/requester/requester.js +1 -0
  180. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  181. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  182. package/modules/tests/doubles/test-module-factory.js +16 -0
  183. package/modules/tests/doubles/test-module-repository.js +39 -0
  184. package/modules/use-cases/get-entities-for-user.js +32 -0
  185. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  186. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  187. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  188. package/modules/use-cases/get-module.js +55 -0
  189. package/modules/use-cases/process-authorization-callback.js +122 -0
  190. package/modules/use-cases/refresh-entity-options.js +59 -0
  191. package/modules/use-cases/test-module-auth.js +55 -0
  192. package/modules/utils/map-module-dto.js +18 -0
  193. package/package.json +82 -50
  194. package/prisma-mongodb/schema.prisma +362 -0
  195. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  196. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  197. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  198. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  199. package/prisma-postgresql/schema.prisma +345 -0
  200. package/queues/queuer-util.js +28 -15
  201. package/syncs/manager.js +468 -443
  202. package/syncs/repositories/sync-repository-factory.js +38 -0
  203. package/syncs/repositories/sync-repository-interface.js +109 -0
  204. package/syncs/repositories/sync-repository-mongo.js +239 -0
  205. package/syncs/repositories/sync-repository-postgres.js +319 -0
  206. package/syncs/sync.js +0 -1
  207. package/token/repositories/token-repository-factory.js +33 -0
  208. package/token/repositories/token-repository-interface.js +131 -0
  209. package/token/repositories/token-repository-mongo.js +212 -0
  210. package/token/repositories/token-repository-postgres.js +257 -0
  211. package/token/repositories/token-repository.js +219 -0
  212. package/types/core/index.d.ts +2 -2
  213. package/types/integrations/index.d.ts +2 -6
  214. package/types/module-plugin/index.d.ts +5 -59
  215. package/types/syncs/index.d.ts +0 -2
  216. package/user/repositories/user-repository-factory.js +46 -0
  217. package/user/repositories/user-repository-interface.js +198 -0
  218. package/user/repositories/user-repository-mongo.js +291 -0
  219. package/user/repositories/user-repository-postgres.js +350 -0
  220. package/user/tests/doubles/test-user-repository.js +72 -0
  221. package/user/use-cases/authenticate-user.js +127 -0
  222. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  223. package/user/use-cases/create-individual-user.js +61 -0
  224. package/user/use-cases/create-organization-user.js +47 -0
  225. package/user/use-cases/create-token-for-user-id.js +30 -0
  226. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  227. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  228. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  229. package/user/use-cases/login-user.js +122 -0
  230. package/user/user.js +93 -0
  231. package/utils/backend-path.js +38 -0
  232. package/utils/index.js +6 -0
  233. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  234. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  235. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  236. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  237. package/websocket/repositories/websocket-connection-repository.js +161 -0
  238. package/database/models/State.js +0 -9
  239. package/database/models/Token.js +0 -70
  240. package/database/mongo.js +0 -45
  241. package/encrypt/Cryptor.test.js +0 -32
  242. package/encrypt/encrypt.js +0 -132
  243. package/encrypt/encrypt.test.js +0 -1069
  244. package/errors/base-error.test.js +0 -32
  245. package/errors/fetch-error.test.js +0 -79
  246. package/errors/halt-error.test.js +0 -11
  247. package/errors/validation-errors.test.js +0 -120
  248. package/integrations/create-frigg-backend.js +0 -31
  249. package/integrations/integration-factory.js +0 -251
  250. package/integrations/integration-mapping.js +0 -43
  251. package/integrations/integration-model.js +0 -46
  252. package/integrations/integration-user.js +0 -144
  253. package/integrations/test/integration-base.test.js +0 -144
  254. package/lambda/TimeoutCatcher.test.js +0 -68
  255. package/logs/logger.test.js +0 -76
  256. package/module-plugin/auther.js +0 -393
  257. package/module-plugin/credential.js +0 -22
  258. package/module-plugin/entity-manager.js +0 -70
  259. package/module-plugin/manager.js +0 -169
  260. package/module-plugin/module-factory.js +0 -61
  261. package/module-plugin/requester/requester.test.js +0 -28
  262. package/module-plugin/test/auther.test.js +0 -97
  263. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  264. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  265. /package/{module-plugin → modules}/requester/basic.js +0 -0
  266. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  267. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,352 @@
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
+ module.exports = { IntegrationRepositoryPostgres };
@@ -0,0 +1,46 @@
1
+ const { ProcessRepositoryMongo } = require('./process-repository-mongo');
2
+ const { ProcessRepositoryPostgres } = require('./process-repository-postgres');
3
+ const config = require('../../database/config');
4
+
5
+ /**
6
+ * Process Repository Factory
7
+ * Creates the appropriate repository adapter based on database type
8
+ *
9
+ * This implements the Factory pattern for Hexagonal Architecture:
10
+ * - Reads database type from app definition (backend/index.js)
11
+ * - Returns correct adapter (MongoDB or PostgreSQL)
12
+ * - Provides clear error for unsupported databases
13
+ *
14
+ * Usage:
15
+ * ```javascript
16
+ * const repository = createProcessRepository();
17
+ * await repository.create({ userId, integrationId, name, type, state });
18
+ * ```
19
+ *
20
+ * @returns {ProcessRepositoryInterface} Configured repository adapter
21
+ * @throws {Error} If database type is not supported
22
+ */
23
+ function createProcessRepository() {
24
+ const dbType = config.DB_TYPE;
25
+
26
+ switch (dbType) {
27
+ case 'mongodb':
28
+ return new ProcessRepositoryMongo();
29
+
30
+ case 'postgresql':
31
+ return new ProcessRepositoryPostgres();
32
+
33
+ default:
34
+ throw new Error(
35
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
36
+ );
37
+ }
38
+ }
39
+
40
+ module.exports = {
41
+ createProcessRepository,
42
+ // Export adapters for direct testing
43
+ ProcessRepositoryMongo,
44
+ ProcessRepositoryPostgres,
45
+ };
46
+
@@ -0,0 +1,90 @@
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
+ * Find processes by integration and type
52
+ * @param {string} integrationId - Integration ID
53
+ * @param {string} type - Process type
54
+ * @returns {Promise<Array>} Array of process records
55
+ */
56
+ async findByIntegrationAndType(integrationId, type) {
57
+ throw new Error('Method findByIntegrationAndType() must be implemented');
58
+ }
59
+
60
+ /**
61
+ * Find active processes (not in excluded states)
62
+ * @param {string} integrationId - Integration ID
63
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
64
+ * @returns {Promise<Array>} Array of active process records
65
+ */
66
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
67
+ throw new Error('Method findActiveProcesses() must be implemented');
68
+ }
69
+
70
+ /**
71
+ * Find a process by name (most recent)
72
+ * @param {string} name - Process name
73
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
74
+ */
75
+ async findByName(name) {
76
+ throw new Error('Method findByName() must be implemented');
77
+ }
78
+
79
+ /**
80
+ * Delete a process by ID
81
+ * @param {string} processId - Process ID to delete
82
+ * @returns {Promise<void>}
83
+ */
84
+ async deleteById(processId) {
85
+ throw new Error('Method deleteById() must be implemented');
86
+ }
87
+ }
88
+
89
+ module.exports = { ProcessRepositoryInterface };
90
+
@@ -0,0 +1,190 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { ProcessRepositoryInterface } = require('./process-repository-interface');
3
+
4
+ /**
5
+ * MongoDB Process Repository Adapter
6
+ * Handles process persistence using Prisma with MongoDB
7
+ *
8
+ * MongoDB-specific characteristics:
9
+ * - Uses scalar fields for relations (userId, integrationId)
10
+ * - IDs are strings with @db.ObjectId
11
+ * - JSON fields for flexible context and results storage
12
+ * - Array field for childProcesses references
13
+ *
14
+ * Design Philosophy:
15
+ * - Generic Process model supports any type of long-running operation
16
+ * - Context and results stored as JSON for maximum flexibility
17
+ * - Integration-specific logic lives in use cases and services
18
+ */
19
+ class ProcessRepositoryMongo extends ProcessRepositoryInterface {
20
+ constructor() {
21
+ super();
22
+ this.prisma = prisma;
23
+ }
24
+
25
+ /**
26
+ * Create a new process record
27
+ * @param {Object} processData - Process data to create
28
+ * @returns {Promise<Object>} Created process record
29
+ */
30
+ async create(processData) {
31
+ const process = await this.prisma.process.create({
32
+ data: {
33
+ userId: processData.userId,
34
+ integrationId: processData.integrationId,
35
+ name: processData.name,
36
+ type: processData.type,
37
+ state: processData.state || 'INITIALIZING',
38
+ context: processData.context || {},
39
+ results: processData.results || {},
40
+ childProcesses: processData.childProcesses || [],
41
+ parentProcessId: processData.parentProcessId || null,
42
+ },
43
+ });
44
+
45
+ return this._toPlainObject(process);
46
+ }
47
+
48
+ /**
49
+ * Find a process by ID
50
+ * @param {string} processId - Process ID to find
51
+ * @returns {Promise<Object|null>} Process record or null if not found
52
+ */
53
+ async findById(processId) {
54
+ const process = await this.prisma.process.findUnique({
55
+ where: { id: processId },
56
+ });
57
+
58
+ return process ? this._toPlainObject(process) : null;
59
+ }
60
+
61
+ /**
62
+ * Update a process record
63
+ * @param {string} processId - Process ID to update
64
+ * @param {Object} updates - Fields to update
65
+ * @returns {Promise<Object>} Updated process record
66
+ */
67
+ async update(processId, updates) {
68
+ // Prepare update data, excluding undefined values
69
+ const updateData = {};
70
+
71
+ if (updates.state !== undefined) {
72
+ updateData.state = updates.state;
73
+ }
74
+ if (updates.context !== undefined) {
75
+ updateData.context = updates.context;
76
+ }
77
+ if (updates.results !== undefined) {
78
+ updateData.results = updates.results;
79
+ }
80
+ if (updates.childProcesses !== undefined) {
81
+ updateData.childProcesses = updates.childProcesses;
82
+ }
83
+ if (updates.parentProcessId !== undefined) {
84
+ updateData.parentProcessId = updates.parentProcessId;
85
+ }
86
+
87
+ const process = await this.prisma.process.update({
88
+ where: { id: processId },
89
+ data: updateData,
90
+ });
91
+
92
+ return this._toPlainObject(process);
93
+ }
94
+
95
+ /**
96
+ * Find processes by integration and type
97
+ * @param {string} integrationId - Integration ID
98
+ * @param {string} type - Process type
99
+ * @returns {Promise<Array>} Array of process records
100
+ */
101
+ async findByIntegrationAndType(integrationId, type) {
102
+ const processes = await this.prisma.process.findMany({
103
+ where: {
104
+ integrationId,
105
+ type,
106
+ },
107
+ orderBy: {
108
+ createdAt: 'desc',
109
+ },
110
+ });
111
+
112
+ return processes.map((p) => this._toPlainObject(p));
113
+ }
114
+
115
+ /**
116
+ * Find active processes (not in excluded states)
117
+ * @param {string} integrationId - Integration ID
118
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
119
+ * @returns {Promise<Array>} Array of active process records
120
+ */
121
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
122
+ const processes = await this.prisma.process.findMany({
123
+ where: {
124
+ integrationId,
125
+ state: {
126
+ notIn: excludeStates,
127
+ },
128
+ },
129
+ orderBy: {
130
+ createdAt: 'desc',
131
+ },
132
+ });
133
+
134
+ return processes.map((p) => this._toPlainObject(p));
135
+ }
136
+
137
+ /**
138
+ * Find a process by name (most recent)
139
+ * @param {string} name - Process name
140
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
141
+ */
142
+ async findByName(name) {
143
+ const process = await this.prisma.process.findFirst({
144
+ where: { name },
145
+ orderBy: {
146
+ createdAt: 'desc',
147
+ },
148
+ });
149
+
150
+ return process ? this._toPlainObject(process) : null;
151
+ }
152
+
153
+ /**
154
+ * Delete a process by ID
155
+ * @param {string} processId - Process ID to delete
156
+ * @returns {Promise<void>}
157
+ */
158
+ async deleteById(processId) {
159
+ await this.prisma.process.delete({
160
+ where: { id: processId },
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Convert Prisma model to plain JavaScript object
166
+ * Ensures consistent API across repository implementations
167
+ * @private
168
+ * @param {Object} process - Prisma process model
169
+ * @returns {Object} Plain process object
170
+ */
171
+ _toPlainObject(process) {
172
+ return {
173
+ id: process.id,
174
+ userId: process.userId,
175
+ integrationId: process.integrationId,
176
+ name: process.name,
177
+ type: process.type,
178
+ state: process.state,
179
+ context: process.context,
180
+ results: process.results,
181
+ childProcesses: process.childProcesses,
182
+ parentProcessId: process.parentProcessId,
183
+ createdAt: process.createdAt,
184
+ updatedAt: process.updatedAt,
185
+ };
186
+ }
187
+ }
188
+
189
+ module.exports = { ProcessRepositoryMongo };
190
+