@friggframework/core 2.0.0-next.40 → 2.0.0-next.42

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 (196) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +931 -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 +160 -0
  7. package/application/commands/integration-commands.test.js +123 -0
  8. package/application/commands/user-commands.js +213 -0
  9. package/application/index.js +69 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/create-handler.js +0 -6
  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 +301 -0
  15. package/credential/repositories/credential-repository-postgres.js +307 -0
  16. package/credential/repositories/credential-repository.js +307 -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/config.js +117 -0
  20. package/database/encryption/README.md +683 -0
  21. package/database/encryption/encryption-integration.test.js +553 -0
  22. package/database/encryption/encryption-schema-registry.js +141 -0
  23. package/database/encryption/encryption-schema-registry.test.js +392 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/field-encryption-service.test.js +525 -0
  26. package/database/encryption/logger.js +79 -0
  27. package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
  28. package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
  29. package/database/encryption/postgres-relation-decryption.test.js +245 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/encryption/prisma-encryption-extension.test.js +439 -0
  32. package/database/index.js +25 -12
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +162 -0
  35. package/database/repositories/health-check-repository-factory.js +38 -0
  36. package/database/repositories/health-check-repository-interface.js +86 -0
  37. package/database/repositories/health-check-repository-mongodb.js +72 -0
  38. package/database/repositories/health-check-repository-postgres.js +75 -0
  39. package/database/repositories/health-check-repository.js +108 -0
  40. package/database/use-cases/check-database-health-use-case.js +34 -0
  41. package/database/use-cases/check-encryption-health-use-case.js +82 -0
  42. package/database/use-cases/test-encryption-use-case.js +252 -0
  43. package/encrypt/Cryptor.js +20 -152
  44. package/encrypt/index.js +1 -2
  45. package/encrypt/test-encrypt.js +0 -2
  46. package/handlers/app-definition-loader.js +38 -0
  47. package/handlers/app-handler-helpers.js +0 -3
  48. package/handlers/auth-flow.integration.test.js +147 -0
  49. package/handlers/backend-utils.js +25 -45
  50. package/handlers/integration-event-dispatcher.js +54 -0
  51. package/handlers/integration-event-dispatcher.test.js +141 -0
  52. package/handlers/routers/HEALTHCHECK.md +103 -1
  53. package/handlers/routers/auth.js +3 -14
  54. package/handlers/routers/health.js +63 -424
  55. package/handlers/routers/health.test.js +7 -0
  56. package/handlers/routers/integration-defined-routers.js +8 -5
  57. package/handlers/routers/user.js +25 -5
  58. package/handlers/routers/websocket.js +5 -3
  59. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  60. package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
  61. package/handlers/workers/integration-defined-workers.js +6 -3
  62. package/index.js +45 -22
  63. package/integrations/index.js +12 -10
  64. package/integrations/integration-base.js +224 -53
  65. package/integrations/integration-router.js +386 -178
  66. package/integrations/options.js +1 -1
  67. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  68. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  69. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  70. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  71. package/integrations/repositories/integration-mapping-repository.js +156 -0
  72. package/integrations/repositories/integration-repository-factory.js +44 -0
  73. package/integrations/repositories/integration-repository-interface.js +115 -0
  74. package/integrations/repositories/integration-repository-mongo.js +271 -0
  75. package/integrations/repositories/integration-repository-postgres.js +319 -0
  76. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  77. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  78. package/integrations/tests/use-cases/create-integration.test.js +131 -0
  79. package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
  80. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
  81. package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
  82. package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
  83. package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
  84. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  85. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  86. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  87. package/integrations/tests/use-cases/update-integration.test.js +141 -0
  88. package/integrations/use-cases/create-integration.js +83 -0
  89. package/integrations/use-cases/delete-integration-for-user.js +73 -0
  90. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  91. package/integrations/use-cases/get-integration-for-user.js +78 -0
  92. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  93. package/integrations/use-cases/get-integration-instance.js +83 -0
  94. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  95. package/integrations/use-cases/get-possible-integrations.js +27 -0
  96. package/integrations/use-cases/index.js +11 -0
  97. package/integrations/use-cases/load-integration-context-full.test.js +329 -0
  98. package/integrations/use-cases/load-integration-context.js +71 -0
  99. package/integrations/use-cases/load-integration-context.test.js +114 -0
  100. package/integrations/use-cases/update-integration-messages.js +44 -0
  101. package/integrations/use-cases/update-integration-status.js +32 -0
  102. package/integrations/use-cases/update-integration.js +93 -0
  103. package/integrations/utils/map-integration-dto.js +36 -0
  104. package/jest-global-setup-noop.js +3 -0
  105. package/jest-global-teardown-noop.js +3 -0
  106. package/{module-plugin → modules}/entity.js +1 -0
  107. package/{module-plugin → modules}/index.js +0 -8
  108. package/modules/module-factory.js +56 -0
  109. package/modules/module-hydration.test.js +205 -0
  110. package/modules/module.js +221 -0
  111. package/modules/repositories/module-repository-factory.js +33 -0
  112. package/modules/repositories/module-repository-interface.js +129 -0
  113. package/modules/repositories/module-repository-mongo.js +386 -0
  114. package/modules/repositories/module-repository-postgres.js +437 -0
  115. package/modules/repositories/module-repository.js +327 -0
  116. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  117. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  118. package/modules/tests/doubles/test-module-factory.js +16 -0
  119. package/modules/tests/doubles/test-module-repository.js +39 -0
  120. package/modules/use-cases/get-entities-for-user.js +32 -0
  121. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  122. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  123. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  124. package/modules/use-cases/get-module.js +56 -0
  125. package/modules/use-cases/process-authorization-callback.js +121 -0
  126. package/modules/use-cases/refresh-entity-options.js +59 -0
  127. package/modules/use-cases/test-module-auth.js +55 -0
  128. package/modules/utils/map-module-dto.js +18 -0
  129. package/package.json +14 -6
  130. package/prisma-mongodb/schema.prisma +321 -0
  131. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  132. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  133. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  134. package/prisma-postgresql/schema.prisma +303 -0
  135. package/syncs/manager.js +468 -443
  136. package/syncs/repositories/sync-repository-factory.js +38 -0
  137. package/syncs/repositories/sync-repository-interface.js +109 -0
  138. package/syncs/repositories/sync-repository-mongo.js +239 -0
  139. package/syncs/repositories/sync-repository-postgres.js +319 -0
  140. package/syncs/sync.js +0 -1
  141. package/token/repositories/token-repository-factory.js +33 -0
  142. package/token/repositories/token-repository-interface.js +131 -0
  143. package/token/repositories/token-repository-mongo.js +212 -0
  144. package/token/repositories/token-repository-postgres.js +257 -0
  145. package/token/repositories/token-repository.js +219 -0
  146. package/types/integrations/index.d.ts +2 -6
  147. package/types/module-plugin/index.d.ts +5 -57
  148. package/types/syncs/index.d.ts +0 -2
  149. package/user/repositories/user-repository-factory.js +46 -0
  150. package/user/repositories/user-repository-interface.js +198 -0
  151. package/user/repositories/user-repository-mongo.js +250 -0
  152. package/user/repositories/user-repository-postgres.js +311 -0
  153. package/user/tests/doubles/test-user-repository.js +72 -0
  154. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  155. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  156. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  157. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  158. package/user/tests/use-cases/login-user.test.js +140 -0
  159. package/user/use-cases/create-individual-user.js +61 -0
  160. package/user/use-cases/create-organization-user.js +47 -0
  161. package/user/use-cases/create-token-for-user-id.js +30 -0
  162. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  163. package/user/use-cases/login-user.js +122 -0
  164. package/user/user.js +77 -0
  165. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  166. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  167. package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
  168. package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
  169. package/websocket/repositories/websocket-connection-repository.js +160 -0
  170. package/database/models/State.js +0 -9
  171. package/database/models/Token.js +0 -70
  172. package/database/mongo.js +0 -171
  173. package/encrypt/Cryptor.test.js +0 -32
  174. package/encrypt/encrypt.js +0 -104
  175. package/encrypt/encrypt.test.js +0 -1069
  176. package/handlers/routers/middleware/loadUser.js +0 -15
  177. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  178. package/integrations/create-frigg-backend.js +0 -31
  179. package/integrations/integration-factory.js +0 -251
  180. package/integrations/integration-mapping.js +0 -43
  181. package/integrations/integration-model.js +0 -46
  182. package/integrations/integration-user.js +0 -144
  183. package/integrations/test/integration-base.test.js +0 -144
  184. package/module-plugin/auther.js +0 -393
  185. package/module-plugin/credential.js +0 -22
  186. package/module-plugin/entity-manager.js +0 -70
  187. package/module-plugin/manager.js +0 -169
  188. package/module-plugin/module-factory.js +0 -61
  189. package/module-plugin/test/auther.test.js +0 -97
  190. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  191. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  192. /package/{module-plugin → modules}/requester/basic.js +0 -0
  193. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  194. /package/{module-plugin → modules}/requester/requester.js +0 -0
  195. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  196. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,437 @@
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
+ type: entity.subType,
140
+ moduleName: entity.moduleName,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Find all entities for a user
146
+ * Replaces: Entity.find({ user: userId }).populate('credential')
147
+ *
148
+ * @param {string} userId - User ID (string from application layer)
149
+ * @returns {Promise<Array>} Array of entity objects with string IDs
150
+ */
151
+ async findEntitiesByUserId(userId) {
152
+ const intUserId = this._convertId(userId);
153
+
154
+ const entities = await this.prisma.entity.findMany({
155
+ where: { userId: intUserId },
156
+ });
157
+
158
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
159
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
160
+
161
+ return entities.map((e) => ({
162
+ id: e.id.toString(),
163
+ accountId: e.accountId,
164
+ credential: credentialMap.get(e.credentialId) || null,
165
+ userId: e.userId?.toString(),
166
+ name: e.name,
167
+ externalId: e.externalId,
168
+ type: e.subType,
169
+ moduleName: e.moduleName,
170
+ }));
171
+ }
172
+
173
+ /**
174
+ * Find entities by array of IDs
175
+ * Replaces: Entity.find({ _id: { $in: entitiesIds } }).populate('credential')
176
+ *
177
+ * @param {Array<string>} entitiesIds - Array of entity IDs (strings from application layer)
178
+ * @returns {Promise<Array>} Array of entity objects with string IDs
179
+ */
180
+ async findEntitiesByIds(entitiesIds) {
181
+ const intIds = entitiesIds.map((id) => this._convertId(id));
182
+
183
+ const entities = await this.prisma.entity.findMany({
184
+ where: { id: { in: intIds } },
185
+ });
186
+
187
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
188
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
189
+
190
+ return entities.map((e) => ({
191
+ id: e.id.toString(),
192
+ accountId: e.accountId,
193
+ credential: credentialMap.get(e.credentialId) || null,
194
+ userId: e.userId?.toString(),
195
+ name: e.name,
196
+ externalId: e.externalId,
197
+ type: e.subType,
198
+ moduleName: e.moduleName,
199
+ }));
200
+ }
201
+
202
+ /**
203
+ * Find entities by user ID and module name
204
+ * Replaces: Entity.find({ user: userId, moduleName: moduleName }).populate('credential')
205
+ *
206
+ * @param {string} userId - User ID (string from application layer)
207
+ * @param {string} moduleName - Module name
208
+ * @returns {Promise<Array>} Array of entity objects with string IDs
209
+ */
210
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
211
+ const intUserId = this._convertId(userId);
212
+
213
+ const entities = await this.prisma.entity.findMany({
214
+ where: {
215
+ userId: intUserId,
216
+ moduleName,
217
+ },
218
+ });
219
+
220
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
221
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
222
+
223
+ return entities.map((e) => ({
224
+ id: e.id.toString(),
225
+ accountId: e.accountId,
226
+ credential: credentialMap.get(e.credentialId) || null,
227
+ userId: e.userId?.toString(),
228
+ name: e.name,
229
+ externalId: e.externalId,
230
+ type: e.subType,
231
+ moduleName: e.moduleName,
232
+ }));
233
+ }
234
+
235
+ /**
236
+ * Remove credential reference from entity
237
+ * Replaces: Entity.updateOne({ _id: entityId }, { $unset: { credential: "" } })
238
+ *
239
+ * @param {string} entityId - Entity ID (string from application layer)
240
+ * @returns {Promise<boolean>} Success indicator
241
+ */
242
+ async unsetCredential(entityId) {
243
+ const intId = this._convertId(entityId);
244
+ await this.prisma.entity.update({
245
+ where: { id: intId },
246
+ data: { credentialId: null },
247
+ });
248
+
249
+ return true;
250
+ }
251
+
252
+ /**
253
+ * Find entity by filter criteria
254
+ *
255
+ * @param {Object} filter - Filter criteria
256
+ * @returns {Promise<Object|null>} Entity object with string IDs or null
257
+ */
258
+ async findEntity(filter) {
259
+ const where = this._convertFilterToWhere(filter);
260
+
261
+ const entity = await this.prisma.entity.findFirst({
262
+ where,
263
+ });
264
+
265
+ if (!entity) {
266
+ return null;
267
+ }
268
+
269
+ const credential = await this._fetchCredential(entity.credentialId);
270
+
271
+ return {
272
+ id: entity.id.toString(),
273
+ accountId: entity.accountId,
274
+ credential,
275
+ userId: entity.userId?.toString(),
276
+ name: entity.name,
277
+ externalId: entity.externalId,
278
+ type: entity.subType,
279
+ moduleName: entity.moduleName,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Create a new entity
285
+ * Replaces: Entity.create(entityData)
286
+ *
287
+ * @param {Object} entityData - Entity data (with string IDs from application layer)
288
+ * @returns {Promise<Object>} Created entity object with string IDs
289
+ */
290
+ async createEntity(entityData) {
291
+ const data = {
292
+ userId: this._convertId(entityData.user || entityData.userId),
293
+ credentialId: this._convertId(
294
+ entityData.credential || entityData.credentialId
295
+ ),
296
+ subType: entityData.type || entityData.subType,
297
+ name: entityData.name,
298
+ moduleName: entityData.moduleName,
299
+ externalId: entityData.externalId,
300
+ accountId: entityData.accountId,
301
+ };
302
+
303
+ const entity = await this.prisma.entity.create({
304
+ data,
305
+ });
306
+
307
+ const credential = await this._fetchCredential(entity.credentialId);
308
+
309
+ return {
310
+ id: entity.id.toString(),
311
+ accountId: entity.accountId,
312
+ credential,
313
+ userId: entity.userId?.toString(),
314
+ name: entity.name,
315
+ externalId: entity.externalId,
316
+ type: entity.subType,
317
+ moduleName: entity.moduleName,
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Update an entity by ID
323
+ * Replaces: Entity.findByIdAndUpdate(entityId, updates, { new: true })
324
+ *
325
+ * @param {string} entityId - Entity ID to update (string from application layer)
326
+ * @param {Object} updates - Fields to update (with string IDs from application layer)
327
+ * @returns {Promise<Object|null>} Updated entity object with string IDs or null if not found
328
+ */
329
+ async updateEntity(entityId, updates) {
330
+ const data = {};
331
+ if (updates.user !== undefined)
332
+ data.userId = this._convertId(updates.user);
333
+ if (updates.userId !== undefined)
334
+ data.userId = this._convertId(updates.userId);
335
+ if (updates.credential !== undefined)
336
+ data.credentialId = this._convertId(updates.credential);
337
+ if (updates.credentialId !== undefined)
338
+ data.credentialId = this._convertId(updates.credentialId);
339
+ if (updates.type !== undefined) data.subType = updates.type;
340
+ if (updates.subType !== undefined) data.subType = updates.subType;
341
+ if (updates.name !== undefined) data.name = updates.name;
342
+ if (updates.moduleName !== undefined)
343
+ data.moduleName = updates.moduleName;
344
+ if (updates.externalId !== undefined)
345
+ data.externalId = updates.externalId;
346
+ if (updates.accountId !== undefined) data.accountId = updates.accountId;
347
+
348
+ try {
349
+ const intId = this._convertId(entityId);
350
+
351
+ const entity = await this.prisma.entity.update({
352
+ where: { id: intId },
353
+ data,
354
+ });
355
+
356
+ const credential = await this._fetchCredential(entity.credentialId);
357
+
358
+ return {
359
+ id: entity.id.toString(),
360
+ accountId: entity.accountId,
361
+ credential,
362
+ userId: entity.userId?.toString(),
363
+ name: entity.name,
364
+ externalId: entity.externalId,
365
+ type: entity.subType,
366
+ moduleName: entity.moduleName,
367
+ };
368
+ } catch (error) {
369
+ if (error.code === 'P2025') {
370
+ return null;
371
+ }
372
+ throw error;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Delete an entity by ID
378
+ * Replaces: Entity.deleteOne({ _id: entityId })
379
+ *
380
+ * @param {string} entityId - Entity ID to delete (string from application layer)
381
+ * @returns {Promise<boolean>} True if deleted successfully
382
+ */
383
+ async deleteEntity(entityId) {
384
+ try {
385
+ const intId = this._convertId(entityId);
386
+ await this.prisma.entity.delete({
387
+ where: { id: intId },
388
+ });
389
+ return true;
390
+ } catch (error) {
391
+ if (error.code === 'P2025') {
392
+ // Record not found
393
+ return false;
394
+ }
395
+ throw error;
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Convert Mongoose-style filter to Prisma where clause (converting IDs to Int)
401
+ * @private
402
+ * @param {Object} filter - Mongoose filter (with string IDs from application layer)
403
+ * @returns {Object} Prisma where clause (with Int IDs for PostgreSQL)
404
+ */
405
+ _convertFilterToWhere(filter) {
406
+ const where = {};
407
+
408
+ // Handle _id field (Mongoose uses _id, Prisma uses id)
409
+ if (filter._id) {
410
+ where.id = this._convertId(filter._id);
411
+ }
412
+
413
+ // Handle user field (Mongoose uses user, Prisma uses userId)
414
+ if (filter.user) {
415
+ where.userId = this._convertId(filter.user);
416
+ }
417
+
418
+ // Handle credential field (Mongoose uses credential, Prisma uses credentialId)
419
+ if (filter.credential) {
420
+ where.credentialId = this._convertId(filter.credential);
421
+ }
422
+
423
+ // Copy other fields directly (converting IDs)
424
+ if (filter.id) where.id = this._convertId(filter.id);
425
+ if (filter.userId) where.userId = this._convertId(filter.userId);
426
+ if (filter.credentialId)
427
+ where.credentialId = this._convertId(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
+ if (filter.subType) where.subType = filter.subType;
432
+
433
+ return where;
434
+ }
435
+ }
436
+
437
+ module.exports = { ModuleRepositoryPostgres };