@friggframework/core 2.0.0-next.6 → 2.0.0-next.61

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 (286) hide show
  1. package/CLAUDE.md +694 -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/user-commands.js +283 -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 +14 -7
  12. package/credential/repositories/credential-repository-documentdb.js +304 -0
  13. package/credential/repositories/credential-repository-factory.js +54 -0
  14. package/credential/repositories/credential-repository-interface.js +98 -0
  15. package/credential/repositories/credential-repository-mongo.js +269 -0
  16. package/credential/repositories/credential-repository-postgres.js +291 -0
  17. package/credential/repositories/credential-repository.js +302 -0
  18. package/credential/use-cases/get-credential-for-user.js +25 -0
  19. package/credential/use-cases/update-authentication-status.js +15 -0
  20. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  21. package/database/adapters/lambda-invoker.js +97 -0
  22. package/database/config.js +154 -0
  23. package/database/documentdb-encryption-service.js +330 -0
  24. package/database/documentdb-utils.js +136 -0
  25. package/database/encryption/README.md +839 -0
  26. package/database/encryption/documentdb-encryption-service.md +3575 -0
  27. package/database/encryption/encryption-schema-registry.js +268 -0
  28. package/database/encryption/field-encryption-service.js +226 -0
  29. package/database/encryption/logger.js +79 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/index.js +61 -21
  32. package/database/models/WebsocketConnection.js +16 -10
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +134 -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/health-check-repository.js +108 -0
  41. package/database/repositories/migration-status-repository-s3.js +137 -0
  42. package/database/use-cases/check-database-health-use-case.js +29 -0
  43. package/database/use-cases/check-database-state-use-case.js +81 -0
  44. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  45. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  46. package/database/use-cases/get-migration-status-use-case.js +93 -0
  47. package/database/use-cases/run-database-migration-use-case.js +139 -0
  48. package/database/use-cases/test-encryption-use-case.js +253 -0
  49. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  50. package/database/utils/mongodb-collection-utils.js +91 -0
  51. package/database/utils/mongodb-schema-init.js +106 -0
  52. package/database/utils/prisma-runner.js +477 -0
  53. package/database/utils/prisma-schema-parser.js +182 -0
  54. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  55. package/encrypt/Cryptor.js +34 -168
  56. package/encrypt/index.js +1 -2
  57. package/encrypt/test-encrypt.js +0 -2
  58. package/errors/client-safe-error.js +26 -0
  59. package/errors/fetch-error.js +2 -1
  60. package/errors/index.js +2 -0
  61. package/generated/prisma-mongodb/client.d.ts +1 -0
  62. package/generated/prisma-mongodb/client.js +4 -0
  63. package/generated/prisma-mongodb/default.d.ts +1 -0
  64. package/generated/prisma-mongodb/default.js +4 -0
  65. package/generated/prisma-mongodb/edge.d.ts +1 -0
  66. package/generated/prisma-mongodb/edge.js +334 -0
  67. package/generated/prisma-mongodb/index-browser.js +316 -0
  68. package/generated/prisma-mongodb/index.d.ts +22903 -0
  69. package/generated/prisma-mongodb/index.js +359 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  72. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  73. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  74. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  75. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  76. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  77. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  78. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  79. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  80. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  81. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  82. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  83. package/generated/prisma-mongodb/schema.prisma +360 -0
  84. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  85. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  86. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  87. package/generated/prisma-mongodb/wasm.js +341 -0
  88. package/generated/prisma-postgresql/client.d.ts +1 -0
  89. package/generated/prisma-postgresql/client.js +4 -0
  90. package/generated/prisma-postgresql/default.d.ts +1 -0
  91. package/generated/prisma-postgresql/default.js +4 -0
  92. package/generated/prisma-postgresql/edge.d.ts +1 -0
  93. package/generated/prisma-postgresql/edge.js +356 -0
  94. package/generated/prisma-postgresql/index-browser.js +338 -0
  95. package/generated/prisma-postgresql/index.d.ts +25077 -0
  96. package/generated/prisma-postgresql/index.js +381 -0
  97. package/generated/prisma-postgresql/package.json +183 -0
  98. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  99. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  100. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  101. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  102. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  103. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  104. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  105. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  106. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  107. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  108. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  109. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  110. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  111. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  112. package/generated/prisma-postgresql/schema.prisma +343 -0
  113. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  114. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  115. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  116. package/generated/prisma-postgresql/wasm.js +363 -0
  117. package/handlers/WEBHOOKS.md +653 -0
  118. package/handlers/app-definition-loader.js +38 -0
  119. package/handlers/app-handler-helpers.js +56 -0
  120. package/handlers/backend-utils.js +186 -0
  121. package/handlers/database-migration-handler.js +227 -0
  122. package/handlers/integration-event-dispatcher.js +54 -0
  123. package/handlers/routers/HEALTHCHECK.md +342 -0
  124. package/handlers/routers/auth.js +15 -0
  125. package/handlers/routers/db-migration.handler.js +29 -0
  126. package/handlers/routers/db-migration.js +326 -0
  127. package/handlers/routers/health.js +516 -0
  128. package/handlers/routers/integration-defined-routers.js +45 -0
  129. package/handlers/routers/integration-webhook-routers.js +67 -0
  130. package/handlers/routers/user.js +63 -0
  131. package/handlers/routers/websocket.js +57 -0
  132. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  133. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  134. package/handlers/workers/db-migration.js +352 -0
  135. package/handlers/workers/integration-defined-workers.js +27 -0
  136. package/index.js +77 -22
  137. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  138. package/integrations/index.js +12 -10
  139. package/integrations/integration-base.js +326 -55
  140. package/integrations/integration-router.js +374 -179
  141. package/integrations/options.js +1 -1
  142. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  143. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  144. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  145. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  146. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  147. package/integrations/repositories/integration-mapping-repository.js +156 -0
  148. package/integrations/repositories/integration-repository-documentdb.js +210 -0
  149. package/integrations/repositories/integration-repository-factory.js +51 -0
  150. package/integrations/repositories/integration-repository-interface.js +127 -0
  151. package/integrations/repositories/integration-repository-mongo.js +303 -0
  152. package/integrations/repositories/integration-repository-postgres.js +352 -0
  153. package/integrations/repositories/process-repository-documentdb.js +243 -0
  154. package/integrations/repositories/process-repository-factory.js +53 -0
  155. package/integrations/repositories/process-repository-interface.js +90 -0
  156. package/integrations/repositories/process-repository-mongo.js +190 -0
  157. package/integrations/repositories/process-repository-postgres.js +217 -0
  158. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  159. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  160. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  161. package/integrations/use-cases/create-integration.js +83 -0
  162. package/integrations/use-cases/create-process.js +128 -0
  163. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  164. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  165. package/integrations/use-cases/get-integration-for-user.js +78 -0
  166. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  167. package/integrations/use-cases/get-integration-instance.js +83 -0
  168. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  169. package/integrations/use-cases/get-possible-integrations.js +27 -0
  170. package/integrations/use-cases/get-process.js +87 -0
  171. package/integrations/use-cases/index.js +19 -0
  172. package/integrations/use-cases/load-integration-context.js +71 -0
  173. package/integrations/use-cases/update-integration-messages.js +44 -0
  174. package/integrations/use-cases/update-integration-status.js +32 -0
  175. package/integrations/use-cases/update-integration.js +92 -0
  176. package/integrations/use-cases/update-process-metrics.js +201 -0
  177. package/integrations/use-cases/update-process-state.js +119 -0
  178. package/integrations/utils/map-integration-dto.js +37 -0
  179. package/jest-global-setup-noop.js +3 -0
  180. package/jest-global-teardown-noop.js +3 -0
  181. package/logs/logger.js +0 -4
  182. package/{module-plugin → modules}/entity.js +1 -1
  183. package/{module-plugin → modules}/index.js +0 -8
  184. package/modules/module-factory.js +56 -0
  185. package/modules/module.js +221 -0
  186. package/modules/repositories/module-repository-documentdb.js +307 -0
  187. package/modules/repositories/module-repository-factory.js +40 -0
  188. package/modules/repositories/module-repository-interface.js +129 -0
  189. package/modules/repositories/module-repository-mongo.js +377 -0
  190. package/modules/repositories/module-repository-postgres.js +426 -0
  191. package/modules/repositories/module-repository.js +316 -0
  192. package/modules/requester/api-key.js +52 -0
  193. package/{module-plugin → modules}/requester/requester.js +1 -0
  194. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  195. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  196. package/modules/tests/doubles/test-module-factory.js +16 -0
  197. package/modules/tests/doubles/test-module-repository.js +39 -0
  198. package/modules/use-cases/get-entities-for-user.js +32 -0
  199. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  200. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  201. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  202. package/modules/use-cases/get-module.js +74 -0
  203. package/modules/use-cases/process-authorization-callback.js +133 -0
  204. package/modules/use-cases/refresh-entity-options.js +72 -0
  205. package/modules/use-cases/test-module-auth.js +72 -0
  206. package/modules/utils/map-module-dto.js +18 -0
  207. package/package.json +82 -50
  208. package/prisma-mongodb/schema.prisma +360 -0
  209. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  210. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  211. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  212. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  213. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  214. package/prisma-postgresql/schema.prisma +343 -0
  215. package/queues/queuer-util.js +27 -22
  216. package/syncs/manager.js +468 -443
  217. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  218. package/syncs/repositories/sync-repository-factory.js +43 -0
  219. package/syncs/repositories/sync-repository-interface.js +109 -0
  220. package/syncs/repositories/sync-repository-mongo.js +239 -0
  221. package/syncs/repositories/sync-repository-postgres.js +319 -0
  222. package/syncs/sync.js +0 -1
  223. package/token/repositories/token-repository-documentdb.js +137 -0
  224. package/token/repositories/token-repository-factory.js +40 -0
  225. package/token/repositories/token-repository-interface.js +131 -0
  226. package/token/repositories/token-repository-mongo.js +219 -0
  227. package/token/repositories/token-repository-postgres.js +264 -0
  228. package/token/repositories/token-repository.js +219 -0
  229. package/types/core/index.d.ts +2 -2
  230. package/types/integrations/index.d.ts +2 -6
  231. package/types/module-plugin/index.d.ts +5 -59
  232. package/types/syncs/index.d.ts +0 -2
  233. package/user/repositories/user-repository-documentdb.js +441 -0
  234. package/user/repositories/user-repository-factory.js +52 -0
  235. package/user/repositories/user-repository-interface.js +201 -0
  236. package/user/repositories/user-repository-mongo.js +308 -0
  237. package/user/repositories/user-repository-postgres.js +360 -0
  238. package/user/tests/doubles/test-user-repository.js +72 -0
  239. package/user/use-cases/authenticate-user.js +127 -0
  240. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  241. package/user/use-cases/create-individual-user.js +61 -0
  242. package/user/use-cases/create-organization-user.js +47 -0
  243. package/user/use-cases/create-token-for-user-id.js +30 -0
  244. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  245. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  246. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  247. package/user/use-cases/login-user.js +122 -0
  248. package/user/user.js +125 -0
  249. package/utils/backend-path.js +38 -0
  250. package/utils/index.js +6 -0
  251. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  252. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  253. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  254. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  255. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  256. package/websocket/repositories/websocket-connection-repository.js +161 -0
  257. package/database/models/State.js +0 -9
  258. package/database/models/Token.js +0 -70
  259. package/database/mongo.js +0 -45
  260. package/encrypt/Cryptor.test.js +0 -32
  261. package/encrypt/encrypt.js +0 -132
  262. package/encrypt/encrypt.test.js +0 -1069
  263. package/errors/base-error.test.js +0 -32
  264. package/errors/fetch-error.test.js +0 -79
  265. package/errors/halt-error.test.js +0 -11
  266. package/errors/validation-errors.test.js +0 -120
  267. package/integrations/create-frigg-backend.js +0 -31
  268. package/integrations/integration-factory.js +0 -251
  269. package/integrations/integration-mapping.js +0 -43
  270. package/integrations/integration-model.js +0 -46
  271. package/integrations/integration-user.js +0 -144
  272. package/integrations/test/integration-base.test.js +0 -144
  273. package/lambda/TimeoutCatcher.test.js +0 -68
  274. package/logs/logger.test.js +0 -76
  275. package/module-plugin/auther.js +0 -393
  276. package/module-plugin/credential.js +0 -22
  277. package/module-plugin/entity-manager.js +0 -70
  278. package/module-plugin/manager.js +0 -169
  279. package/module-plugin/module-factory.js +0 -61
  280. package/module-plugin/requester/api-key.js +0 -36
  281. package/module-plugin/requester/requester.test.js +0 -28
  282. package/module-plugin/test/auther.test.js +0 -97
  283. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  284. /package/{module-plugin → modules}/requester/basic.js +0 -0
  285. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  286. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,302 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ CredentialRepositoryInterface,
4
+ } = require('./credential-repository-interface');
5
+
6
+ /**
7
+ * Prisma-based Credential Repository
8
+ * Handles OAuth credentials and API tokens persistence
9
+ *
10
+ * Works identically for both MongoDB and PostgreSQL:
11
+ * - MongoDB: String IDs with @db.ObjectId
12
+ * - PostgreSQL: Integer IDs with auto-increment
13
+ * - Both use same query patterns (no many-to-many differences)
14
+ *
15
+ * Migration from Mongoose:
16
+ * - Constructor injection of Prisma client
17
+ * - Dynamic schema (strict: false) → JSON field (data)
18
+ * - All OAuth tokens stored in data JSON field
19
+ * - Mongoose field names → Prisma field names (user → userId)
20
+ */
21
+ class CredentialRepository extends CredentialRepositoryInterface {
22
+ constructor(prismaClient = prisma) {
23
+ super();
24
+ this.prisma = prismaClient; // Allow injection for testing
25
+ }
26
+
27
+ /**
28
+ * Find credential by ID
29
+ * Replaces: Credential.findById(id)
30
+ *
31
+ * @param {string} id - Credential ID
32
+ * @returns {Promise<Object|null>} Credential object or null
33
+ */
34
+ async findCredentialById(id) {
35
+ const credential = await this.prisma.credential.findUnique({
36
+ where: { id },
37
+ });
38
+
39
+ if (!credential) {
40
+ return null;
41
+ }
42
+
43
+ // Extract data from JSON field
44
+ const data = credential.data || {};
45
+
46
+ return {
47
+ _id: credential.id,
48
+ id: credential.id,
49
+ user: credential.userId,
50
+ userId: credential.userId,
51
+ externalId: credential.externalId,
52
+ authIsValid: credential.authIsValid,
53
+ ...data, // Spread OAuth tokens from JSON field
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Update authentication status
59
+ * Replaces: Credential.updateOne({ _id: credentialId }, { $set: { authIsValid } })
60
+ *
61
+ * @param {string} credentialId - Credential ID
62
+ * @param {boolean} authIsValid - Authentication validity status
63
+ * @returns {Promise<Object>} Update result
64
+ */
65
+ async updateAuthenticationStatus(credentialId, authIsValid) {
66
+ await this.prisma.credential.update({
67
+ where: { id: credentialId },
68
+ data: { authIsValid },
69
+ });
70
+
71
+ return { acknowledged: true, modifiedCount: 1 };
72
+ }
73
+
74
+ /**
75
+ * Permanently remove a credential document
76
+ * Replaces: Credential.deleteOne({ _id: credentialId })
77
+ *
78
+ * @param {string} credentialId - Credential ID
79
+ * @returns {Promise<Object>} Deletion result
80
+ */
81
+ async deleteCredentialById(credentialId) {
82
+ try {
83
+ await this.prisma.credential.delete({
84
+ where: { id: credentialId },
85
+ });
86
+ return { acknowledged: true, deletedCount: 1 };
87
+ } catch (error) {
88
+ if (error.code === 'P2025') {
89
+ // Record not found
90
+ return { acknowledged: true, deletedCount: 0 };
91
+ }
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Create or update credential matching identifiers
98
+ * Replaces: Credential.findOneAndUpdate(query, update, { upsert: true })
99
+ *
100
+ * @param {{identifiers: Object, details: Object}} credentialDetails
101
+ * @returns {Promise<Object>} The persisted credential
102
+ */
103
+ async upsertCredential(credentialDetails) {
104
+ const { identifiers, details } = credentialDetails;
105
+ if (!identifiers)
106
+ throw new Error('identifiers required to upsert credential');
107
+
108
+ // Build where clause from identifiers
109
+ const where = this._convertIdentifiersToWhere(identifiers);
110
+
111
+ // Separate schema fields from dynamic OAuth data
112
+ const {
113
+ user,
114
+ userId,
115
+ externalId,
116
+ authIsValid,
117
+
118
+ ...oauthData
119
+ } = details;
120
+
121
+ // Find existing credential
122
+ const existing = await this.prisma.credential.findFirst({ where });
123
+
124
+ if (existing) {
125
+ // Update existing - merge OAuth data into existing data JSON
126
+ const mergedData = { ...(existing.data || {}), ...oauthData };
127
+
128
+ const updated = await this.prisma.credential.update({
129
+ where: { id: existing.id },
130
+ data: {
131
+ userId: userId || user || existing.userId,
132
+ externalId:
133
+ externalId !== undefined
134
+ ? externalId
135
+ : existing.externalId,
136
+ authIsValid:
137
+ authIsValid !== undefined
138
+ ? authIsValid
139
+ : existing.authIsValid,
140
+ data: mergedData,
141
+ },
142
+ });
143
+
144
+ return {
145
+ id: updated.id,
146
+ externalId: updated.externalId,
147
+ userId: updated.userId,
148
+ authIsValid: updated.authIsValid,
149
+ ...(updated.data || {}),
150
+ };
151
+ }
152
+
153
+ // Create new credential
154
+ const created = await this.prisma.credential.create({
155
+ data: {
156
+ userId: userId || user,
157
+ externalId,
158
+ authIsValid: authIsValid,
159
+
160
+ data: oauthData,
161
+ },
162
+ });
163
+
164
+ return {
165
+ id: created.id,
166
+ externalId: created.externalId,
167
+ userId: created.userId,
168
+ authIsValid: created.authIsValid,
169
+ ...(created.data || {}),
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Find a credential by filter criteria
175
+ * Replaces: Credential.findOne(query)
176
+ *
177
+ * @param {Object} filter
178
+ * @param {string} [filter.userId] - User ID
179
+ * @param {string} [filter.externalId] - External ID
180
+ * @param {string} [filter.credentialId] - Credential ID
181
+ * @returns {Promise<Object|null>} Credential object or null if not found
182
+ */
183
+ async findCredential(filter) {
184
+ const where = this._convertFilterToWhere(filter);
185
+
186
+ const credential = await this.prisma.credential.findFirst({
187
+ where,
188
+ });
189
+
190
+ if (!credential) {
191
+ return null;
192
+ }
193
+
194
+ const data = credential.data || {};
195
+
196
+ return {
197
+ id: credential.id,
198
+ userId: credential.userId,
199
+ externalId: credential.externalId,
200
+ authIsValid: credential.authIsValid,
201
+ access_token: data.access_token,
202
+ refresh_token: data.refresh_token,
203
+ domain: data.domain,
204
+ ...data,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Update a credential by ID
210
+ * Replaces: Credential.findByIdAndUpdate(credentialId, { $set: updates })
211
+ *
212
+ * @param {string} credentialId - Credential ID
213
+ * @param {Object} updates - Fields to update
214
+ * @returns {Promise<Object|null>} Updated credential object or null if not found
215
+ */
216
+ async updateCredential(credentialId, updates) {
217
+ // Get existing credential to merge OAuth data
218
+ const existing = await this.prisma.credential.findUnique({
219
+ where: { id: credentialId },
220
+ });
221
+
222
+ if (!existing) {
223
+ return null;
224
+ }
225
+
226
+ // Separate schema fields from OAuth data
227
+ const {
228
+ user,
229
+ userId,
230
+ externalId,
231
+ authIsValid,
232
+
233
+ ...oauthData
234
+ } = updates;
235
+
236
+ // Merge OAuth data with existing
237
+ const mergedData = { ...(existing.data || {}), ...oauthData };
238
+
239
+ const updated = await this.prisma.credential.update({
240
+ where: { id: credentialId },
241
+ data: {
242
+ userId: userId || user || existing.userId,
243
+ externalId:
244
+ externalId !== undefined ? externalId : existing.externalId,
245
+ authIsValid:
246
+ authIsValid !== undefined ? authIsValid : existing.authIsValid,
247
+ data: mergedData,
248
+ },
249
+ });
250
+
251
+ const data = updated.data || {};
252
+
253
+ return {
254
+ id: updated.id,
255
+ userId: updated.userId,
256
+ externalId: updated.externalId,
257
+ authIsValid: updated.authIsValid,
258
+ access_token: data.access_token,
259
+ refresh_token: data.refresh_token,
260
+ domain: data.domain,
261
+ ...data,
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Convert identifiers to Prisma where clause
267
+ * @private
268
+ * @param {Object} identifiers - Identifier fields
269
+ * @returns {Object} Prisma where clause
270
+ */
271
+ _convertIdentifiersToWhere(identifiers) {
272
+ const where = {};
273
+
274
+ if (identifiers._id) where.id = identifiers._id;
275
+ if (identifiers.id) where.id = identifiers.id;
276
+ if (identifiers.user) where.userId = identifiers.user;
277
+ if (identifiers.userId) where.userId = identifiers.userId;
278
+ if (identifiers.externalId) where.externalId = identifiers.externalId;
279
+
280
+ return where;
281
+ }
282
+
283
+ /**
284
+ * Convert filter to Prisma where clause
285
+ * @private
286
+ * @param {Object} filter - Filter criteria
287
+ * @returns {Object} Prisma where clause
288
+ */
289
+ _convertFilterToWhere(filter) {
290
+ const where = {};
291
+
292
+ if (filter.credentialId) where.id = filter.credentialId;
293
+ if (filter.id) where.id = filter.id;
294
+ if (filter.user) where.userId = filter.user;
295
+ if (filter.userId) where.userId = filter.userId;
296
+ if (filter.externalId) where.externalId = filter.externalId;
297
+
298
+ return where;
299
+ }
300
+ }
301
+
302
+ module.exports = { CredentialRepository };
@@ -0,0 +1,25 @@
1
+ class GetCredentialForUser {
2
+ constructor({ credentialRepository }) {
3
+ this.credentialRepository = credentialRepository;
4
+ }
5
+
6
+ async execute(credentialId, userId) {
7
+ const credential = await this.credentialRepository.findCredentialById(
8
+ credentialId
9
+ );
10
+
11
+ if (!credential) {
12
+ throw new Error(`Credential with id ${credentialId} not found`);
13
+ }
14
+
15
+ if (credential.userId.toString() !== userId.toString()) {
16
+ throw new Error(
17
+ `Credential ${credentialId} does not belong to user ${userId}`
18
+ );
19
+ }
20
+
21
+ return credential;
22
+ }
23
+ }
24
+
25
+ module.exports = { GetCredentialForUser };
@@ -0,0 +1,15 @@
1
+ class UpdateAuthenticationStatus {
2
+ constructor({ credentialRepository }) {
3
+ this.credentialRepository = credentialRepository;
4
+ }
5
+
6
+ /**
7
+ * @param {string} credentialId
8
+ * @param {boolean} authIsValid
9
+ */
10
+ async execute(credentialId, authIsValid) {
11
+ await this.credentialRepository.updateAuthenticationStatus(credentialId, authIsValid);
12
+ }
13
+ }
14
+
15
+ module.exports = { UpdateAuthenticationStatus };
@@ -0,0 +1,198 @@
1
+ # MongoDB Transaction Namespace Fix
2
+
3
+ ## Problem
4
+
5
+ The encryption health check was failing with the following error:
6
+
7
+ ```
8
+ Cannot create namespace frigg.Credential in multi-document transaction.
9
+ Error code: 263
10
+ ```
11
+
12
+ ### Root Cause
13
+
14
+ MongoDB does not allow creating collections (namespaces) inside multi-document transactions. When Prisma tries to create a document in a collection that doesn't exist yet, MongoDB needs to implicitly create the collection. If this happens inside a transaction context, MongoDB throws error code 263.
15
+
16
+ ### Technical Details
17
+
18
+ - **MongoDB Constraint**: Collections must exist before being used in multi-document transactions
19
+ - **Prisma Behavior**: Prisma may implicitly use transactions for certain operations
20
+ - **Impact**: Health checks fail on fresh databases or when collections haven't been created yet
21
+
22
+ ## Solution
23
+
24
+ **Implemented a comprehensive schema initialization system that ensures all collections exist at application startup.**
25
+
26
+ ### Architectural Approach
27
+
28
+ Rather than checking before each individual database operation, we take a **systematic, fail-fast approach**:
29
+
30
+ 1. **Parse Prisma Schema**: Extract all collection names from the Prisma schema definition
31
+ 2. **Initialize at Startup**: Create all collections when the database connection is established
32
+ 3. **Fail Fast**: If there are database issues, the application fails immediately at startup rather than during runtime operations
33
+ 4. **Idempotent**: Safe to run multiple times - only creates collections that don't exist
34
+
35
+ This follows the **"fail fast"** principle and ensures consistent state across all application instances.
36
+
37
+ ### Changes Made
38
+
39
+ 1. **Created MongoDB Schema Initialization** (`packages/core/database/utils/mongodb-schema-init.js`)
40
+ - `initializeMongoDBSchema()` - Ensures all Prisma collections exist at startup
41
+ - `getPrismaCollections()` - Returns list of all Prisma collection names
42
+ - `PRISMA_COLLECTIONS` - Constant array of all 13 Prisma collections
43
+ - Only runs for MongoDB (skips PostgreSQL)
44
+ - Fails fast if database not connected
45
+
46
+ 2. **Created MongoDB Collection Utilities** (`packages/core/database/utils/mongodb-collection-utils.js`)
47
+ - `ensureCollectionExists(collectionName)` - Ensures a single collection exists
48
+ - `ensureCollectionsExist(collectionNames)` - Batch creates multiple collections
49
+ - `collectionExists(collectionName)` - Checks if a collection exists
50
+ - Handles race conditions gracefully (NamespaceExists errors)
51
+
52
+ 3. **Integrated into Database Connection** (`packages/core/database/prisma.js`)
53
+ - Modified `connectPrisma()` to call `initializeMongoDBSchema()` after connection
54
+ - Ensures all collections exist before application handles requests
55
+
56
+ 4. **Updated Health Check Repository** (`packages/core/database/repositories/health-check-repository-mongodb.js`)
57
+ - Removed per-operation collection existence checks
58
+ - Added documentation noting schema is initialized at startup
59
+
60
+ 5. **Added Comprehensive Tests**
61
+ - `mongodb-schema-init.test.js` - Tests schema initialization system
62
+ - `mongodb-collection-utils.test.js` - Tests collection utility functions
63
+ - Tests error handling, race conditions, and edge cases
64
+
65
+ ### Implementation Flow
66
+
67
+ ```javascript
68
+ // 1. Application startup - connect to database
69
+ await connectPrisma();
70
+ └─> await initializeMongoDBSchema();
71
+ └─> await ensureCollectionsExist([
72
+ 'User', 'Token', 'Credential', 'Entity',
73
+ 'Integration', 'IntegrationMapping', 'Process',
74
+ 'Sync', 'DataIdentifier', 'Association',
75
+ 'AssociationObject', 'State', 'WebsocketConnection'
76
+ ]);
77
+
78
+ // 2. Now all collections exist - safe to handle requests
79
+ // No per-operation checks needed!
80
+ await prisma.credential.create({ data: {...} }); // Works without namespace error
81
+ ```
82
+
83
+ ## Best Practices Followed
84
+
85
+ 1. **Domain-Driven Design**: Created reusable utility module for MongoDB-specific concerns
86
+ 2. **Hexagonal Architecture**: Infrastructure concerns (schema initialization) handled in infrastructure layer
87
+ 3. **Test-Driven Development**: Added comprehensive tests for all utility functions
88
+ 4. **Fail Fast Principle**: Database issues discovered at startup, not during runtime
89
+ 5. **Idempotency**: Safe to run multiple times across multiple instances
90
+ 6. **Error Handling**: Graceful degradation on race conditions and errors
91
+ 7. **Documentation**: Inline comments, JSDoc, and comprehensive documentation
92
+
93
+ ## Benefits
94
+
95
+ ### Immediate Benefits
96
+ - ✅ Fixes encryption health check failures on fresh databases
97
+ - ✅ Prevents transaction namespace errors across **all** Prisma operations
98
+ - ✅ No per-operation overhead - collections created once at startup
99
+ - ✅ Fail fast - database issues discovered immediately at startup
100
+ - ✅ Idempotent - safe to run multiple times and across multiple instances
101
+
102
+ ### Architectural Benefits
103
+ - ✅ **Clean separation of concerns**: Schema initialization is infrastructure concern, handled at startup
104
+ - ✅ **Follows DDD/Hexagonal Architecture**: Infrastructure layer handles database setup, repositories focus on business operations
105
+ - ✅ **Consistent across all environments**: Dev, test, staging, production all follow same pattern
106
+ - ✅ **No repository-level checks needed**: All repositories benefit automatically
107
+ - ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
108
+
109
+ ### Operational Benefits
110
+ - ✅ **Predictable startup**: Clear logging of schema initialization
111
+ - ✅ **Zero runtime overhead**: Collections created once, not on every operation
112
+ - ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
113
+
114
+ ## Design Decisions
115
+
116
+ ### Why Initialize at Startup?
117
+
118
+ We considered two approaches:
119
+
120
+ **❌ Per-Operation Checks (Initial approach)**
121
+ ```javascript
122
+ async createCredential(data) {
123
+ await ensureCollectionExists('Credential'); // Check every time
124
+ return await prisma.credential.create({ data });
125
+ }
126
+ ```
127
+ - Pros: Guarantees collection exists before each operation
128
+ - Cons: Runtime overhead, repeated checks, scattered logic
129
+
130
+ **✅ Startup Initialization (Final approach)**
131
+ ```javascript
132
+ // Once at startup
133
+ await connectPrisma(); // Initializes all collections
134
+
135
+ // All operations just work
136
+ async createCredential(data) {
137
+ return await prisma.credential.create({ data }); // No checks needed
138
+ }
139
+ ```
140
+ - Pros: Zero runtime overhead, centralized logic, fail fast, consistent
141
+ - Cons: Requires database connection at startup (already required)
142
+
143
+ ### Benefits of Startup Approach
144
+
145
+ 1. **Performance**: Collections created once vs. checking before every operation
146
+ 2. **Simplicity**: No conditional logic in repositories
147
+ 3. **Reliability**: Fail fast at startup if database has issues
148
+ 4. **Maintainability**: Single source of truth for schema initialization
149
+ 5. **DDD Alignment**: Infrastructure concerns handled in infrastructure layer
150
+
151
+ ## Logging Output
152
+
153
+ When the application starts, you'll see clear logging:
154
+
155
+ ```
156
+ Initializing MongoDB schema - ensuring all collections exist...
157
+ Created MongoDB collection: Credential
158
+ MongoDB schema initialization complete - 13 collections verified (45ms)
159
+ ```
160
+
161
+ On subsequent startups (collections already exist):
162
+ ```
163
+ Initializing MongoDB schema - ensuring all collections exist...
164
+ MongoDB schema initialization complete - 13 collections verified (12ms)
165
+ ```
166
+
167
+ ## References
168
+
169
+ - [Prisma Issue #8305](https://github.com/prisma/prisma/issues/8305) - MongoDB "Cannot create namespace" error
170
+ - [Mongoose Issue #6699](https://github.com/Automattic/mongoose/issues/6699) - Similar issue in Mongoose
171
+ - [MongoDB Transactions Documentation](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations) - Operations allowed in transactions
172
+ - [Prisma MongoDB Guide](https://www.prisma.io/docs/guides/database/mongodb) - Using Prisma with MongoDB
173
+
174
+ ## Future Considerations
175
+
176
+ ### Automatic Schema Sync
177
+ Consider enhancing the system to:
178
+ - Parse Prisma schema file dynamically to extract collection names
179
+ - Auto-detect schema changes and create new collections
180
+ - Provide CLI command for manual schema initialization
181
+
182
+ ### Migration Support
183
+ For production deployments with existing data:
184
+ - Document migration procedures for new collections
185
+ - Consider pre-migration scripts for blue-green deployments
186
+ - Add health check for schema initialization status
187
+
188
+ ### Multi-Database Support
189
+ The system already handles:
190
+ - ✅ MongoDB - Full schema initialization
191
+ - ✅ PostgreSQL - Skips initialization (uses Prisma migrations)
192
+ - Consider adding explicit migration support for DocumentDB-specific features
193
+
194
+ ### Index Creation
195
+ Future enhancement could also create indexes at startup:
196
+ - Parse Prisma schema for `@@index` directives
197
+ - Create indexes if they don't exist
198
+ - Provide index health checks
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Lambda Invoker Adapter
3
+ * Infrastructure layer - handles AWS Lambda function invocations
4
+ *
5
+ * Part of Hexagonal Architecture:
6
+ * - Infrastructure Layer adapter for AWS SDK
7
+ * - Used by Domain Layer use cases
8
+ * - Isolates AWS-specific logic from business logic
9
+ */
10
+
11
+ const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
12
+
13
+ /**
14
+ * Custom error for Lambda invocation failures
15
+ * Provides structured error information for debugging
16
+ */
17
+ class LambdaInvocationError extends Error {
18
+ constructor(message, functionName, statusCode) {
19
+ super(message);
20
+ this.name = 'LambdaInvocationError';
21
+ this.functionName = functionName;
22
+ this.statusCode = statusCode;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Adapter for invoking AWS Lambda functions
28
+ *
29
+ * Infrastructure layer - handles AWS SDK communication
30
+ * Converts AWS SDK responses to domain-friendly formats
31
+ */
32
+ class LambdaInvoker {
33
+ /**
34
+ * @param {LambdaClient} lambdaClient - AWS Lambda client (injected for testability)
35
+ */
36
+ constructor(lambdaClient = new LambdaClient({})) {
37
+ this.client = lambdaClient;
38
+ }
39
+
40
+ /**
41
+ * Invoke Lambda function synchronously
42
+ *
43
+ * @param {string} functionName - Lambda function name or ARN
44
+ * @param {Object} payload - Event payload to send to Lambda
45
+ * @returns {Promise<Object>} Parsed response body
46
+ * @throws {LambdaInvocationError} If Lambda returns error status
47
+ * @throws {Error} If AWS SDK call fails
48
+ */
49
+ async invoke(functionName, payload) {
50
+ try {
51
+ const command = new InvokeCommand({
52
+ FunctionName: functionName,
53
+ InvocationType: 'RequestResponse', // Synchronous
54
+ Payload: JSON.stringify(payload),
55
+ });
56
+
57
+ const response = await this.client.send(command);
58
+
59
+ // Parse response payload
60
+ let result;
61
+ try {
62
+ result = JSON.parse(Buffer.from(response.Payload).toString());
63
+ } catch (parseError) {
64
+ throw new LambdaInvocationError(
65
+ `Failed to parse Lambda response: ${parseError.message}`,
66
+ functionName,
67
+ null
68
+ );
69
+ }
70
+
71
+ // Check status code
72
+ if (result.statusCode === 200) {
73
+ return result.body;
74
+ }
75
+
76
+ // Lambda returned error status
77
+ const errorMessage = result.body?.error || 'Lambda invocation failed';
78
+ throw new LambdaInvocationError(
79
+ `Lambda ${functionName} returned error: ${errorMessage}`,
80
+ functionName,
81
+ result.statusCode
82
+ );
83
+ } catch (error) {
84
+ // Re-throw LambdaInvocationError as-is
85
+ if (error instanceof LambdaInvocationError) {
86
+ throw error;
87
+ }
88
+
89
+ // Wrap AWS SDK errors
90
+ throw new Error(`Failed to invoke Lambda ${functionName}: ${error.message}`);
91
+ }
92
+ }
93
+ }
94
+
95
+ module.exports = { LambdaInvoker, LambdaInvocationError };
96
+
97
+