@friggframework/core 2.0.0-next.7 → 2.0.0-next.70

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 (293) 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/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/Worker.js +8 -21
  12. package/core/create-handler.js +14 -7
  13. package/credential/repositories/credential-repository-documentdb.js +304 -0
  14. package/credential/repositories/credential-repository-factory.js +54 -0
  15. package/credential/repositories/credential-repository-interface.js +98 -0
  16. package/credential/repositories/credential-repository-mongo.js +269 -0
  17. package/credential/repositories/credential-repository-postgres.js +287 -0
  18. package/credential/repositories/credential-repository.js +300 -0
  19. package/credential/use-cases/get-credential-for-user.js +25 -0
  20. package/credential/use-cases/update-authentication-status.js +15 -0
  21. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  22. package/database/adapters/lambda-invoker.js +97 -0
  23. package/database/config.js +154 -0
  24. package/database/documentdb-encryption-service.js +330 -0
  25. package/database/documentdb-utils.js +136 -0
  26. package/database/encryption/README.md +839 -0
  27. package/database/encryption/documentdb-encryption-service.md +3575 -0
  28. package/database/encryption/encryption-schema-registry.js +268 -0
  29. package/database/encryption/field-encryption-service.js +226 -0
  30. package/database/encryption/logger.js +79 -0
  31. package/database/encryption/prisma-encryption-extension.js +222 -0
  32. package/database/index.js +61 -21
  33. package/database/models/WebsocketConnection.js +16 -10
  34. package/database/models/readme.md +1 -0
  35. package/database/prisma.js +182 -0
  36. package/database/repositories/health-check-repository-documentdb.js +134 -0
  37. package/database/repositories/health-check-repository-factory.js +48 -0
  38. package/database/repositories/health-check-repository-interface.js +82 -0
  39. package/database/repositories/health-check-repository-mongodb.js +89 -0
  40. package/database/repositories/health-check-repository-postgres.js +82 -0
  41. package/database/repositories/health-check-repository.js +108 -0
  42. package/database/repositories/migration-status-repository-s3.js +137 -0
  43. package/database/use-cases/check-database-health-use-case.js +29 -0
  44. package/database/use-cases/check-database-state-use-case.js +81 -0
  45. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  46. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  47. package/database/use-cases/get-migration-status-use-case.js +93 -0
  48. package/database/use-cases/run-database-migration-use-case.js +139 -0
  49. package/database/use-cases/test-encryption-use-case.js +253 -0
  50. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  51. package/database/utils/mongodb-collection-utils.js +91 -0
  52. package/database/utils/mongodb-schema-init.js +106 -0
  53. package/database/utils/prisma-runner.js +477 -0
  54. package/database/utils/prisma-schema-parser.js +182 -0
  55. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  56. package/encrypt/Cryptor.js +34 -168
  57. package/encrypt/index.js +1 -2
  58. package/encrypt/test-encrypt.js +0 -2
  59. package/errors/client-safe-error.js +26 -0
  60. package/errors/fetch-error.js +2 -1
  61. package/errors/index.js +2 -0
  62. package/generated/prisma-mongodb/client.d.ts +1 -0
  63. package/generated/prisma-mongodb/client.js +4 -0
  64. package/generated/prisma-mongodb/default.d.ts +1 -0
  65. package/generated/prisma-mongodb/default.js +4 -0
  66. package/generated/prisma-mongodb/edge.d.ts +1 -0
  67. package/generated/prisma-mongodb/edge.js +335 -0
  68. package/generated/prisma-mongodb/index-browser.js +317 -0
  69. package/generated/prisma-mongodb/index.d.ts +22955 -0
  70. package/generated/prisma-mongodb/index.js +360 -0
  71. package/generated/prisma-mongodb/package.json +183 -0
  72. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  73. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  74. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  75. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  76. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  77. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  78. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  79. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  80. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  81. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  82. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  83. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  84. package/generated/prisma-mongodb/schema.prisma +362 -0
  85. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  86. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  87. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  88. package/generated/prisma-mongodb/wasm.js +342 -0
  89. package/generated/prisma-postgresql/client.d.ts +1 -0
  90. package/generated/prisma-postgresql/client.js +4 -0
  91. package/generated/prisma-postgresql/default.d.ts +1 -0
  92. package/generated/prisma-postgresql/default.js +4 -0
  93. package/generated/prisma-postgresql/edge.d.ts +1 -0
  94. package/generated/prisma-postgresql/edge.js +357 -0
  95. package/generated/prisma-postgresql/index-browser.js +339 -0
  96. package/generated/prisma-postgresql/index.d.ts +25131 -0
  97. package/generated/prisma-postgresql/index.js +382 -0
  98. package/generated/prisma-postgresql/package.json +183 -0
  99. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  100. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  101. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  102. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  103. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  104. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  105. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  106. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  107. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  108. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  109. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  110. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  111. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  112. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  113. package/generated/prisma-postgresql/schema.prisma +345 -0
  114. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  115. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  116. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  117. package/generated/prisma-postgresql/wasm.js +364 -0
  118. package/handlers/WEBHOOKS.md +653 -0
  119. package/handlers/app-definition-loader.js +38 -0
  120. package/handlers/app-handler-helpers.js +56 -0
  121. package/handlers/backend-utils.js +186 -0
  122. package/handlers/database-migration-handler.js +227 -0
  123. package/handlers/integration-event-dispatcher.js +54 -0
  124. package/handlers/routers/HEALTHCHECK.md +342 -0
  125. package/handlers/routers/auth.js +15 -0
  126. package/handlers/routers/db-migration.handler.js +29 -0
  127. package/handlers/routers/db-migration.js +326 -0
  128. package/handlers/routers/health.js +516 -0
  129. package/handlers/routers/integration-defined-routers.js +45 -0
  130. package/handlers/routers/integration-webhook-routers.js +67 -0
  131. package/handlers/routers/user.js +63 -0
  132. package/handlers/routers/websocket.js +57 -0
  133. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  134. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  135. package/handlers/workers/db-migration.js +352 -0
  136. package/handlers/workers/integration-defined-workers.js +27 -0
  137. package/index.js +78 -22
  138. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  139. package/infrastructure/scheduler/index.js +33 -0
  140. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  141. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  142. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  143. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  144. package/integrations/index.js +12 -10
  145. package/integrations/integration-base.js +326 -55
  146. package/integrations/integration-router.js +374 -179
  147. package/integrations/options.js +1 -1
  148. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  149. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  150. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  151. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  152. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  153. package/integrations/repositories/integration-mapping-repository.js +156 -0
  154. package/integrations/repositories/integration-repository-documentdb.js +210 -0
  155. package/integrations/repositories/integration-repository-factory.js +51 -0
  156. package/integrations/repositories/integration-repository-interface.js +127 -0
  157. package/integrations/repositories/integration-repository-mongo.js +303 -0
  158. package/integrations/repositories/integration-repository-postgres.js +352 -0
  159. package/integrations/repositories/process-repository-documentdb.js +243 -0
  160. package/integrations/repositories/process-repository-factory.js +53 -0
  161. package/integrations/repositories/process-repository-interface.js +90 -0
  162. package/integrations/repositories/process-repository-mongo.js +190 -0
  163. package/integrations/repositories/process-repository-postgres.js +217 -0
  164. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  165. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  166. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  167. package/integrations/use-cases/create-integration.js +83 -0
  168. package/integrations/use-cases/create-process.js +128 -0
  169. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  170. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  171. package/integrations/use-cases/get-integration-for-user.js +78 -0
  172. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  173. package/integrations/use-cases/get-integration-instance.js +83 -0
  174. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  175. package/integrations/use-cases/get-possible-integrations.js +27 -0
  176. package/integrations/use-cases/get-process.js +87 -0
  177. package/integrations/use-cases/index.js +19 -0
  178. package/integrations/use-cases/load-integration-context.js +71 -0
  179. package/integrations/use-cases/update-integration-messages.js +44 -0
  180. package/integrations/use-cases/update-integration-status.js +32 -0
  181. package/integrations/use-cases/update-integration.js +92 -0
  182. package/integrations/use-cases/update-process-metrics.js +201 -0
  183. package/integrations/use-cases/update-process-state.js +119 -0
  184. package/integrations/utils/map-integration-dto.js +37 -0
  185. package/jest-global-setup-noop.js +3 -0
  186. package/jest-global-teardown-noop.js +3 -0
  187. package/logs/logger.js +0 -4
  188. package/{module-plugin → modules}/entity.js +1 -1
  189. package/{module-plugin → modules}/index.js +0 -8
  190. package/modules/module-factory.js +56 -0
  191. package/modules/module.js +221 -0
  192. package/modules/repositories/module-repository-documentdb.js +335 -0
  193. package/modules/repositories/module-repository-factory.js +40 -0
  194. package/modules/repositories/module-repository-interface.js +129 -0
  195. package/modules/repositories/module-repository-mongo.js +408 -0
  196. package/modules/repositories/module-repository-postgres.js +453 -0
  197. package/modules/repositories/module-repository.js +345 -0
  198. package/modules/requester/api-key.js +52 -0
  199. package/modules/requester/oauth-2.js +384 -0
  200. package/{module-plugin → modules}/requester/requester.js +4 -2
  201. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  202. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  203. package/modules/tests/doubles/test-module-factory.js +16 -0
  204. package/modules/tests/doubles/test-module-repository.js +39 -0
  205. package/modules/use-cases/get-entities-for-user.js +32 -0
  206. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  207. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  208. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  209. package/modules/use-cases/get-module.js +74 -0
  210. package/modules/use-cases/process-authorization-callback.js +133 -0
  211. package/modules/use-cases/refresh-entity-options.js +72 -0
  212. package/modules/use-cases/test-module-auth.js +72 -0
  213. package/modules/utils/map-module-dto.js +18 -0
  214. package/package.json +82 -50
  215. package/prisma-mongodb/schema.prisma +362 -0
  216. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  217. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  218. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  219. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  220. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  221. package/prisma-postgresql/schema.prisma +345 -0
  222. package/queues/queuer-util.js +27 -22
  223. package/syncs/manager.js +468 -443
  224. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  225. package/syncs/repositories/sync-repository-factory.js +43 -0
  226. package/syncs/repositories/sync-repository-interface.js +109 -0
  227. package/syncs/repositories/sync-repository-mongo.js +239 -0
  228. package/syncs/repositories/sync-repository-postgres.js +319 -0
  229. package/syncs/sync.js +0 -1
  230. package/token/repositories/token-repository-documentdb.js +137 -0
  231. package/token/repositories/token-repository-factory.js +40 -0
  232. package/token/repositories/token-repository-interface.js +131 -0
  233. package/token/repositories/token-repository-mongo.js +219 -0
  234. package/token/repositories/token-repository-postgres.js +264 -0
  235. package/token/repositories/token-repository.js +219 -0
  236. package/types/core/index.d.ts +2 -2
  237. package/types/integrations/index.d.ts +2 -6
  238. package/types/module-plugin/index.d.ts +5 -59
  239. package/types/syncs/index.d.ts +0 -2
  240. package/user/repositories/user-repository-documentdb.js +441 -0
  241. package/user/repositories/user-repository-factory.js +52 -0
  242. package/user/repositories/user-repository-interface.js +201 -0
  243. package/user/repositories/user-repository-mongo.js +308 -0
  244. package/user/repositories/user-repository-postgres.js +360 -0
  245. package/user/tests/doubles/test-user-repository.js +72 -0
  246. package/user/use-cases/authenticate-user.js +127 -0
  247. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  248. package/user/use-cases/create-individual-user.js +61 -0
  249. package/user/use-cases/create-organization-user.js +47 -0
  250. package/user/use-cases/create-token-for-user-id.js +30 -0
  251. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  252. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  253. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  254. package/user/use-cases/login-user.js +122 -0
  255. package/user/user.js +125 -0
  256. package/utils/backend-path.js +38 -0
  257. package/utils/index.js +6 -0
  258. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  259. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  260. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  261. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  262. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  263. package/websocket/repositories/websocket-connection-repository.js +161 -0
  264. package/database/models/State.js +0 -9
  265. package/database/models/Token.js +0 -70
  266. package/database/mongo.js +0 -45
  267. package/encrypt/Cryptor.test.js +0 -32
  268. package/encrypt/encrypt.js +0 -132
  269. package/encrypt/encrypt.test.js +0 -1069
  270. package/errors/base-error.test.js +0 -32
  271. package/errors/fetch-error.test.js +0 -79
  272. package/errors/halt-error.test.js +0 -11
  273. package/errors/validation-errors.test.js +0 -120
  274. package/integrations/create-frigg-backend.js +0 -31
  275. package/integrations/integration-factory.js +0 -251
  276. package/integrations/integration-mapping.js +0 -43
  277. package/integrations/integration-model.js +0 -46
  278. package/integrations/integration-user.js +0 -144
  279. package/integrations/test/integration-base.test.js +0 -144
  280. package/lambda/TimeoutCatcher.test.js +0 -68
  281. package/logs/logger.test.js +0 -76
  282. package/module-plugin/auther.js +0 -393
  283. package/module-plugin/credential.js +0 -22
  284. package/module-plugin/entity-manager.js +0 -70
  285. package/module-plugin/manager.js +0 -169
  286. package/module-plugin/module-factory.js +0 -61
  287. package/module-plugin/requester/api-key.js +0 -36
  288. package/module-plugin/requester/oauth-2.js +0 -219
  289. package/module-plugin/requester/requester.test.js +0 -28
  290. package/module-plugin/test/auther.test.js +0 -97
  291. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  292. /package/{module-plugin → modules}/requester/basic.js +0 -0
  293. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Module Repository Interface
3
+ * Abstract base class defining the contract for Entity (module) 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, Entity model has identical structure across MongoDB and PostgreSQL,
11
+ * so ModuleRepository serves both. This interface exists for consistency and
12
+ * future-proofing if database-specific implementations become needed.
13
+ *
14
+ * @abstract
15
+ */
16
+ class ModuleRepositoryInterface {
17
+ /**
18
+ * Find entity by ID with credential
19
+ *
20
+ * @param {string|number} entityId - Entity ID
21
+ * @returns {Promise<Object>} Entity object
22
+ * @abstract
23
+ */
24
+ async findEntityById(entityId) {
25
+ throw new Error(
26
+ 'Method findEntityById must be implemented by subclass'
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Find all entities for a user
32
+ *
33
+ * @param {string|number} userId - User ID
34
+ * @returns {Promise<Array>} Array of entity objects
35
+ * @abstract
36
+ */
37
+ async findEntitiesByUserId(userId) {
38
+ throw new Error(
39
+ 'Method findEntitiesByUserId must be implemented by subclass'
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Find entities by IDs
45
+ *
46
+ * @param {Array<string|number>} entitiesIds - Array of entity IDs
47
+ * @returns {Promise<Array>} Array of entity objects
48
+ * @abstract
49
+ */
50
+ async findEntitiesByIds(entitiesIds) {
51
+ throw new Error(
52
+ 'Method findEntitiesByIds must be implemented by subclass'
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Find entities by user ID and module name
58
+ *
59
+ * @param {string|number} userId - User ID
60
+ * @param {string} moduleName - Module name
61
+ * @returns {Promise<Array>} Array of entity objects
62
+ * @abstract
63
+ */
64
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
65
+ throw new Error(
66
+ 'Method findEntitiesByUserIdAndModuleName must be implemented by subclass'
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Unset credential from entity
72
+ *
73
+ * @param {string|number} entityId - Entity ID
74
+ * @returns {Promise<Object>} Update result
75
+ * @abstract
76
+ */
77
+ async unsetCredential(entityId) {
78
+ throw new Error(
79
+ 'Method unsetCredential must be implemented by subclass'
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Find entity by filter
85
+ *
86
+ * @param {Object} filter - Filter criteria
87
+ * @returns {Promise<Object|null>} Entity object or null
88
+ * @abstract
89
+ */
90
+ async findEntity(filter) {
91
+ throw new Error('Method findEntity must be implemented by subclass');
92
+ }
93
+
94
+ /**
95
+ * Create a new entity
96
+ *
97
+ * @param {Object} entityData - Entity data
98
+ * @returns {Promise<Object>} Created entity object
99
+ * @abstract
100
+ */
101
+ async createEntity(entityData) {
102
+ throw new Error('Method createEntity must be implemented by subclass');
103
+ }
104
+
105
+ /**
106
+ * Update entity by ID
107
+ *
108
+ * @param {string|number} entityId - Entity ID to update
109
+ * @param {Object} updates - Fields to update
110
+ * @returns {Promise<Object>} Updated entity object
111
+ * @abstract
112
+ */
113
+ async updateEntity(entityId, updates) {
114
+ throw new Error('Method updateEntity must be implemented by subclass');
115
+ }
116
+
117
+ /**
118
+ * Delete entity by ID
119
+ *
120
+ * @param {string|number} entityId - Entity ID to delete
121
+ * @returns {Promise<Object>} Deletion result
122
+ * @abstract
123
+ */
124
+ async deleteEntity(entityId) {
125
+ throw new Error('Method deleteEntity must be implemented by subclass');
126
+ }
127
+ }
128
+
129
+ module.exports = { ModuleRepositoryInterface };
@@ -0,0 +1,408 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { ModuleRepositoryInterface } = require('./module-repository-interface');
3
+
4
+ /**
5
+ * MongoDB Module Repository Adapter
6
+ * Handles Entity model operations for external service entities with MongoDB
7
+ *
8
+ * MongoDB-specific characteristics:
9
+ * - Uses String IDs (ObjectId)
10
+ * - No ID conversion needed (IDs are already strings)
11
+ *
12
+ * Prisma Migration Notes:
13
+ * - Mongoose discriminator (__t) → moduleName field (module type: salesforce, hubspot, etc.)
14
+ */
15
+ class ModuleRepositoryMongo extends ModuleRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Convert any value to string (handles null/undefined)
23
+ * @private
24
+ * @param {*} value - Value to convert
25
+ * @returns {string|null|undefined} String value or null/undefined
26
+ */
27
+ _toString(value) {
28
+ if (value === null || value === undefined) return value;
29
+ return String(value);
30
+ }
31
+
32
+ /**
33
+ * Fetch credential by ID separately to ensure encryption extension processes it
34
+ * This fixes the bug where credentials fetched via include bypass decryption
35
+ * @private
36
+ * @param {string|null|undefined} credentialId - Credential ID
37
+ * @returns {Promise<Object|null>} Decrypted credential or null
38
+ */
39
+ async _fetchCredential(credentialId) {
40
+ if (!credentialId) return null;
41
+
42
+ const credential = await this.prisma.credential.findUnique({
43
+ where: { id: credentialId },
44
+ });
45
+
46
+ return credential;
47
+ }
48
+
49
+ /**
50
+ * Fetch multiple credentials in bulk separately to ensure decryption
51
+ * More efficient than fetching one-by-one for arrays of entities
52
+ * @private
53
+ * @param {Array<string>} credentialIds - Array of credential IDs
54
+ * @returns {Promise<Map<string, Object>>} Map of credentialId -> credential object
55
+ */
56
+ async _fetchCredentialsBulk(credentialIds) {
57
+ if (!credentialIds || credentialIds.length === 0) {
58
+ return new Map();
59
+ }
60
+
61
+ const validIds = credentialIds.filter(id => id !== null && id !== undefined);
62
+
63
+ if (validIds.length === 0) {
64
+ return new Map();
65
+ }
66
+
67
+ const credentials = await this.prisma.credential.findMany({
68
+ where: { id: { in: validIds } },
69
+ });
70
+
71
+ const credentialMap = new Map();
72
+ for (const credential of credentials) {
73
+ credentialMap.set(credential.id, credential);
74
+ }
75
+
76
+ return credentialMap;
77
+ }
78
+
79
+ /**
80
+ * Find entity by ID with credential
81
+ * Replaces: Entity.findById(entityId).populate('credential')
82
+ *
83
+ * @param {string} entityId - Entity ID
84
+ * @returns {Promise<Object>} Entity object with string IDs
85
+ * @throws {Error} If entity not found
86
+ */
87
+ async findEntityById(entityId) {
88
+ const entity = await this.prisma.entity.findUnique({
89
+ where: { id: entityId },
90
+ });
91
+
92
+ if (!entity) {
93
+ throw new Error(`Entity ${entityId} not found`);
94
+ }
95
+
96
+ const credential = await this._fetchCredential(entity.credentialId);
97
+
98
+ return {
99
+ id: entity.id,
100
+ credential,
101
+ userId: entity.userId,
102
+ name: entity.name,
103
+ externalId: entity.externalId,
104
+ moduleName: entity.moduleName,
105
+ ...(entity.data || {}),
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Find all entities for a user
111
+ * Replaces: Entity.find({ user: userId }).populate('credential')
112
+ *
113
+ * @param {string} userId - User ID
114
+ * @returns {Promise<Array>} Array of entity objects with string IDs
115
+ */
116
+ async findEntitiesByUserId(userId) {
117
+ const entities = await this.prisma.entity.findMany({
118
+ where: { userId },
119
+ });
120
+
121
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
122
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
123
+
124
+ return entities.map((e) => ({
125
+ id: e.id,
126
+ credential: credentialMap.get(e.credentialId) || null,
127
+ userId: e.userId,
128
+ name: e.name,
129
+ externalId: e.externalId,
130
+ moduleName: e.moduleName,
131
+ ...(e.data || {}),
132
+ }));
133
+ }
134
+
135
+ /**
136
+ * Find entities by array of IDs
137
+ * Replaces: Entity.find({ _id: { $in: entitiesIds } }).populate('credential')
138
+ *
139
+ * @param {Array<string>} entitiesIds - Array of entity IDs
140
+ * @returns {Promise<Array>} Array of entity objects with string IDs
141
+ */
142
+ async findEntitiesByIds(entitiesIds) {
143
+ const entities = await this.prisma.entity.findMany({
144
+ where: { id: { in: entitiesIds } },
145
+ });
146
+
147
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
148
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
149
+
150
+ return entities.map((e) => ({
151
+ id: e.id,
152
+ credential: credentialMap.get(e.credentialId) || null,
153
+ userId: e.userId,
154
+ name: e.name,
155
+ externalId: e.externalId,
156
+ moduleName: e.moduleName,
157
+ ...(e.data || {}),
158
+ }));
159
+ }
160
+
161
+ /**
162
+ * Find entities by user ID and module name
163
+ * Replaces: Entity.find({ user: userId, moduleName: moduleName }).populate('credential')
164
+ *
165
+ * @param {string} userId - User ID
166
+ * @param {string} moduleName - Module name
167
+ * @returns {Promise<Array>} Array of entity objects with string IDs
168
+ */
169
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
170
+ const entities = await this.prisma.entity.findMany({
171
+ where: {
172
+ userId,
173
+ moduleName,
174
+ },
175
+ });
176
+
177
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
178
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
179
+
180
+ return entities.map((e) => ({
181
+ id: e.id,
182
+ credential: credentialMap.get(e.credentialId) || null,
183
+ userId: e.userId,
184
+ name: e.name,
185
+ externalId: e.externalId,
186
+ moduleName: e.moduleName,
187
+ ...(e.data || {}),
188
+ }));
189
+ }
190
+
191
+ /**
192
+ * Remove credential reference from entity
193
+ * Replaces: Entity.updateOne({ _id: entityId }, { $unset: { credential: "" } })
194
+ *
195
+ * @param {string} entityId - Entity ID
196
+ * @returns {Promise<boolean>} Success indicator
197
+ */
198
+ async unsetCredential(entityId) {
199
+ await this.prisma.entity.update({
200
+ where: { id: entityId },
201
+ data: { credentialId: null },
202
+ });
203
+
204
+ return true;
205
+ }
206
+
207
+ /**
208
+ * Find entity by filter criteria
209
+ * Replaces: Entity.findOne(filter).populate('credential')
210
+ *
211
+ * @param {Object} filter - Filter criteria
212
+ * @returns {Promise<Object|null>} Entity object with string IDs or null
213
+ */
214
+ async findEntity(filter) {
215
+ const where = this._convertFilterToWhere(filter);
216
+ const entity = await this.prisma.entity.findFirst({
217
+ where,
218
+ });
219
+
220
+ if (!entity) {
221
+ return null;
222
+ }
223
+
224
+ const credential = await this._fetchCredential(entity.credentialId);
225
+
226
+ return {
227
+ id: entity.id,
228
+ credential,
229
+ userId: entity.userId,
230
+ name: entity.name,
231
+ externalId: entity.externalId,
232
+ moduleName: entity.moduleName,
233
+ ...(entity.data || {}),
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Create a new entity
239
+ * Replaces: Entity.create(entityData)
240
+ *
241
+ * @param {Object} entityData - Entity data
242
+ * @returns {Promise<Object>} Created entity object with string IDs
243
+ */
244
+ async createEntity(entityData) {
245
+ const {
246
+ user,
247
+ userId,
248
+ credential,
249
+ credentialId,
250
+ name,
251
+ moduleName,
252
+ externalId,
253
+ ...dynamicData
254
+ } = entityData;
255
+
256
+ const data = {
257
+ userId: userId || user,
258
+ credentialId: credentialId || credential,
259
+ name,
260
+ moduleName,
261
+ externalId,
262
+ data: dynamicData,
263
+ };
264
+
265
+ const entity = await this.prisma.entity.create({
266
+ data,
267
+ });
268
+
269
+ const credentialObj = await this._fetchCredential(entity.credentialId);
270
+
271
+ return {
272
+ id: entity.id,
273
+ credential: credentialObj,
274
+ userId: entity.userId,
275
+ name: entity.name,
276
+ externalId: entity.externalId,
277
+ moduleName: entity.moduleName,
278
+ ...(entity.data || {}),
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Update an entity by ID
284
+ * Replaces: Entity.findByIdAndUpdate(entityId, updates, { new: true })
285
+ *
286
+ * @param {string} entityId - Entity ID to update
287
+ * @param {Object} updates - Fields to update
288
+ * @returns {Promise<Object|null>} Updated entity object with string IDs or null if not found
289
+ */
290
+ async updateEntity(entityId, updates) {
291
+ const existing = await this.prisma.entity.findUnique({
292
+ where: { id: entityId },
293
+ });
294
+
295
+ if (!existing) {
296
+ return null;
297
+ }
298
+
299
+ const {
300
+ user,
301
+ userId,
302
+ credential,
303
+ credentialId,
304
+ name,
305
+ moduleName,
306
+ externalId,
307
+ ...dynamicData
308
+ } = updates;
309
+
310
+ const schemaUpdates = {};
311
+ if (user !== undefined || userId !== undefined) {
312
+ schemaUpdates.userId = userId || user;
313
+ }
314
+ if (credential !== undefined || credentialId !== undefined) {
315
+ schemaUpdates.credentialId = credentialId || credential;
316
+ }
317
+ if (name !== undefined) schemaUpdates.name = name;
318
+ if (moduleName !== undefined) schemaUpdates.moduleName = moduleName;
319
+ if (externalId !== undefined) schemaUpdates.externalId = externalId;
320
+
321
+ if (Object.keys(dynamicData).length > 0) {
322
+ schemaUpdates.data = { ...(existing.data || {}), ...dynamicData };
323
+ }
324
+
325
+ try {
326
+ const entity = await this.prisma.entity.update({
327
+ where: { id: entityId },
328
+ data: schemaUpdates,
329
+ });
330
+
331
+ const credentialObj = await this._fetchCredential(entity.credentialId);
332
+
333
+ return {
334
+ id: entity.id,
335
+ credential: credentialObj,
336
+ userId: entity.userId,
337
+ name: entity.name,
338
+ externalId: entity.externalId,
339
+ moduleName: entity.moduleName,
340
+ ...(entity.data || {}),
341
+ };
342
+ } catch (error) {
343
+ if (error.code === 'P2025') {
344
+ return null;
345
+ }
346
+ throw error;
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Delete an entity by ID
352
+ * Replaces: Entity.deleteOne({ _id: entityId })
353
+ *
354
+ * @param {string} entityId - Entity ID to delete
355
+ * @returns {Promise<boolean>} True if deleted successfully
356
+ */
357
+ async deleteEntity(entityId) {
358
+ try {
359
+ await this.prisma.entity.delete({
360
+ where: { id: entityId },
361
+ });
362
+ return true;
363
+ } catch (error) {
364
+ if (error.code === 'P2025') {
365
+ // Record not found
366
+ return false;
367
+ }
368
+ throw error;
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Convert Mongoose-style filter to Prisma where clause
374
+ * @private
375
+ * @param {Object} filter - Mongoose filter
376
+ * @returns {Object} Prisma where clause
377
+ */
378
+ _convertFilterToWhere(filter) {
379
+ const where = {};
380
+
381
+ // Handle _id field (Mongoose uses _id, Prisma uses id)
382
+ if (filter._id) {
383
+ where.id = filter._id;
384
+ }
385
+
386
+ // Handle user field (Mongoose uses user, Prisma uses userId)
387
+ if (filter.user) {
388
+ where.userId = filter.user;
389
+ }
390
+
391
+ // Handle credential field (Mongoose uses credential, Prisma uses credentialId)
392
+ if (filter.credential) {
393
+ where.credentialId = filter.credential;
394
+ }
395
+
396
+ // Copy other fields directly
397
+ if (filter.id) where.id = filter.id;
398
+ if (filter.userId) where.userId = filter.userId;
399
+ if (filter.credentialId) where.credentialId = filter.credentialId;
400
+ if (filter.name) where.name = filter.name;
401
+ if (filter.moduleName) where.moduleName = filter.moduleName;
402
+ if (filter.externalId) where.externalId = this._toString(filter.externalId);
403
+
404
+ return where;
405
+ }
406
+ }
407
+
408
+ module.exports = { ModuleRepositoryMongo };