@friggframework/core 2.0.0-next.53 → 2.0.0-next.55

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 (70) hide show
  1. package/CLAUDE.md +2 -1
  2. package/application/commands/credential-commands.js +1 -1
  3. package/application/commands/integration-commands.js +1 -1
  4. package/application/index.js +1 -1
  5. package/core/create-handler.js +12 -0
  6. package/credential/repositories/credential-repository-documentdb.js +304 -0
  7. package/credential/repositories/credential-repository-factory.js +8 -1
  8. package/credential/repositories/credential-repository-mongo.js +16 -54
  9. package/credential/repositories/credential-repository-postgres.js +14 -41
  10. package/credential/use-cases/get-credential-for-user.js +7 -3
  11. package/database/config.js +4 -4
  12. package/database/documentdb-encryption-service.js +330 -0
  13. package/database/documentdb-utils.js +136 -0
  14. package/database/encryption/README.md +50 -1
  15. package/database/encryption/documentdb-encryption-service.md +3270 -0
  16. package/database/encryption/encryption-schema-registry.js +46 -0
  17. package/database/prisma.js +7 -47
  18. package/database/repositories/health-check-repository-documentdb.js +134 -0
  19. package/database/repositories/health-check-repository-factory.js +6 -1
  20. package/database/repositories/health-check-repository-interface.js +29 -34
  21. package/database/repositories/health-check-repository-mongodb.js +1 -3
  22. package/database/use-cases/check-database-state-use-case.js +3 -3
  23. package/database/use-cases/run-database-migration-use-case.js +6 -4
  24. package/database/use-cases/trigger-database-migration-use-case.js +2 -2
  25. package/database/utils/mongodb-schema-init.js +5 -5
  26. package/database/utils/prisma-runner.js +15 -9
  27. package/errors/client-safe-error.js +26 -0
  28. package/errors/fetch-error.js +2 -1
  29. package/errors/index.js +2 -0
  30. package/generated/prisma-mongodb/edge.js +3 -3
  31. package/generated/prisma-mongodb/index.d.ts +10 -4
  32. package/generated/prisma-mongodb/index.js +3 -3
  33. package/generated/prisma-mongodb/package.json +1 -1
  34. package/generated/prisma-mongodb/schema.prisma +1 -3
  35. package/generated/prisma-mongodb/wasm.js +2 -2
  36. package/generated/prisma-postgresql/edge.js +3 -3
  37. package/generated/prisma-postgresql/index.d.ts +10 -4
  38. package/generated/prisma-postgresql/index.js +3 -3
  39. package/generated/prisma-postgresql/package.json +1 -1
  40. package/generated/prisma-postgresql/schema.prisma +1 -3
  41. package/generated/prisma-postgresql/wasm.js +2 -2
  42. package/handlers/routers/db-migration.js +2 -3
  43. package/handlers/routers/health.js +0 -3
  44. package/handlers/workers/db-migration.js +8 -8
  45. package/integrations/integration-router.js +6 -6
  46. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  47. package/integrations/repositories/integration-mapping-repository-factory.js +8 -1
  48. package/integrations/repositories/integration-repository-documentdb.js +210 -0
  49. package/integrations/repositories/integration-repository-factory.js +8 -1
  50. package/integrations/repositories/process-repository-documentdb.js +243 -0
  51. package/integrations/repositories/process-repository-factory.js +8 -1
  52. package/modules/repositories/module-repository-documentdb.js +307 -0
  53. package/modules/repositories/module-repository-factory.js +8 -1
  54. package/package.json +5 -5
  55. package/prisma-mongodb/schema.prisma +1 -3
  56. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +69 -0
  57. package/prisma-postgresql/schema.prisma +1 -3
  58. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  59. package/syncs/repositories/sync-repository-factory.js +6 -1
  60. package/token/repositories/token-repository-documentdb.js +137 -0
  61. package/token/repositories/token-repository-factory.js +8 -1
  62. package/token/repositories/token-repository-mongo.js +10 -3
  63. package/token/repositories/token-repository-postgres.js +10 -3
  64. package/user/repositories/user-repository-documentdb.js +432 -0
  65. package/user/repositories/user-repository-factory.js +6 -1
  66. package/user/repositories/user-repository-mongo.js +3 -2
  67. package/user/repositories/user-repository-postgres.js +3 -2
  68. package/user/use-cases/login-user.js +1 -1
  69. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  70. package/websocket/repositories/websocket-connection-repository-factory.js +8 -1
@@ -0,0 +1,280 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ toObjectId,
4
+ fromObjectId,
5
+ findMany,
6
+ findOne,
7
+ insertOne,
8
+ updateOne,
9
+ deleteOne,
10
+ deleteMany,
11
+ } = require('../../database/documentdb-utils');
12
+ const {
13
+ IntegrationMappingRepositoryInterface,
14
+ } = require('./integration-mapping-repository-interface');
15
+ const {
16
+ DocumentDBEncryptionService,
17
+ } = require('../../database/documentdb-encryption-service');
18
+ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositoryInterface {
19
+ constructor() {
20
+ super();
21
+ this.prisma = prisma;
22
+ this.encryptionService = new DocumentDBEncryptionService();
23
+ }
24
+
25
+ async findMappingBy(integrationId, sourceId) {
26
+ const filter = this._compositeFilter(integrationId, sourceId);
27
+ const doc = await findOne(this.prisma, 'IntegrationMapping', filter);
28
+ if (!doc) return null;
29
+
30
+ const decryptedMapping = await this.encryptionService.decryptFields(
31
+ 'IntegrationMapping',
32
+ doc
33
+ );
34
+ return this._mapMapping(decryptedMapping);
35
+ }
36
+
37
+ async upsertMapping(integrationId, sourceId, mapping) {
38
+ const filter = this._compositeFilter(integrationId, sourceId);
39
+ const existing = await findOne(
40
+ this.prisma,
41
+ 'IntegrationMapping',
42
+ filter
43
+ );
44
+ const now = new Date();
45
+
46
+ if (existing) {
47
+ const decryptedExisting =
48
+ await this.encryptionService.decryptFields(
49
+ 'IntegrationMapping',
50
+ existing
51
+ );
52
+
53
+ const updateDocument = {
54
+ mapping,
55
+ updatedAt: now,
56
+ };
57
+
58
+ const encryptedUpdate = await this.encryptionService.encryptFields(
59
+ 'IntegrationMapping',
60
+ { mapping: updateDocument.mapping }
61
+ );
62
+
63
+ await updateOne(
64
+ this.prisma,
65
+ 'IntegrationMapping',
66
+ { _id: existing._id },
67
+ {
68
+ $set: {
69
+ mapping: encryptedUpdate.mapping,
70
+ updatedAt: updateDocument.updatedAt,
71
+ },
72
+ }
73
+ );
74
+
75
+ const updated = await findOne(this.prisma, 'IntegrationMapping', {
76
+ _id: existing._id,
77
+ });
78
+ if (!updated) {
79
+ console.error(
80
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after update',
81
+ {
82
+ mappingId: fromObjectId(existing._id),
83
+ integrationId,
84
+ sourceId,
85
+ }
86
+ );
87
+ throw new Error(
88
+ 'Failed to update mapping: Document not found after update. ' +
89
+ 'This indicates a database consistency issue.'
90
+ );
91
+ }
92
+ const decryptedMapping = await this.encryptionService.decryptFields(
93
+ 'IntegrationMapping',
94
+ updated
95
+ );
96
+ return this._mapMapping(decryptedMapping);
97
+ }
98
+
99
+ const plainDocument = {
100
+ integrationId: integrationId,
101
+ sourceId:
102
+ sourceId === null || sourceId === undefined
103
+ ? null
104
+ : String(sourceId),
105
+ mapping,
106
+ createdAt: now,
107
+ updatedAt: now,
108
+ };
109
+
110
+ const encryptedDocument = await this.encryptionService.encryptFields(
111
+ 'IntegrationMapping',
112
+ plainDocument
113
+ );
114
+
115
+ const insertedId = await insertOne(
116
+ this.prisma,
117
+ 'IntegrationMapping',
118
+ encryptedDocument
119
+ );
120
+
121
+ const created = await findOne(this.prisma, 'IntegrationMapping', {
122
+ _id: insertedId,
123
+ });
124
+ if (!created) {
125
+ console.error(
126
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after insert',
127
+ {
128
+ insertedId: fromObjectId(insertedId),
129
+ integrationId,
130
+ sourceId,
131
+ }
132
+ );
133
+ throw new Error(
134
+ 'Failed to create mapping: Document not found after insert. ' +
135
+ 'This indicates a database consistency issue.'
136
+ );
137
+ }
138
+ const decryptedMapping = await this.encryptionService.decryptFields(
139
+ 'IntegrationMapping',
140
+ created
141
+ );
142
+ return this._mapMapping(decryptedMapping);
143
+ }
144
+
145
+ async findMappingsByIntegration(integrationId) {
146
+ const filter = {};
147
+ if (integrationId) filter.integrationId = integrationId;
148
+ const docs = await findMany(this.prisma, 'IntegrationMapping', filter);
149
+
150
+ const decryptedDocs = await Promise.all(
151
+ docs.map((doc) =>
152
+ this.encryptionService.decryptFields('IntegrationMapping', doc)
153
+ )
154
+ );
155
+
156
+ return decryptedDocs.map((doc) => this._mapMapping(doc));
157
+ }
158
+
159
+ async deleteMapping(integrationId, sourceId) {
160
+ const filter = this._compositeFilter(integrationId, sourceId);
161
+ const result = await deleteOne(
162
+ this.prisma,
163
+ 'IntegrationMapping',
164
+ filter
165
+ );
166
+ const deleted = result?.n ?? 0;
167
+ return { acknowledged: true, deletedCount: deleted };
168
+ }
169
+
170
+ async deleteMappingsByIntegration(integrationId) {
171
+ if (!integrationId) {
172
+ return { acknowledged: true, deletedCount: 0 };
173
+ }
174
+ const result = await deleteMany(this.prisma, 'IntegrationMapping', {
175
+ integrationId: integrationId,
176
+ });
177
+ const deleted = result?.n ?? 0;
178
+ return { acknowledged: true, deletedCount: deleted };
179
+ }
180
+
181
+ async findMappingById(id) {
182
+ const objectId = toObjectId(id);
183
+ if (!objectId) return null;
184
+ const doc = await findOne(this.prisma, 'IntegrationMapping', {
185
+ _id: objectId,
186
+ });
187
+ if (!doc) return null;
188
+
189
+ const decryptedMapping = await this.encryptionService.decryptFields(
190
+ 'IntegrationMapping',
191
+ doc
192
+ );
193
+ return this._mapMapping(decryptedMapping);
194
+ }
195
+
196
+ async updateMapping(id, updates) {
197
+ const objectId = toObjectId(id);
198
+ if (!objectId) return null;
199
+
200
+ const existing = await findOne(this.prisma, 'IntegrationMapping', {
201
+ _id: objectId,
202
+ });
203
+ if (!existing) return null;
204
+
205
+ const decryptedExisting = await this.encryptionService.decryptFields(
206
+ 'IntegrationMapping',
207
+ existing
208
+ );
209
+
210
+ const mergedMapping =
211
+ updates.mapping !== undefined
212
+ ? updates.mapping
213
+ : decryptedExisting.mapping;
214
+
215
+ const updateDocument = {
216
+ ...updates,
217
+ updatedAt: new Date(),
218
+ };
219
+
220
+ if (mergedMapping !== undefined) {
221
+ const encryptedUpdate = await this.encryptionService.encryptFields(
222
+ 'IntegrationMapping',
223
+ { mapping: mergedMapping }
224
+ );
225
+ updateDocument.mapping = encryptedUpdate.mapping;
226
+ }
227
+
228
+ await updateOne(
229
+ this.prisma,
230
+ 'IntegrationMapping',
231
+ { _id: objectId },
232
+ {
233
+ $set: updateDocument,
234
+ }
235
+ );
236
+
237
+ const updated = await findOne(this.prisma, 'IntegrationMapping', {
238
+ _id: objectId,
239
+ });
240
+ if (!updated) {
241
+ console.error(
242
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after update',
243
+ {
244
+ mappingId: fromObjectId(objectId),
245
+ }
246
+ );
247
+ throw new Error(
248
+ 'Failed to update mapping: Document not found after update. ' +
249
+ 'This indicates a database consistency issue.'
250
+ );
251
+ }
252
+ const decryptedMapping = await this.encryptionService.decryptFields(
253
+ 'IntegrationMapping',
254
+ updated
255
+ );
256
+ return this._mapMapping(decryptedMapping);
257
+ }
258
+
259
+ _compositeFilter(integrationId, sourceId) {
260
+ const filter = {};
261
+ if (integrationId) filter.integrationId = integrationId;
262
+ if (sourceId !== undefined) {
263
+ filter.sourceId = sourceId === null ? null : String(sourceId);
264
+ }
265
+ return filter;
266
+ }
267
+
268
+ _mapMapping(doc) {
269
+ return {
270
+ id: fromObjectId(doc?._id),
271
+ integrationId: doc?.integrationId ?? null,
272
+ sourceId: doc?.sourceId ?? null,
273
+ mapping: doc?.mapping ?? null,
274
+ createdAt: doc?.createdAt,
275
+ updatedAt: doc?.updatedAt,
276
+ };
277
+ }
278
+ }
279
+
280
+ module.exports = { IntegrationMappingRepositoryDocumentDB };
@@ -4,6 +4,9 @@ const {
4
4
  const {
5
5
  IntegrationMappingRepositoryPostgres,
6
6
  } = require('./integration-mapping-repository-postgres');
7
+ const {
8
+ IntegrationMappingRepositoryDocumentDB,
9
+ } = require('./integration-mapping-repository-documentdb');
7
10
  const config = require('../../database/config');
8
11
 
9
12
  /**
@@ -35,9 +38,12 @@ function createIntegrationMappingRepository() {
35
38
  case 'postgresql':
36
39
  return new IntegrationMappingRepositoryPostgres();
37
40
 
41
+ case 'documentdb':
42
+ return new IntegrationMappingRepositoryDocumentDB();
43
+
38
44
  default:
39
45
  throw new Error(
40
- `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
46
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
41
47
  );
42
48
  }
43
49
  }
@@ -47,4 +53,5 @@ module.exports = {
47
53
  // Export adapters for direct testing
48
54
  IntegrationMappingRepositoryMongo,
49
55
  IntegrationMappingRepositoryPostgres,
56
+ IntegrationMappingRepositoryDocumentDB,
50
57
  };
@@ -0,0 +1,210 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ toObjectId,
4
+ toObjectIdArray,
5
+ fromObjectId,
6
+ findMany,
7
+ findOne,
8
+ insertOne,
9
+ updateOne,
10
+ deleteOne,
11
+ } = require('../../database/documentdb-utils');
12
+ const {
13
+ IntegrationRepositoryInterface,
14
+ } = require('./integration-repository-interface');
15
+
16
+ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
17
+ constructor() {
18
+ super();
19
+ this.prisma = prisma;
20
+ }
21
+
22
+ async findIntegrationsByUserId(userId) {
23
+ const objectId = toObjectId(userId);
24
+ const filter = objectId ? { userId: objectId } : {};
25
+ const records = await findMany(this.prisma, 'Integration', filter);
26
+ return records.map((doc) => this._mapIntegration(doc));
27
+ }
28
+
29
+ async deleteIntegrationById(integrationId) {
30
+ const objectId = toObjectId(integrationId);
31
+ if (!objectId) return { acknowledged: true, deletedCount: 0 };
32
+ const result = await deleteOne(this.prisma, 'Integration', { _id: objectId });
33
+ const deleted = result?.n ?? 0;
34
+ return { acknowledged: true, deletedCount: deleted };
35
+ }
36
+
37
+ async findIntegrationByName(name) {
38
+ const doc = await findOne(this.prisma, 'Integration', { 'config.type': name });
39
+ if (!doc) {
40
+ throw new Error(`Integration with name ${name} not found`);
41
+ }
42
+ return this._mapIntegration(doc);
43
+ }
44
+
45
+ async findIntegrationById(id) {
46
+ const objectId = toObjectId(id);
47
+ if (!objectId) {
48
+ throw new Error(`Integration with id ${id} not found`);
49
+ }
50
+ const doc = await findOne(this.prisma, 'Integration', { _id: objectId });
51
+ if (!doc) {
52
+ throw new Error(`Integration with id ${id} not found`);
53
+ }
54
+ return this._mapIntegration(doc);
55
+ }
56
+
57
+ async updateIntegrationStatus(integrationId, status) {
58
+ const objectId = toObjectId(integrationId);
59
+ if (!objectId) return false;
60
+ await updateOne(
61
+ this.prisma,
62
+ 'Integration',
63
+ { _id: objectId },
64
+ {
65
+ $set: { status, updatedAt: new Date() },
66
+ }
67
+ );
68
+ return true;
69
+ }
70
+
71
+ async updateIntegrationMessages(
72
+ integrationId,
73
+ messageType,
74
+ messageTitle,
75
+ messageBody,
76
+ messageTimestamp
77
+ ) {
78
+ const objectId = toObjectId(integrationId);
79
+ if (!objectId) {
80
+ throw new Error(`Integration ${integrationId} not found`);
81
+ }
82
+ const existing = await findOne(this.prisma, 'Integration', { _id: objectId });
83
+ if (!existing) {
84
+ throw new Error(`Integration ${integrationId} not found`);
85
+ }
86
+ const messages = this._extractMessages(existing);
87
+ const list = Array.isArray(messages[messageType]) ? [...messages[messageType]] : [];
88
+ list.push({
89
+ title: messageTitle ?? null,
90
+ message: messageBody,
91
+ timestamp: messageTimestamp,
92
+ });
93
+ const updatedMessages = { ...messages, [messageType]: list };
94
+ await updateOne(
95
+ this.prisma,
96
+ 'Integration',
97
+ { _id: objectId },
98
+ {
99
+ $set: {
100
+ messages: updatedMessages,
101
+ errors: updatedMessages.errors ?? [],
102
+ warnings: updatedMessages.warnings ?? [],
103
+ info: updatedMessages.info ?? [],
104
+ logs: updatedMessages.logs ?? [],
105
+ updatedAt: new Date(),
106
+ },
107
+ }
108
+ );
109
+ return true;
110
+ }
111
+
112
+ async createIntegration(entities, userId, config) {
113
+ const now = new Date();
114
+ const document = {
115
+ userId: toObjectId(userId) || null,
116
+ config,
117
+ version: '0.0.0',
118
+ status: 'ENABLED',
119
+ entityIds: toObjectIdArray(entities),
120
+ messages: { errors: [], warnings: [], info: [], logs: [] },
121
+ errors: [],
122
+ warnings: [],
123
+ info: [],
124
+ logs: [],
125
+ createdAt: now,
126
+ updatedAt: now,
127
+ };
128
+ const insertedId = await insertOne(this.prisma, 'Integration', document);
129
+ const created = await findOne(this.prisma, 'Integration', { _id: insertedId });
130
+ if (!created) {
131
+ console.error('[IntegrationRepositoryDocumentDB] Integration not found after insert', {
132
+ insertedId: fromObjectId(insertedId),
133
+ userId,
134
+ config,
135
+ });
136
+ throw new Error(
137
+ 'Failed to create integration: Document not found after insert. ' +
138
+ 'This indicates a database consistency issue.'
139
+ );
140
+ }
141
+ return this._mapIntegration(created);
142
+ }
143
+
144
+ async findIntegrationByUserId(userId) {
145
+ const objectId = toObjectId(userId);
146
+ if (!objectId) return null;
147
+ const doc = await findOne(this.prisma, 'Integration', { userId: objectId });
148
+ return doc ? this._mapIntegration(doc) : null;
149
+ }
150
+
151
+ async updateIntegrationConfig(integrationId, config) {
152
+ if (config === null || config === undefined) {
153
+ throw new Error('Config parameter is required');
154
+ }
155
+ const objectId = toObjectId(integrationId);
156
+ if (!objectId) {
157
+ throw new Error(`Integration with id ${integrationId} not found`);
158
+ }
159
+ await updateOne(
160
+ this.prisma,
161
+ 'Integration',
162
+ { _id: objectId },
163
+ {
164
+ $set: {
165
+ config,
166
+ updatedAt: new Date(),
167
+ },
168
+ }
169
+ );
170
+ const updated = await findOne(this.prisma, 'Integration', { _id: objectId });
171
+ if (!updated) {
172
+ console.error('[IntegrationRepositoryDocumentDB] Integration not found after update', {
173
+ integrationId: fromObjectId(objectId),
174
+ config,
175
+ });
176
+ throw new Error(
177
+ 'Failed to update integration: Document not found after update. ' +
178
+ 'This indicates a database consistency issue.'
179
+ );
180
+ }
181
+ return this._mapIntegration(updated);
182
+ }
183
+
184
+ _mapIntegration(doc) {
185
+ const messages = this._extractMessages(doc);
186
+ return {
187
+ id: fromObjectId(doc?._id),
188
+ entitiesIds: (doc?.entityIds || []).map((value) => fromObjectId(value)),
189
+ userId: fromObjectId(doc?.userId),
190
+ config: doc?.config ?? null,
191
+ version: doc?.version ?? null,
192
+ status: doc?.status ?? null,
193
+ messages,
194
+ };
195
+ }
196
+
197
+ _extractMessages(doc) {
198
+ const base = doc?.messages && typeof doc.messages === 'object' ? doc.messages : {};
199
+ return {
200
+ errors: base.errors ?? doc?.errors ?? [],
201
+ warnings: base.warnings ?? doc?.warnings ?? [],
202
+ info: base.info ?? doc?.info ?? [],
203
+ logs: base.logs ?? doc?.logs ?? [],
204
+ };
205
+ }
206
+ }
207
+
208
+ module.exports = { IntegrationRepositoryDocumentDB };
209
+
210
+
@@ -1,5 +1,8 @@
1
1
  const { IntegrationRepositoryMongo } = require('./integration-repository-mongo');
2
2
  const { IntegrationRepositoryPostgres } = require('./integration-repository-postgres');
3
+ const {
4
+ IntegrationRepositoryDocumentDB,
5
+ } = require('./integration-repository-documentdb');
3
6
  const config = require('../../database/config');
4
7
 
5
8
  /**
@@ -29,9 +32,12 @@ function createIntegrationRepository() {
29
32
  case 'postgresql':
30
33
  return new IntegrationRepositoryPostgres();
31
34
 
35
+ case 'documentdb':
36
+ return new IntegrationRepositoryDocumentDB();
37
+
32
38
  default:
33
39
  throw new Error(
34
- `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
40
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
35
41
  );
36
42
  }
37
43
  }
@@ -41,4 +47,5 @@ module.exports = {
41
47
  // Export adapters for direct testing
42
48
  IntegrationRepositoryMongo,
43
49
  IntegrationRepositoryPostgres,
50
+ IntegrationRepositoryDocumentDB,
44
51
  };