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

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