@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.
- package/CLAUDE.md +2 -1
- package/application/commands/credential-commands.js +1 -1
- package/application/commands/integration-commands.js +1 -1
- package/application/index.js +1 -1
- package/core/create-handler.js +12 -0
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +8 -1
- package/credential/repositories/credential-repository-mongo.js +16 -54
- package/credential/repositories/credential-repository-postgres.js +14 -41
- package/credential/use-cases/get-credential-for-user.js +7 -3
- package/database/config.js +4 -4
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +50 -1
- package/database/encryption/documentdb-encryption-service.md +3270 -0
- package/database/encryption/encryption-schema-registry.js +46 -0
- package/database/prisma.js +7 -47
- package/database/repositories/health-check-repository-documentdb.js +134 -0
- package/database/repositories/health-check-repository-factory.js +6 -1
- package/database/repositories/health-check-repository-interface.js +29 -34
- package/database/repositories/health-check-repository-mongodb.js +1 -3
- package/database/use-cases/check-database-state-use-case.js +3 -3
- package/database/use-cases/run-database-migration-use-case.js +6 -4
- package/database/use-cases/trigger-database-migration-use-case.js +2 -2
- package/database/utils/mongodb-schema-init.js +5 -5
- package/database/utils/prisma-runner.js +15 -9
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/edge.js +3 -3
- package/generated/prisma-mongodb/index.d.ts +10 -4
- package/generated/prisma-mongodb/index.js +3 -3
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +1 -3
- package/generated/prisma-mongodb/wasm.js +2 -2
- package/generated/prisma-postgresql/edge.js +3 -3
- package/generated/prisma-postgresql/index.d.ts +10 -4
- package/generated/prisma-postgresql/index.js +3 -3
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +1 -3
- package/generated/prisma-postgresql/wasm.js +2 -2
- package/handlers/routers/db-migration.js +2 -3
- package/handlers/routers/health.js +0 -3
- package/handlers/workers/db-migration.js +8 -8
- package/integrations/integration-router.js +6 -6
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +8 -1
- package/integrations/repositories/integration-repository-documentdb.js +210 -0
- package/integrations/repositories/integration-repository-factory.js +8 -1
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +8 -1
- package/modules/repositories/module-repository-documentdb.js +307 -0
- package/modules/repositories/module-repository-factory.js +8 -1
- package/package.json +5 -5
- package/prisma-mongodb/schema.prisma +1 -3
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +69 -0
- package/prisma-postgresql/schema.prisma +1 -3
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +6 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +8 -1
- package/token/repositories/token-repository-mongo.js +10 -3
- package/token/repositories/token-repository-postgres.js +10 -3
- package/user/repositories/user-repository-documentdb.js +432 -0
- package/user/repositories/user-repository-factory.js +6 -1
- package/user/repositories/user-repository-mongo.js +3 -2
- package/user/repositories/user-repository-postgres.js +3 -2
- package/user/use-cases/login-user.js +1 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +8 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const { ObjectId } = require('mongodb');
|
|
2
|
+
|
|
3
|
+
function toObjectId(value) {
|
|
4
|
+
if (value === null || value === undefined || value === '') return undefined;
|
|
5
|
+
if (value instanceof ObjectId) return value;
|
|
6
|
+
if (typeof value === 'object' && value.$oid) return new ObjectId(value.$oid);
|
|
7
|
+
if (typeof value === 'string') return ObjectId.isValid(value) ? new ObjectId(value) : undefined;
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function toObjectIdArray(values) {
|
|
12
|
+
if (!Array.isArray(values)) return [];
|
|
13
|
+
return values.map(toObjectId).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fromObjectId(value) {
|
|
17
|
+
if (value instanceof ObjectId) return value.toHexString();
|
|
18
|
+
if (typeof value === 'object' && value !== null && value.$oid) return value.$oid;
|
|
19
|
+
if (typeof value === 'string') return value;
|
|
20
|
+
return value === undefined || value === null ? value : String(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function findMany(client, collection, filter = {}, options = {}) {
|
|
24
|
+
const command = { find: collection, filter };
|
|
25
|
+
if (options.projection) command.projection = options.projection;
|
|
26
|
+
if (options.sort) command.sort = options.sort;
|
|
27
|
+
if (options.limit) command.limit = options.limit;
|
|
28
|
+
const result = await client.$runCommandRaw(command);
|
|
29
|
+
return result?.cursor?.firstBatch || [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function findOne(client, collection, filter = {}, options = {}) {
|
|
33
|
+
const docs = await findMany(client, collection, filter, { ...options, limit: 1 });
|
|
34
|
+
return docs[0] || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function insertOne(client, collection, document) {
|
|
38
|
+
// Generate ObjectId if not present (MongoDB raw insert doesn't return insertedIds)
|
|
39
|
+
const _id = document._id || new ObjectId();
|
|
40
|
+
const docWithId = { ...document, _id };
|
|
41
|
+
|
|
42
|
+
const result = await client.$runCommandRaw({
|
|
43
|
+
insert: collection,
|
|
44
|
+
documents: [docWithId],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Validate insert succeeded
|
|
48
|
+
if (result.ok !== 1) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Insert command failed for collection '${collection}': ${JSON.stringify(result)}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check for write errors (duplicate keys, validation errors, etc.)
|
|
55
|
+
if (result.writeErrors && result.writeErrors.length > 0) {
|
|
56
|
+
const error = result.writeErrors[0];
|
|
57
|
+
const errorMsg = `Insert failed in '${collection}': ${error.errmsg} (code: ${error.code})`;
|
|
58
|
+
|
|
59
|
+
// Provide helpful context for common errors
|
|
60
|
+
if (error.code === 11000) {
|
|
61
|
+
throw new Error(`${errorMsg} - Duplicate key violation`);
|
|
62
|
+
}
|
|
63
|
+
throw new Error(errorMsg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Verify exactly one document was inserted
|
|
67
|
+
if (result.n !== 1) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Expected to insert 1 document into '${collection}', but inserted ${result.n}. ` +
|
|
70
|
+
`Result: ${JSON.stringify(result)}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return _id;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function updateOne(client, collection, filter, update, options = {}) {
|
|
78
|
+
const updates = [{
|
|
79
|
+
q: filter,
|
|
80
|
+
u: update,
|
|
81
|
+
upsert: Boolean(options.upsert),
|
|
82
|
+
}];
|
|
83
|
+
if (options.arrayFilters) updates[0].arrayFilters = options.arrayFilters;
|
|
84
|
+
const result = await client.$runCommandRaw({
|
|
85
|
+
update: collection,
|
|
86
|
+
updates,
|
|
87
|
+
});
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function deleteOne(client, collection, filter) {
|
|
92
|
+
return client.$runCommandRaw({
|
|
93
|
+
delete: collection,
|
|
94
|
+
deletes: [
|
|
95
|
+
{
|
|
96
|
+
q: filter,
|
|
97
|
+
limit: 1,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function deleteMany(client, collection, filter) {
|
|
104
|
+
return client.$runCommandRaw({
|
|
105
|
+
delete: collection,
|
|
106
|
+
deletes: [
|
|
107
|
+
{
|
|
108
|
+
q: filter,
|
|
109
|
+
limit: 0,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function aggregate(client, collection, pipeline) {
|
|
116
|
+
const result = await client.$runCommandRaw({
|
|
117
|
+
aggregate: collection,
|
|
118
|
+
pipeline,
|
|
119
|
+
cursor: {},
|
|
120
|
+
});
|
|
121
|
+
return result?.cursor?.firstBatch || [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
toObjectId,
|
|
126
|
+
toObjectIdArray,
|
|
127
|
+
fromObjectId,
|
|
128
|
+
findMany,
|
|
129
|
+
findOne,
|
|
130
|
+
insertOne,
|
|
131
|
+
updateOne,
|
|
132
|
+
deleteOne,
|
|
133
|
+
deleteMany,
|
|
134
|
+
aggregate,
|
|
135
|
+
};
|
|
136
|
+
|
|
@@ -130,7 +130,6 @@ const ENCRYPTION_SCHEMA = {
|
|
|
130
130
|
fields: [
|
|
131
131
|
'data.access_token', // OAuth access token
|
|
132
132
|
'data.refresh_token', // OAuth refresh token
|
|
133
|
-
'data.domain', // Service domain
|
|
134
133
|
'data.id_token', // OpenID Connect ID token
|
|
135
134
|
],
|
|
136
135
|
},
|
|
@@ -267,6 +266,14 @@ const CORE_ENCRYPTION_SCHEMA = {
|
|
|
267
266
|
- Integration-specific sensitive data (use custom schema instead)
|
|
268
267
|
- Temporary/experimental encryption (use custom schema instead)
|
|
269
268
|
|
|
269
|
+
#### After Adding Encrypted Fields
|
|
270
|
+
|
|
271
|
+
After adding fields to `encryption-schema-registry.js`:
|
|
272
|
+
|
|
273
|
+
1. **For MongoDB/PostgreSQL**: No code changes needed (automatic via Prisma Extension)
|
|
274
|
+
2. **For DocumentDB**: Encryption is automatic via DocumentDBEncryptionService
|
|
275
|
+
(service reads from same registry)
|
|
276
|
+
|
|
270
277
|
## How It Works
|
|
271
278
|
|
|
272
279
|
### Write Operation (Create/Update)
|
|
@@ -403,6 +410,48 @@ return entities.map((e) => ({
|
|
|
403
410
|
|
|
404
411
|
See `modules/repositories/module-repository-postgres.js` and `module-repository-mongo.js` for complete implementation examples using `_fetchCredential()` and `_fetchCredentialsBulk()` helper methods.
|
|
405
412
|
|
|
413
|
+
## DocumentDB Encryption
|
|
414
|
+
|
|
415
|
+
### Why DocumentDB Needs Manual Encryption
|
|
416
|
+
|
|
417
|
+
DocumentDB repositories use `$runCommandRaw()` for MongoDB protocol compatibility, which bypasses Prisma Client Extensions. This means the automatic encryption extension does not apply.
|
|
418
|
+
|
|
419
|
+
### DocumentDBEncryptionService
|
|
420
|
+
|
|
421
|
+
For DocumentDB repositories, use `DocumentDBEncryptionService` to manually encrypt/decrypt documents before/after database operations.
|
|
422
|
+
|
|
423
|
+
#### Usage Example
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
const { DocumentDBEncryptionService } = require('../documentdb-encryption-service');
|
|
427
|
+
const { insertOne, findOne } = require('../documentdb-utils');
|
|
428
|
+
|
|
429
|
+
class MyRepositoryDocumentDB {
|
|
430
|
+
constructor() {
|
|
431
|
+
this.encryptionService = new DocumentDBEncryptionService();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async create(data) {
|
|
435
|
+
// Encrypt before write
|
|
436
|
+
const encrypted = await this.encryptionService.encryptFields('ModelName', data);
|
|
437
|
+
const id = await insertOne(this.prisma, 'CollectionName', encrypted);
|
|
438
|
+
|
|
439
|
+
// Decrypt after read
|
|
440
|
+
const doc = await findOne(this.prisma, 'CollectionName', { _id: id });
|
|
441
|
+
const decrypted = await this.encryptionService.decryptFields('ModelName', doc);
|
|
442
|
+
|
|
443
|
+
return decrypted;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
#### Configuration
|
|
449
|
+
|
|
450
|
+
Uses the same environment variables and Cryptor as the Prisma Extension:
|
|
451
|
+
- `STAGE`: Bypasses encryption for dev/test/local
|
|
452
|
+
- `KMS_KEY_ARN`: AWS KMS encryption (production)
|
|
453
|
+
- `AES_KEY_ID` + `AES_KEY`: AES encryption (fallback)
|
|
454
|
+
|
|
406
455
|
## Usage Examples
|
|
407
456
|
|
|
408
457
|
### Repository Code (No Changes Needed!)
|