@friggframework/core 2.0.0-next.9 → 2.0.0-next.91

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 (309) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +271 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +84 -6
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +401 -0
  30. package/database/encryption/field-encryption-service.js +254 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +230 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  69. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  72. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  73. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  74. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  75. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  76. package/generated/prisma-mongodb/runtime/library.js +146 -0
  77. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  78. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  79. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  80. package/generated/prisma-mongodb/schema.prisma +368 -0
  81. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  82. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  84. package/generated/prisma-mongodb/wasm.js +342 -0
  85. package/generated/prisma-postgresql/client.d.ts +1 -0
  86. package/generated/prisma-postgresql/client.js +4 -0
  87. package/generated/prisma-postgresql/default.d.ts +1 -0
  88. package/generated/prisma-postgresql/default.js +4 -0
  89. package/generated/prisma-postgresql/edge.d.ts +1 -0
  90. package/generated/prisma-postgresql/edge.js +357 -0
  91. package/generated/prisma-postgresql/index-browser.js +339 -0
  92. package/generated/prisma-postgresql/index.d.ts +25135 -0
  93. package/generated/prisma-postgresql/index.js +382 -0
  94. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  95. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  96. package/generated/prisma-postgresql/package.json +183 -0
  97. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  98. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  99. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  100. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  101. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  102. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  103. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  104. package/generated/prisma-postgresql/runtime/library.js +146 -0
  105. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  106. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  107. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  108. package/generated/prisma-postgresql/schema.prisma +351 -0
  109. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  110. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  111. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  112. package/generated/prisma-postgresql/wasm.js +364 -0
  113. package/handlers/WEBHOOKS.md +653 -0
  114. package/handlers/app-definition-loader.js +38 -0
  115. package/handlers/app-handler-helpers.js +57 -0
  116. package/handlers/backend-utils.js +297 -0
  117. package/handlers/database-migration-handler.js +227 -0
  118. package/handlers/integration-event-dispatcher.js +54 -0
  119. package/handlers/routers/HEALTHCHECK.md +342 -0
  120. package/handlers/routers/auth.js +15 -0
  121. package/handlers/routers/db-migration.handler.js +29 -0
  122. package/handlers/routers/db-migration.js +326 -0
  123. package/handlers/routers/health.js +518 -0
  124. package/handlers/routers/integration-defined-routers.js +117 -0
  125. package/handlers/routers/integration-webhook-routers.js +67 -0
  126. package/handlers/routers/user.js +63 -0
  127. package/handlers/routers/websocket.js +57 -0
  128. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  129. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  130. package/handlers/workers/db-migration.js +352 -0
  131. package/handlers/workers/dlq-processor.js +63 -0
  132. package/handlers/workers/integration-defined-workers.js +30 -0
  133. package/index.js +82 -46
  134. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  135. package/infrastructure/scheduler/index.js +33 -0
  136. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  137. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  138. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  139. package/integrations/EXTENSIONS.md +240 -0
  140. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  141. package/integrations/extension.js +254 -0
  142. package/integrations/index.js +20 -10
  143. package/integrations/integration-base.js +487 -55
  144. package/integrations/integration-router.js +396 -179
  145. package/integrations/options.js +1 -1
  146. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  147. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  148. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  149. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  150. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  151. package/integrations/repositories/integration-mapping-repository.js +156 -0
  152. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  153. package/integrations/repositories/integration-repository-factory.js +51 -0
  154. package/integrations/repositories/integration-repository-interface.js +144 -0
  155. package/integrations/repositories/integration-repository-mongo.js +330 -0
  156. package/integrations/repositories/integration-repository-postgres.js +385 -0
  157. package/integrations/repositories/process-repository-documentdb.js +311 -0
  158. package/integrations/repositories/process-repository-factory.js +53 -0
  159. package/integrations/repositories/process-repository-interface.js +136 -0
  160. package/integrations/repositories/process-repository-mongo.js +262 -0
  161. package/integrations/repositories/process-repository-postgres.js +380 -0
  162. package/integrations/repositories/process-update-ops-shared.js +112 -0
  163. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  164. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  165. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  166. package/integrations/use-cases/create-integration.js +83 -0
  167. package/integrations/use-cases/create-process.js +128 -0
  168. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  169. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  170. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -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/list-integrations-by-entity-external-id.js +46 -0
  179. package/integrations/use-cases/load-integration-context.js +71 -0
  180. package/integrations/use-cases/update-integration-messages.js +44 -0
  181. package/integrations/use-cases/update-integration-status.js +32 -0
  182. package/integrations/use-cases/update-integration.js +92 -0
  183. package/integrations/use-cases/update-process-metrics.js +214 -0
  184. package/integrations/use-cases/update-process-state.js +158 -0
  185. package/integrations/utils/map-integration-dto.js +37 -0
  186. package/jest-global-setup-noop.js +3 -0
  187. package/jest-global-teardown-noop.js +3 -0
  188. package/logs/logger.js +0 -4
  189. package/{module-plugin → modules}/index.js +0 -10
  190. package/modules/module-factory.js +56 -0
  191. package/modules/module.js +274 -0
  192. package/modules/repositories/module-repository-documentdb.js +350 -0
  193. package/modules/repositories/module-repository-factory.js +40 -0
  194. package/modules/repositories/module-repository-interface.js +145 -0
  195. package/modules/repositories/module-repository-mongo.js +436 -0
  196. package/modules/repositories/module-repository-postgres.js +481 -0
  197. package/modules/repositories/module-repository.js +369 -0
  198. package/modules/requester/api-key.js +52 -0
  199. package/modules/requester/oauth-2.js +396 -0
  200. package/modules/requester/requester.js +280 -0
  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 +34 -0
  209. package/modules/use-cases/get-module.js +74 -0
  210. package/modules/use-cases/process-authorization-callback.js +243 -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 +368 -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/20260422120000_add_entity_data_column/migration.sql +10 -0
  221. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  222. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  223. package/prisma-postgresql/schema.prisma +351 -0
  224. package/queues/queuer-util.js +103 -21
  225. package/syncs/manager.js +468 -443
  226. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  227. package/syncs/repositories/sync-repository-factory.js +43 -0
  228. package/syncs/repositories/sync-repository-interface.js +109 -0
  229. package/syncs/repositories/sync-repository-mongo.js +239 -0
  230. package/syncs/repositories/sync-repository-postgres.js +319 -0
  231. package/syncs/sync.js +0 -1
  232. package/token/repositories/token-repository-documentdb.js +137 -0
  233. package/token/repositories/token-repository-factory.js +40 -0
  234. package/token/repositories/token-repository-interface.js +131 -0
  235. package/token/repositories/token-repository-mongo.js +219 -0
  236. package/token/repositories/token-repository-postgres.js +264 -0
  237. package/token/repositories/token-repository.js +219 -0
  238. package/types/associations/index.d.ts +0 -17
  239. package/types/core/index.d.ts +12 -4
  240. package/types/database/index.d.ts +10 -2
  241. package/types/encrypt/index.d.ts +5 -3
  242. package/types/integrations/index.d.ts +3 -8
  243. package/types/module-plugin/index.d.ts +17 -69
  244. package/types/syncs/index.d.ts +0 -17
  245. package/user/repositories/user-repository-documentdb.js +441 -0
  246. package/user/repositories/user-repository-factory.js +52 -0
  247. package/user/repositories/user-repository-interface.js +201 -0
  248. package/user/repositories/user-repository-mongo.js +308 -0
  249. package/user/repositories/user-repository-postgres.js +360 -0
  250. package/user/tests/doubles/test-user-repository.js +72 -0
  251. package/user/use-cases/authenticate-user.js +127 -0
  252. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  253. package/user/use-cases/create-individual-user.js +61 -0
  254. package/user/use-cases/create-organization-user.js +47 -0
  255. package/user/use-cases/create-token-for-user-id.js +30 -0
  256. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  257. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  258. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  259. package/user/use-cases/login-user.js +122 -0
  260. package/user/user.js +125 -0
  261. package/utils/backend-path.js +38 -0
  262. package/utils/index.js +6 -0
  263. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  264. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  265. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  266. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  267. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  268. package/websocket/repositories/websocket-connection-repository.js +161 -0
  269. package/assertions/is-equal.js +0 -17
  270. package/associations/model.js +0 -54
  271. package/database/models/IndividualUser.js +0 -76
  272. package/database/models/OrganizationUser.js +0 -29
  273. package/database/models/State.js +0 -9
  274. package/database/models/Token.js +0 -70
  275. package/database/models/UserModel.js +0 -7
  276. package/database/models/WebsocketConnection.js +0 -49
  277. package/database/mongo.js +0 -45
  278. package/database/mongoose.js +0 -5
  279. package/encrypt/Cryptor.test.js +0 -32
  280. package/encrypt/encrypt.js +0 -132
  281. package/encrypt/encrypt.test.js +0 -1069
  282. package/encrypt/test-encrypt.js +0 -107
  283. package/errors/base-error.test.js +0 -32
  284. package/errors/fetch-error.test.js +0 -79
  285. package/errors/halt-error.test.js +0 -11
  286. package/errors/validation-errors.test.js +0 -120
  287. package/integrations/create-frigg-backend.js +0 -31
  288. package/integrations/integration-factory.js +0 -251
  289. package/integrations/integration-mapping.js +0 -43
  290. package/integrations/integration-model.js +0 -46
  291. package/integrations/integration-user.js +0 -144
  292. package/integrations/test/integration-base.test.js +0 -144
  293. package/lambda/TimeoutCatcher.test.js +0 -68
  294. package/logs/logger.test.js +0 -76
  295. package/module-plugin/auther.js +0 -393
  296. package/module-plugin/credential.js +0 -22
  297. package/module-plugin/entity-manager.js +0 -70
  298. package/module-plugin/entity.js +0 -46
  299. package/module-plugin/manager.js +0 -169
  300. package/module-plugin/module-factory.js +0 -61
  301. package/module-plugin/requester/api-key.js +0 -36
  302. package/module-plugin/requester/oauth-2.js +0 -219
  303. package/module-plugin/requester/requester.js +0 -165
  304. package/module-plugin/requester/requester.test.js +0 -28
  305. package/module-plugin/test/auther.test.js +0 -97
  306. package/syncs/model.js +0 -62
  307. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  308. /package/{module-plugin → modules}/requester/basic.js +0 -0
  309. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,145 @@
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
+ * Find all entities matching a filter.
96
+ *
97
+ * Symmetric with findEntity but returns the full match set. Use this when
98
+ * a single externalId may correspond to more than one Entity row
99
+ * (e.g. shared upstream account across tenants) and the caller needs to
100
+ * detect/handle the multi-match case explicitly.
101
+ *
102
+ * @param {Object} filter - Filter criteria
103
+ * @returns {Promise<Array>} Array of entity objects (empty if no match)
104
+ * @abstract
105
+ */
106
+ async findEntities(filter) {
107
+ throw new Error('Method findEntities must be implemented by subclass');
108
+ }
109
+
110
+ /**
111
+ * Create a new entity
112
+ *
113
+ * @param {Object} entityData - Entity data
114
+ * @returns {Promise<Object>} Created entity object
115
+ * @abstract
116
+ */
117
+ async createEntity(entityData) {
118
+ throw new Error('Method createEntity must be implemented by subclass');
119
+ }
120
+
121
+ /**
122
+ * Update entity by ID
123
+ *
124
+ * @param {string|number} entityId - Entity ID to update
125
+ * @param {Object} updates - Fields to update
126
+ * @returns {Promise<Object>} Updated entity object
127
+ * @abstract
128
+ */
129
+ async updateEntity(entityId, updates) {
130
+ throw new Error('Method updateEntity must be implemented by subclass');
131
+ }
132
+
133
+ /**
134
+ * Delete entity by ID
135
+ *
136
+ * @param {string|number} entityId - Entity ID to delete
137
+ * @returns {Promise<Object>} Deletion result
138
+ * @abstract
139
+ */
140
+ async deleteEntity(entityId) {
141
+ throw new Error('Method deleteEntity must be implemented by subclass');
142
+ }
143
+ }
144
+
145
+ module.exports = { ModuleRepositoryInterface };
@@ -0,0 +1,436 @@
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
+ * Find all entities matching a filter.
239
+ *
240
+ * @param {Object} filter - Filter criteria
241
+ * @returns {Promise<Array>} Array of entity objects with string IDs (empty if no match)
242
+ */
243
+ async findEntities(filter) {
244
+ const where = this._convertFilterToWhere(filter);
245
+ const entities = await this.prisma.entity.findMany({
246
+ where,
247
+ });
248
+
249
+ const credentialIds = entities
250
+ .map((e) => e.credentialId)
251
+ .filter(Boolean);
252
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
253
+
254
+ return entities.map((e) => ({
255
+ id: e.id,
256
+ credential: credentialMap.get(e.credentialId) || null,
257
+ userId: e.userId,
258
+ name: e.name,
259
+ externalId: e.externalId,
260
+ moduleName: e.moduleName,
261
+ ...(e.data || {}),
262
+ }));
263
+ }
264
+
265
+ /**
266
+ * Create a new entity
267
+ * Replaces: Entity.create(entityData)
268
+ *
269
+ * @param {Object} entityData - Entity data
270
+ * @returns {Promise<Object>} Created entity object with string IDs
271
+ */
272
+ async createEntity(entityData) {
273
+ const {
274
+ user,
275
+ userId,
276
+ credential,
277
+ credentialId,
278
+ name,
279
+ moduleName,
280
+ externalId,
281
+ ...dynamicData
282
+ } = entityData;
283
+
284
+ const data = {
285
+ userId: userId || user,
286
+ credentialId: credentialId || credential,
287
+ name,
288
+ moduleName,
289
+ externalId,
290
+ data: dynamicData,
291
+ };
292
+
293
+ const entity = await this.prisma.entity.create({
294
+ data,
295
+ });
296
+
297
+ const credentialObj = await this._fetchCredential(entity.credentialId);
298
+
299
+ return {
300
+ id: entity.id,
301
+ credential: credentialObj,
302
+ userId: entity.userId,
303
+ name: entity.name,
304
+ externalId: entity.externalId,
305
+ moduleName: entity.moduleName,
306
+ ...(entity.data || {}),
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Update an entity by ID
312
+ * Replaces: Entity.findByIdAndUpdate(entityId, updates, { new: true })
313
+ *
314
+ * @param {string} entityId - Entity ID to update
315
+ * @param {Object} updates - Fields to update
316
+ * @returns {Promise<Object|null>} Updated entity object with string IDs or null if not found
317
+ */
318
+ async updateEntity(entityId, updates) {
319
+ const existing = await this.prisma.entity.findUnique({
320
+ where: { id: entityId },
321
+ });
322
+
323
+ if (!existing) {
324
+ return null;
325
+ }
326
+
327
+ const {
328
+ user,
329
+ userId,
330
+ credential,
331
+ credentialId,
332
+ name,
333
+ moduleName,
334
+ externalId,
335
+ ...dynamicData
336
+ } = updates;
337
+
338
+ const schemaUpdates = {};
339
+ if (user !== undefined || userId !== undefined) {
340
+ schemaUpdates.userId = userId || user;
341
+ }
342
+ if (credential !== undefined || credentialId !== undefined) {
343
+ schemaUpdates.credentialId = credentialId || credential;
344
+ }
345
+ if (name !== undefined) schemaUpdates.name = name;
346
+ if (moduleName !== undefined) schemaUpdates.moduleName = moduleName;
347
+ if (externalId !== undefined) schemaUpdates.externalId = externalId;
348
+
349
+ if (Object.keys(dynamicData).length > 0) {
350
+ schemaUpdates.data = { ...(existing.data || {}), ...dynamicData };
351
+ }
352
+
353
+ try {
354
+ const entity = await this.prisma.entity.update({
355
+ where: { id: entityId },
356
+ data: schemaUpdates,
357
+ });
358
+
359
+ const credentialObj = await this._fetchCredential(entity.credentialId);
360
+
361
+ return {
362
+ id: entity.id,
363
+ credential: credentialObj,
364
+ userId: entity.userId,
365
+ name: entity.name,
366
+ externalId: entity.externalId,
367
+ moduleName: entity.moduleName,
368
+ ...(entity.data || {}),
369
+ };
370
+ } catch (error) {
371
+ if (error.code === 'P2025') {
372
+ return null;
373
+ }
374
+ throw error;
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Delete an entity by ID
380
+ * Replaces: Entity.deleteOne({ _id: entityId })
381
+ *
382
+ * @param {string} entityId - Entity ID to delete
383
+ * @returns {Promise<boolean>} True if deleted successfully
384
+ */
385
+ async deleteEntity(entityId) {
386
+ try {
387
+ await this.prisma.entity.delete({
388
+ where: { id: entityId },
389
+ });
390
+ return true;
391
+ } catch (error) {
392
+ if (error.code === 'P2025') {
393
+ // Record not found
394
+ return false;
395
+ }
396
+ throw error;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Convert Mongoose-style filter to Prisma where clause
402
+ * @private
403
+ * @param {Object} filter - Mongoose filter
404
+ * @returns {Object} Prisma where clause
405
+ */
406
+ _convertFilterToWhere(filter) {
407
+ const where = {};
408
+
409
+ // Handle _id field (Mongoose uses _id, Prisma uses id)
410
+ if (filter._id) {
411
+ where.id = filter._id;
412
+ }
413
+
414
+ // Handle user field (Mongoose uses user, Prisma uses userId)
415
+ if (filter.user) {
416
+ where.userId = filter.user;
417
+ }
418
+
419
+ // Handle credential field (Mongoose uses credential, Prisma uses credentialId)
420
+ if (filter.credential) {
421
+ where.credentialId = filter.credential;
422
+ }
423
+
424
+ // Copy other fields directly
425
+ if (filter.id) where.id = filter.id;
426
+ if (filter.userId) where.userId = filter.userId;
427
+ if (filter.credentialId) where.credentialId = filter.credentialId;
428
+ if (filter.name) where.name = filter.name;
429
+ if (filter.moduleName) where.moduleName = filter.moduleName;
430
+ if (filter.externalId) where.externalId = this._toString(filter.externalId);
431
+
432
+ return where;
433
+ }
434
+ }
435
+
436
+ module.exports = { ModuleRepositoryMongo };