@friggframework/core 2.0.0-next.41 → 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,129 @@
1
+ /**
2
+ * Module Repository Interface
3
+ * Abstract base class defining the contract for Entity (module) persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * Note: Currently, Entity model has identical structure across MongoDB and PostgreSQL,
11
+ * so ModuleRepository serves both. This interface exists for consistency and
12
+ * future-proofing if database-specific implementations become needed.
13
+ *
14
+ * @abstract
15
+ */
16
+ class ModuleRepositoryInterface {
17
+ /**
18
+ * Find entity by ID with credential
19
+ *
20
+ * @param {string|number} entityId - Entity ID
21
+ * @returns {Promise<Object>} Entity object
22
+ * @abstract
23
+ */
24
+ async findEntityById(entityId) {
25
+ throw new Error(
26
+ 'Method findEntityById must be implemented by subclass'
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Find all entities for a user
32
+ *
33
+ * @param {string|number} userId - User ID
34
+ * @returns {Promise<Array>} Array of entity objects
35
+ * @abstract
36
+ */
37
+ async findEntitiesByUserId(userId) {
38
+ throw new Error(
39
+ 'Method findEntitiesByUserId must be implemented by subclass'
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Find entities by IDs
45
+ *
46
+ * @param {Array<string|number>} entitiesIds - Array of entity IDs
47
+ * @returns {Promise<Array>} Array of entity objects
48
+ * @abstract
49
+ */
50
+ async findEntitiesByIds(entitiesIds) {
51
+ throw new Error(
52
+ 'Method findEntitiesByIds must be implemented by subclass'
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Find entities by user ID and module name
58
+ *
59
+ * @param {string|number} userId - User ID
60
+ * @param {string} moduleName - Module name
61
+ * @returns {Promise<Array>} Array of entity objects
62
+ * @abstract
63
+ */
64
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
65
+ throw new Error(
66
+ 'Method findEntitiesByUserIdAndModuleName must be implemented by subclass'
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Unset credential from entity
72
+ *
73
+ * @param {string|number} entityId - Entity ID
74
+ * @returns {Promise<Object>} Update result
75
+ * @abstract
76
+ */
77
+ async unsetCredential(entityId) {
78
+ throw new Error(
79
+ 'Method unsetCredential must be implemented by subclass'
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Find entity by filter
85
+ *
86
+ * @param {Object} filter - Filter criteria
87
+ * @returns {Promise<Object|null>} Entity object or null
88
+ * @abstract
89
+ */
90
+ async findEntity(filter) {
91
+ throw new Error('Method findEntity must be implemented by subclass');
92
+ }
93
+
94
+ /**
95
+ * Create a new entity
96
+ *
97
+ * @param {Object} entityData - Entity data
98
+ * @returns {Promise<Object>} Created entity object
99
+ * @abstract
100
+ */
101
+ async createEntity(entityData) {
102
+ throw new Error('Method createEntity must be implemented by subclass');
103
+ }
104
+
105
+ /**
106
+ * Update entity by ID
107
+ *
108
+ * @param {string|number} entityId - Entity ID to update
109
+ * @param {Object} updates - Fields to update
110
+ * @returns {Promise<Object>} Updated entity object
111
+ * @abstract
112
+ */
113
+ async updateEntity(entityId, updates) {
114
+ throw new Error('Method updateEntity must be implemented by subclass');
115
+ }
116
+
117
+ /**
118
+ * Delete entity by ID
119
+ *
120
+ * @param {string|number} entityId - Entity ID to delete
121
+ * @returns {Promise<Object>} Deletion result
122
+ * @abstract
123
+ */
124
+ async deleteEntity(entityId) {
125
+ throw new Error('Method deleteEntity must be implemented by subclass');
126
+ }
127
+ }
128
+
129
+ module.exports = { ModuleRepositoryInterface };
@@ -0,0 +1,386 @@
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
+ * - entity.__t (discriminator) → entity.subType
12
+ */
13
+ class ModuleRepositoryMongo extends ModuleRepositoryInterface {
14
+ constructor() {
15
+ super();
16
+ this.prisma = prisma;
17
+ }
18
+
19
+ /**
20
+ * Convert any value to string (handles null/undefined)
21
+ * @private
22
+ * @param {*} value - Value to convert
23
+ * @returns {string|null|undefined} String value or null/undefined
24
+ */
25
+ _toString(value) {
26
+ if (value === null || value === undefined) return value;
27
+ return String(value);
28
+ }
29
+
30
+ /**
31
+ * Fetch credential by ID separately to ensure encryption extension processes it
32
+ * This fixes the bug where credentials fetched via include bypass decryption
33
+ * @private
34
+ * @param {string|null|undefined} credentialId - Credential ID
35
+ * @returns {Promise<Object|null>} Decrypted credential or null
36
+ */
37
+ async _fetchCredential(credentialId) {
38
+ if (!credentialId) return null;
39
+
40
+ const credential = await this.prisma.credential.findUnique({
41
+ where: { id: credentialId },
42
+ });
43
+
44
+ return credential;
45
+ }
46
+
47
+ /**
48
+ * Fetch multiple credentials in bulk separately to ensure decryption
49
+ * More efficient than fetching one-by-one for arrays of entities
50
+ * @private
51
+ * @param {Array<string>} credentialIds - Array of credential IDs
52
+ * @returns {Promise<Map<string, Object>>} Map of credentialId -> credential object
53
+ */
54
+ async _fetchCredentialsBulk(credentialIds) {
55
+ if (!credentialIds || credentialIds.length === 0) {
56
+ return new Map();
57
+ }
58
+
59
+ const validIds = credentialIds.filter(id => id !== null && id !== undefined);
60
+
61
+ if (validIds.length === 0) {
62
+ return new Map();
63
+ }
64
+
65
+ const credentials = await this.prisma.credential.findMany({
66
+ where: { id: { in: validIds } },
67
+ });
68
+
69
+ const credentialMap = new Map();
70
+ for (const credential of credentials) {
71
+ credentialMap.set(credential.id, credential);
72
+ }
73
+
74
+ return credentialMap;
75
+ }
76
+
77
+ /**
78
+ * Find entity by ID with credential
79
+ * Replaces: Entity.findById(entityId).populate('credential')
80
+ *
81
+ * @param {string} entityId - Entity ID
82
+ * @returns {Promise<Object>} Entity object with string IDs
83
+ * @throws {Error} If entity not found
84
+ */
85
+ async findEntityById(entityId) {
86
+ const entity = await this.prisma.entity.findUnique({
87
+ where: { id: entityId },
88
+ });
89
+
90
+ if (!entity) {
91
+ throw new Error(`Entity ${entityId} not found`);
92
+ }
93
+
94
+ const credential = await this._fetchCredential(entity.credentialId);
95
+
96
+ return {
97
+ id: entity.id,
98
+ accountId: entity.accountId,
99
+ credential,
100
+ userId: entity.userId,
101
+ name: entity.name,
102
+ externalId: entity.externalId,
103
+ type: entity.subType,
104
+ moduleName: entity.moduleName,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Find all entities for a user
110
+ * Replaces: Entity.find({ user: userId }).populate('credential')
111
+ *
112
+ * @param {string} userId - User ID
113
+ * @returns {Promise<Array>} Array of entity objects with string IDs
114
+ */
115
+ async findEntitiesByUserId(userId) {
116
+ const entities = await this.prisma.entity.findMany({
117
+ where: { userId },
118
+ });
119
+
120
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
121
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
122
+
123
+ return entities.map((e) => ({
124
+ id: e.id,
125
+ accountId: e.accountId,
126
+ credential: credentialMap.get(e.credentialId) || null,
127
+ userId: e.userId,
128
+ name: e.name,
129
+ externalId: e.externalId,
130
+ type: e.subType,
131
+ moduleName: e.moduleName,
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
+ accountId: e.accountId,
153
+ credential: credentialMap.get(e.credentialId) || null,
154
+ userId: e.userId,
155
+ name: e.name,
156
+ externalId: e.externalId,
157
+ type: e.subType,
158
+ moduleName: e.moduleName,
159
+ }));
160
+ }
161
+
162
+ /**
163
+ * Find entities by user ID and module name
164
+ * Replaces: Entity.find({ user: userId, moduleName: moduleName }).populate('credential')
165
+ *
166
+ * @param {string} userId - User ID
167
+ * @param {string} moduleName - Module name
168
+ * @returns {Promise<Array>} Array of entity objects with string IDs
169
+ */
170
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
171
+ const entities = await this.prisma.entity.findMany({
172
+ where: {
173
+ userId,
174
+ moduleName,
175
+ },
176
+ });
177
+
178
+ const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
179
+ const credentialMap = await this._fetchCredentialsBulk(credentialIds);
180
+
181
+ return entities.map((e) => ({
182
+ id: e.id,
183
+ accountId: e.accountId,
184
+ credential: credentialMap.get(e.credentialId) || null,
185
+ userId: e.userId,
186
+ name: e.name,
187
+ externalId: e.externalId,
188
+ type: e.subType,
189
+ moduleName: e.moduleName,
190
+ }));
191
+ }
192
+
193
+ /**
194
+ * Remove credential reference from entity
195
+ * Replaces: Entity.updateOne({ _id: entityId }, { $unset: { credential: "" } })
196
+ *
197
+ * @param {string} entityId - Entity ID
198
+ * @returns {Promise<boolean>} Success indicator
199
+ */
200
+ async unsetCredential(entityId) {
201
+ await this.prisma.entity.update({
202
+ where: { id: entityId },
203
+ data: { credentialId: null },
204
+ });
205
+
206
+ return true;
207
+ }
208
+
209
+ /**
210
+ * Find entity by filter criteria
211
+ * Replaces: Entity.findOne(filter).populate('credential')
212
+ *
213
+ * @param {Object} filter - Filter criteria
214
+ * @returns {Promise<Object|null>} Entity object with string IDs or null
215
+ */
216
+ async findEntity(filter) {
217
+ const where = this._convertFilterToWhere(filter);
218
+ const entity = await this.prisma.entity.findFirst({
219
+ where,
220
+ });
221
+
222
+ if (!entity) {
223
+ return null;
224
+ }
225
+
226
+ const credential = await this._fetchCredential(entity.credentialId);
227
+
228
+ return {
229
+ id: entity.id,
230
+ accountId: entity.accountId,
231
+ credential,
232
+ userId: entity.userId,
233
+ name: entity.name,
234
+ externalId: entity.externalId,
235
+ type: entity.subType,
236
+ moduleName: entity.moduleName,
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Create a new entity
242
+ * Replaces: Entity.create(entityData)
243
+ *
244
+ * @param {Object} entityData - Entity data
245
+ * @returns {Promise<Object>} Created entity object with string IDs
246
+ */
247
+ async createEntity(entityData) {
248
+ const data = {
249
+ userId: entityData.user || entityData.userId,
250
+ credentialId: entityData.credential || entityData.credentialId,
251
+ subType: entityData.type || entityData.subType,
252
+ name: entityData.name,
253
+ moduleName: entityData.moduleName,
254
+ externalId: entityData.externalId,
255
+ accountId: entityData.accountId,
256
+ };
257
+
258
+ const entity = await this.prisma.entity.create({
259
+ data,
260
+ });
261
+
262
+ const credential = await this._fetchCredential(entity.credentialId);
263
+
264
+ return {
265
+ id: entity.id,
266
+ accountId: entity.accountId,
267
+ credential,
268
+ userId: entity.userId,
269
+ name: entity.name,
270
+ externalId: entity.externalId,
271
+ type: entity.subType,
272
+ moduleName: entity.moduleName,
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Update an entity by ID
278
+ * Replaces: Entity.findByIdAndUpdate(entityId, updates, { new: true })
279
+ *
280
+ * @param {string} entityId - Entity ID to update
281
+ * @param {Object} updates - Fields to update
282
+ * @returns {Promise<Object|null>} Updated entity object with string IDs or null if not found
283
+ */
284
+ async updateEntity(entityId, updates) {
285
+ const data = {};
286
+ if (updates.user !== undefined) data.userId = updates.user;
287
+ if (updates.userId !== undefined) data.userId = updates.userId;
288
+ if (updates.credential !== undefined)
289
+ data.credentialId = updates.credential;
290
+ if (updates.credentialId !== undefined)
291
+ data.credentialId = updates.credentialId;
292
+ if (updates.type !== undefined) data.subType = updates.type;
293
+ if (updates.subType !== undefined) data.subType = updates.subType;
294
+ if (updates.name !== undefined) data.name = updates.name;
295
+ if (updates.moduleName !== undefined)
296
+ data.moduleName = updates.moduleName;
297
+ if (updates.externalId !== undefined)
298
+ data.externalId = updates.externalId;
299
+ if (updates.accountId !== undefined) data.accountId = updates.accountId;
300
+
301
+ try {
302
+ const entity = await this.prisma.entity.update({
303
+ where: { id: entityId },
304
+ data,
305
+ });
306
+
307
+ const credential = await this._fetchCredential(entity.credentialId);
308
+
309
+ return {
310
+ id: entity.id,
311
+ accountId: entity.accountId,
312
+ credential,
313
+ userId: entity.userId,
314
+ name: entity.name,
315
+ externalId: entity.externalId,
316
+ type: entity.subType,
317
+ moduleName: entity.moduleName,
318
+ };
319
+ } catch (error) {
320
+ if (error.code === 'P2025') {
321
+ return null;
322
+ }
323
+ throw error;
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Delete an entity by ID
329
+ * Replaces: Entity.deleteOne({ _id: entityId })
330
+ *
331
+ * @param {string} entityId - Entity ID to delete
332
+ * @returns {Promise<boolean>} True if deleted successfully
333
+ */
334
+ async deleteEntity(entityId) {
335
+ try {
336
+ await this.prisma.entity.delete({
337
+ where: { id: entityId },
338
+ });
339
+ return true;
340
+ } catch (error) {
341
+ if (error.code === 'P2025') {
342
+ // Record not found
343
+ return false;
344
+ }
345
+ throw error;
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Convert Mongoose-style filter to Prisma where clause
351
+ * @private
352
+ * @param {Object} filter - Mongoose filter
353
+ * @returns {Object} Prisma where clause
354
+ */
355
+ _convertFilterToWhere(filter) {
356
+ const where = {};
357
+
358
+ // Handle _id field (Mongoose uses _id, Prisma uses id)
359
+ if (filter._id) {
360
+ where.id = filter._id;
361
+ }
362
+
363
+ // Handle user field (Mongoose uses user, Prisma uses userId)
364
+ if (filter.user) {
365
+ where.userId = filter.user;
366
+ }
367
+
368
+ // Handle credential field (Mongoose uses credential, Prisma uses credentialId)
369
+ if (filter.credential) {
370
+ where.credentialId = filter.credential;
371
+ }
372
+
373
+ // Copy other fields directly
374
+ if (filter.id) where.id = filter.id;
375
+ if (filter.userId) where.userId = filter.userId;
376
+ if (filter.credentialId) where.credentialId = filter.credentialId;
377
+ if (filter.name) where.name = filter.name;
378
+ if (filter.moduleName) where.moduleName = filter.moduleName;
379
+ if (filter.externalId) where.externalId = this._toString(filter.externalId);
380
+ if (filter.subType) where.subType = filter.subType;
381
+
382
+ return where;
383
+ }
384
+ }
385
+
386
+ module.exports = { ModuleRepositoryMongo };