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

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 +87 -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 +36 -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
package/core/Worker.js CHANGED
@@ -1,10 +1,9 @@
1
- const AWS = require('aws-sdk');
1
+ const { SQSClient, GetQueueUrlCommand, SendMessageCommand } = require('@aws-sdk/client-sqs');
2
2
  const _ = require('lodash');
3
3
  const { RequiredPropertyError } = require('../errors');
4
4
  const { get } = require('../assertions');
5
5
 
6
- AWS.config.update({ region: process.env.AWS_REGION });
7
- const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
6
+ const sqs = new SQSClient({ region: process.env.AWS_REGION });
8
7
 
9
8
  class Worker {
10
9
  async getQueueURL(params) {
@@ -12,15 +11,9 @@ class Worker {
12
11
  // let params = {
13
12
  // QueueName: process.env.QueueName
14
13
  // };
15
- return new Promise((resolve, reject) => {
16
- sqs.getQueueUrl(params, (err, data) => {
17
- if (err) {
18
- reject(err);
19
- } else {
20
- resolve(data.QueueUrl);
21
- }
22
- });
23
- });
14
+ const command = new GetQueueUrlCommand(params);
15
+ const data = await sqs.send(command);
16
+ return data.QueueUrl;
24
17
  }
25
18
 
26
19
  async run(params, context = {}) {
@@ -54,15 +47,9 @@ class Worker {
54
47
  }
55
48
 
56
49
  async sendAsyncSQSMessage(params) {
57
- return new Promise((resolve, reject) => {
58
- sqs.sendMessage(params, (err, data) => {
59
- if (err) {
60
- reject(err);
61
- } else {
62
- resolve(data.MessageId);
63
- }
64
- });
65
- });
50
+ const command = new SendMessageCommand(params);
51
+ const data = await sqs.send(command);
52
+ return data.MessageId;
66
53
  }
67
54
 
68
55
  // Throw an exception if the params do not validate
@@ -1,7 +1,7 @@
1
1
  // This line should be at the top of the webpacked output, so be sure to require createHandler first in any handlers. "Soon" sourcemaps will be built into Node... after that, this package won't be needed.
2
- require('source-map-support').install();
2
+ // REMOVING FOR NOW UNTIL WE ADD WEBPACK BACK IN
3
+ // require('source-map-support').install();
3
4
 
4
- const { connectToDatabase } = require('../database/mongo');
5
5
  const { initDebugLog, flushDebugLog } = require('../logs');
6
6
  const { secretsToEnv } = require('./secrets-to-env');
7
7
 
@@ -10,7 +10,6 @@ const createHandler = (optionByName = {}) => {
10
10
  eventName = 'Event',
11
11
  isUserFacingResponse = true,
12
12
  method,
13
- shouldUseDatabase = true,
14
13
  } = optionByName;
15
14
 
16
15
  if (!method) {
@@ -33,10 +32,6 @@ const createHandler = (optionByName = {}) => {
33
32
  // Helps mongoose reuse the connection. Lowers response times.
34
33
  context.callbackWaitsForEmptyEventLoop = false;
35
34
 
36
- if (shouldUseDatabase) {
37
- await connectToDatabase();
38
- }
39
-
40
35
  // Run the Lambda
41
36
  return await method(event, context);
42
37
  } catch (error) {
@@ -0,0 +1,47 @@
1
+ const { CredentialRepositoryMongo } = require('./credential-repository-mongo');
2
+ const {
3
+ CredentialRepositoryPostgres,
4
+ } = require('./credential-repository-postgres');
5
+ const config = require('../../database/config');
6
+
7
+ /**
8
+ * Credential Repository Factory
9
+ * Creates the appropriate repository adapter based on database type
10
+ *
11
+ * Database-specific implementations:
12
+ * - MongoDB: Uses String IDs (ObjectId), no conversion needed
13
+ * - PostgreSQL: Uses Int IDs, converts String ↔ Int
14
+ *
15
+ * All repository methods return String IDs regardless of database type,
16
+ * ensuring application layer consistency.
17
+ *
18
+ * Usage:
19
+ * ```javascript
20
+ * const repository = createCredentialRepository();
21
+ * ```
22
+ *
23
+ * @returns {CredentialRepositoryInterface} Configured repository adapter
24
+ */
25
+ function createCredentialRepository() {
26
+ const dbType = config.DB_TYPE;
27
+
28
+ switch (dbType) {
29
+ case 'mongodb':
30
+ return new CredentialRepositoryMongo();
31
+
32
+ case 'postgresql':
33
+ return new CredentialRepositoryPostgres();
34
+
35
+ default:
36
+ throw new Error(
37
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
38
+ );
39
+ }
40
+ }
41
+
42
+ module.exports = {
43
+ createCredentialRepository,
44
+ // Export adapters for direct testing
45
+ CredentialRepositoryMongo,
46
+ CredentialRepositoryPostgres,
47
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Credential Repository Interface
3
+ * Abstract base class defining the contract for credential persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * Note: Currently, Credential model has identical structure across MongoDB and PostgreSQL,
11
+ * so CredentialRepository serves both. This interface exists for consistency and
12
+ * future-proofing if database-specific implementations become needed.
13
+ *
14
+ * @abstract
15
+ */
16
+ class CredentialRepositoryInterface {
17
+ /**
18
+ * Find credential by ID
19
+ *
20
+ * @param {string|number} id - Credential ID
21
+ * @returns {Promise<Object|null>} Credential object or null
22
+ * @abstract
23
+ */
24
+ async findCredentialById(id) {
25
+ throw new Error(
26
+ 'Method findCredentialById must be implemented by subclass'
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Update authentication status
32
+ *
33
+ * @param {string|number} credentialId - Credential ID
34
+ * @param {boolean} authIsValid - Authentication validity status
35
+ * @returns {Promise<Object>} Update result
36
+ * @abstract
37
+ */
38
+ async updateAuthenticationStatus(credentialId, authIsValid) {
39
+ throw new Error(
40
+ 'Method updateAuthenticationStatus must be implemented by subclass'
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Permanently remove a credential document
46
+ *
47
+ * @param {string|number} credentialId - Credential ID
48
+ * @returns {Promise<Object>} Deletion result
49
+ * @abstract
50
+ */
51
+ async deleteCredentialById(credentialId) {
52
+ throw new Error(
53
+ 'Method deleteCredentialById must be implemented by subclass'
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Create or update credential matching identifiers
59
+ *
60
+ * @param {{identifiers: Object, details: Object}} credentialDetails
61
+ * @returns {Promise<Object>} The persisted credential
62
+ * @abstract
63
+ */
64
+ async upsertCredential(credentialDetails) {
65
+ throw new Error(
66
+ 'Method upsertCredential must be implemented by subclass'
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Find a credential by filter criteria
72
+ *
73
+ * @param {Object} filter - Filter criteria
74
+ * @returns {Promise<Object|null>} Credential object or null if not found
75
+ * @abstract
76
+ */
77
+ async findCredential(filter) {
78
+ throw new Error(
79
+ 'Method findCredential must be implemented by subclass'
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Update a credential by ID
85
+ *
86
+ * @param {string|number} credentialId - Credential ID
87
+ * @param {Object} updates - Fields to update
88
+ * @returns {Promise<Object|null>} Updated credential object or null if not found
89
+ * @abstract
90
+ */
91
+ async updateCredential(credentialId, updates) {
92
+ throw new Error(
93
+ 'Method updateCredential must be implemented by subclass'
94
+ );
95
+ }
96
+ }
97
+
98
+ module.exports = { CredentialRepositoryInterface };
@@ -0,0 +1,307 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ CredentialRepositoryInterface,
4
+ } = require('./credential-repository-interface');
5
+
6
+ /**
7
+ * MongoDB Credential Repository Adapter
8
+ * Handles OAuth credentials and API tokens persistence with MongoDB
9
+ *
10
+ * MongoDB-specific characteristics:
11
+ * - Uses String IDs (ObjectId)
12
+ * - No ID conversion needed (IDs are already strings)
13
+ * - Dynamic schema support via JSON field
14
+ */
15
+ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Find credential by ID
23
+ * Replaces: Credential.findById(id)
24
+ *
25
+ * @param {string} id - Credential ID
26
+ * @returns {Promise<Object|null>} Credential object or null
27
+ */
28
+ async findCredentialById(id) {
29
+ const credential = await this.prisma.credential.findUnique({
30
+ where: { id },
31
+ });
32
+
33
+ if (!credential) {
34
+ return null;
35
+ }
36
+
37
+ // Extract data from JSON field
38
+ const data = credential.data || {};
39
+
40
+ return {
41
+ _id: credential.id,
42
+ id: credential.id,
43
+ user: credential.userId,
44
+ userId: credential.userId,
45
+ externalId: credential.externalId,
46
+ authIsValid: credential.authIsValid,
47
+ ...data, // Spread OAuth tokens from JSON field
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Update authentication status
53
+ * Replaces: Credential.updateOne({ _id: credentialId }, { $set: { authIsValid } })
54
+ *
55
+ * @param {string} credentialId - Credential ID
56
+ * @param {boolean} authIsValid - Authentication validity status
57
+ * @returns {Promise<Object>} Update result
58
+ */
59
+ async updateAuthenticationStatus(credentialId, authIsValid) {
60
+ await this.prisma.credential.update({
61
+ where: { id: credentialId },
62
+ data: { authIsValid },
63
+ });
64
+
65
+ return { acknowledged: true, modifiedCount: 1 };
66
+ }
67
+
68
+ /**
69
+ * Permanently remove a credential document
70
+ * Replaces: Credential.deleteOne({ _id: credentialId })
71
+ *
72
+ * @param {string} credentialId - Credential ID
73
+ * @returns {Promise<Object>} Deletion result
74
+ */
75
+ async deleteCredentialById(credentialId) {
76
+ try {
77
+ await this.prisma.credential.delete({
78
+ where: { id: credentialId },
79
+ });
80
+ return { acknowledged: true, deletedCount: 1 };
81
+ } catch (error) {
82
+ if (error.code === 'P2025') {
83
+ // Record not found
84
+ return { acknowledged: true, deletedCount: 0 };
85
+ }
86
+ throw error;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Create or update credential matching identifiers
92
+ * Replaces: Credential.findOneAndUpdate(query, update, { upsert: true })
93
+ *
94
+ * @param {{identifiers: Object, details: Object}} credentialDetails
95
+ * @returns {Promise<Object>} The persisted credential
96
+ */
97
+ async upsertCredential(credentialDetails) {
98
+ const { identifiers, details } = credentialDetails;
99
+ if (!identifiers)
100
+ throw new Error('identifiers required to upsert credential');
101
+
102
+ if (!identifiers.user && !identifiers.userId) {
103
+ throw new Error('user or userId required in identifiers');
104
+ }
105
+ if (!identifiers.externalId) {
106
+ throw new Error(
107
+ 'externalId required in identifiers to prevent credential collision. ' +
108
+ 'When multiple credentials exist for the same user, both userId and externalId ' +
109
+ 'are needed to uniquely identify which credential to update.'
110
+ );
111
+ }
112
+
113
+ // Build where clause from identifiers
114
+ const where = this._convertIdentifiersToWhere(identifiers);
115
+
116
+ // Separate schema fields from dynamic OAuth data
117
+ const {
118
+ user,
119
+ userId,
120
+ externalId,
121
+ authIsValid,
122
+
123
+ ...oauthData
124
+ } = details;
125
+
126
+ // Find existing credential
127
+ const existing = await this.prisma.credential.findFirst({ where });
128
+
129
+ if (existing) {
130
+ // Update existing - merge OAuth data into existing data JSON
131
+ const mergedData = { ...(existing.data || {}), ...oauthData };
132
+
133
+ const updated = await this.prisma.credential.update({
134
+ where: { id: existing.id },
135
+ data: {
136
+ userId: userId || user || existing.userId,
137
+ externalId:
138
+ externalId !== undefined
139
+ ? externalId
140
+ : existing.externalId,
141
+ authIsValid:
142
+ authIsValid !== undefined
143
+ ? authIsValid
144
+ : existing.authIsValid,
145
+ data: mergedData,
146
+ },
147
+ });
148
+
149
+ return {
150
+ id: updated.id,
151
+ externalId: updated.externalId,
152
+ userId: updated.userId,
153
+ authIsValid: updated.authIsValid,
154
+ ...(updated.data || {}),
155
+ };
156
+ }
157
+
158
+ // Create new credential
159
+ const created = await this.prisma.credential.create({
160
+ data: {
161
+ userId: userId || user,
162
+ externalId,
163
+ authIsValid: authIsValid,
164
+
165
+ data: oauthData,
166
+ },
167
+ });
168
+
169
+ return {
170
+ id: created.id,
171
+ externalId: created.externalId,
172
+ userId: created.userId,
173
+ authIsValid: created.authIsValid,
174
+ ...(created.data || {}),
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Find a credential by filter criteria
180
+ * Replaces: Credential.findOne(query)
181
+ *
182
+ * @param {Object} filter
183
+ * @param {string} [filter.userId] - User ID
184
+ * @param {string} [filter.externalId] - External ID
185
+ * @param {string} [filter.credentialId] - Credential ID
186
+ * @returns {Promise<Object|null>} Credential object or null if not found
187
+ */
188
+ async findCredential(filter) {
189
+ const where = this._convertFilterToWhere(filter);
190
+
191
+ const credential = await this.prisma.credential.findFirst({
192
+ where,
193
+ });
194
+
195
+ if (!credential) {
196
+ return null;
197
+ }
198
+
199
+ const data = credential.data || {};
200
+
201
+ return {
202
+ id: credential.id,
203
+ userId: credential.userId,
204
+ externalId: credential.externalId,
205
+ authIsValid: credential.authIsValid,
206
+ access_token: data.access_token,
207
+ refresh_token: data.refresh_token,
208
+ domain: data.domain,
209
+ ...data,
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Update a credential by ID
215
+ * Replaces: Credential.findByIdAndUpdate(credentialId, { $set: updates })
216
+ *
217
+ * @param {string} credentialId - Credential ID
218
+ * @param {Object} updates - Fields to update
219
+ * @returns {Promise<Object|null>} Updated credential object or null if not found
220
+ */
221
+ async updateCredential(credentialId, updates) {
222
+ // Get existing credential to merge OAuth data
223
+ const existing = await this.prisma.credential.findUnique({
224
+ where: { id: credentialId },
225
+ });
226
+
227
+ if (!existing) {
228
+ return null;
229
+ }
230
+
231
+ // Separate schema fields from OAuth data
232
+ const {
233
+ user,
234
+ userId,
235
+ externalId,
236
+ authIsValid,
237
+
238
+ ...oauthData
239
+ } = updates;
240
+
241
+ // Merge OAuth data with existing
242
+ const mergedData = { ...(existing.data || {}), ...oauthData };
243
+
244
+ const updated = await this.prisma.credential.update({
245
+ where: { id: credentialId },
246
+ data: {
247
+ userId: userId || user || existing.userId,
248
+ externalId:
249
+ externalId !== undefined ? externalId : existing.externalId,
250
+ authIsValid:
251
+ authIsValid !== undefined ? authIsValid : existing.authIsValid,
252
+ data: mergedData,
253
+ },
254
+ });
255
+
256
+ const data = updated.data || {};
257
+
258
+ return {
259
+ id: updated.id,
260
+ userId: updated.userId,
261
+ externalId: updated.externalId,
262
+ authIsValid: updated.authIsValid,
263
+ access_token: data.access_token,
264
+ refresh_token: data.refresh_token,
265
+ domain: data.domain,
266
+ ...data,
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Convert identifiers to Prisma where clause
272
+ * @private
273
+ * @param {Object} identifiers - Identifier fields
274
+ * @returns {Object} Prisma where clause
275
+ */
276
+ _convertIdentifiersToWhere(identifiers) {
277
+ const where = {};
278
+
279
+ if (identifiers._id) where.id = identifiers._id;
280
+ if (identifiers.id) where.id = identifiers.id;
281
+ if (identifiers.user) where.userId = identifiers.user;
282
+ if (identifiers.userId) where.userId = identifiers.userId;
283
+ if (identifiers.externalId) where.externalId = identifiers.externalId;
284
+
285
+ return where;
286
+ }
287
+
288
+ /**
289
+ * Convert filter to Prisma where clause
290
+ * @private
291
+ * @param {Object} filter - Filter criteria
292
+ * @returns {Object} Prisma where clause
293
+ */
294
+ _convertFilterToWhere(filter) {
295
+ const where = {};
296
+
297
+ if (filter.credentialId) where.id = filter.credentialId;
298
+ if (filter.id) where.id = filter.id;
299
+ if (filter.user) where.userId = filter.user;
300
+ if (filter.userId) where.userId = filter.userId;
301
+ if (filter.externalId) where.externalId = filter.externalId;
302
+
303
+ return where;
304
+ }
305
+ }
306
+
307
+ module.exports = { CredentialRepositoryMongo };