@friggframework/core 2.0.0--canary.711.f8b6445.0 → 2.0.0--canary.710.91495fa.0
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 -2
- package/application/commands/README.md +17 -11
- package/database/encryption/README.md +3 -3
- package/database/encryption/encryption-schema-registry.js +27 -7
- package/jest-setup.js +13 -2
- package/jest-teardown.js +5 -2
- package/jest.config.js +4 -0
- package/jest.db.config.js +23 -0
- package/package.json +6 -5
- package/queues/queuer-util.js +6 -5
package/CLAUDE.md
CHANGED
|
@@ -224,7 +224,7 @@ packages/core/
|
|
|
224
224
|
(`remove-entities-from-integration.js`) refuses to leave a module with no
|
|
225
225
|
entity (`LAST_ENTITY_OF_MODULE`, 409). Re-adding an attached entity or
|
|
226
226
|
removing a detached one changes nothing; a detached entity is not deleted.
|
|
227
|
-
`integration-repository-entities-parity.test.js` runs every adapter against
|
|
227
|
+
`integration-repository-entities-parity.db.test.js` runs every adapter against
|
|
228
228
|
real databases when `INTEGRATION_ENTITIES_PARITY_MONGO_URL` /
|
|
229
229
|
`INTEGRATION_ENTITIES_PARITY_POSTGRES_URL` are set. The DocumentDB adapter
|
|
230
230
|
writes only `Integration.entityIds` (as its `createIntegration` does), not
|
|
@@ -266,7 +266,7 @@ packages/core/
|
|
|
266
266
|
mapping keys to leave out of the rows; never write such rows back. Path
|
|
267
267
|
segments must match `^[A-Za-z_][A-Za-z0-9_]*$`. The PostgreSQL, MongoDB and
|
|
268
268
|
DocumentDB adapters give the same pages and totals;
|
|
269
|
-
`integration-mapping-repository-query-parity.test.js` checks that against
|
|
269
|
+
`integration-mapping-repository-query-parity.db.test.js` checks that against
|
|
270
270
|
real databases when `QUERY_MAPPINGS_PARITY_MONGO_URL` /
|
|
271
271
|
`QUERY_MAPPINGS_PARITY_POSTGRES_URL` are set. Two orderings still differ:
|
|
272
272
|
strings compare by the database collation on PostgreSQL and by code point
|
|
@@ -67,9 +67,10 @@ class MyIntegration extends IntegrationBase {
|
|
|
67
67
|
async hydrateFromExternalUser(externalUserId) {
|
|
68
68
|
// Find integration context by external entity ID
|
|
69
69
|
const result =
|
|
70
|
-
await this.commands.findIntegrationContextByExternalEntityId(
|
|
71
|
-
externalUserId
|
|
72
|
-
|
|
70
|
+
await this.commands.findIntegrationContextByExternalEntityId({
|
|
71
|
+
externalId: externalUserId,
|
|
72
|
+
type: 'my-integration', // integration config.type to match
|
|
73
|
+
});
|
|
73
74
|
|
|
74
75
|
if (result.error) {
|
|
75
76
|
return { error: result.error };
|
|
@@ -231,9 +232,10 @@ Manage integration records and load full integration contexts.
|
|
|
231
232
|
```javascript
|
|
232
233
|
// Find integration context by external entity ID
|
|
233
234
|
// Returns { context, error } where context includes record + hydrated modules
|
|
234
|
-
const result = await commands.findIntegrationContextByExternalEntityId(
|
|
235
|
-
'external-user-or-workspace-id'
|
|
236
|
-
|
|
235
|
+
const result = await commands.findIntegrationContextByExternalEntityId({
|
|
236
|
+
externalId: 'external-user-or-workspace-id',
|
|
237
|
+
type: 'my-integration', // integration config.type to match
|
|
238
|
+
});
|
|
237
239
|
|
|
238
240
|
if (!result.error) {
|
|
239
241
|
integration.setIntegrationRecord(result.context);
|
|
@@ -299,7 +301,10 @@ try {
|
|
|
299
301
|
For integration context operations, errors are returned in the result:
|
|
300
302
|
|
|
301
303
|
```javascript
|
|
302
|
-
const result = await commands.findIntegrationContextByExternalEntityId(
|
|
304
|
+
const result = await commands.findIntegrationContextByExternalEntityId({
|
|
305
|
+
externalId: userId,
|
|
306
|
+
type: 'my-integration',
|
|
307
|
+
});
|
|
303
308
|
|
|
304
309
|
if (result.error) {
|
|
305
310
|
return { error: result.error };
|
|
@@ -349,9 +354,10 @@ const result =
|
|
|
349
354
|
const { createFriggCommands } = require('@friggframework/core');
|
|
350
355
|
|
|
351
356
|
this.commands = createFriggCommands({ integrationClass: MyIntegration });
|
|
352
|
-
const result = await this.commands.findIntegrationContextByExternalEntityId(
|
|
353
|
-
userId
|
|
354
|
-
|
|
357
|
+
const result = await this.commands.findIntegrationContextByExternalEntityId({
|
|
358
|
+
externalId: userId,
|
|
359
|
+
type: 'my-integration',
|
|
360
|
+
});
|
|
355
361
|
```
|
|
356
362
|
|
|
357
363
|
## Best Practices
|
|
@@ -433,7 +439,7 @@ if (!user) {
|
|
|
433
439
|
| | `findEntitiesByIds(ids)` | Find multiple by IDs |
|
|
434
440
|
| | `updateEntity(id, updates)` | Update entity properties |
|
|
435
441
|
| | `deleteEntity(id)` | Delete entity |
|
|
436
|
-
| **Integration** | `findIntegrationContextByExternalEntityId(externalId)`
|
|
442
|
+
| **Integration** | `findIntegrationContextByExternalEntityId({ externalId, type })` | Load integration + modules by external ID |
|
|
437
443
|
| | `loadIntegrationContextById(integrationId)` | Load integration + modules by ID |
|
|
438
444
|
|
|
439
445
|
## Support
|
|
@@ -508,9 +508,9 @@ return entities.map((e) => ({
|
|
|
508
508
|
|
|
509
509
|
**Verified:**
|
|
510
510
|
|
|
511
|
-
- ✅ `postgres-relation-decryption.test.js` - Proves the bug exists
|
|
512
|
-
- ✅ `postgres-decryption-fix-verification.test.js` - Verifies separate queries work
|
|
513
|
-
- ✅ `mongo-decryption-fix-verification.test.js` - Verifies fix for MongoDB
|
|
511
|
+
- ✅ `postgres-relation-decryption.db.test.js` - Proves the bug exists
|
|
512
|
+
- ✅ `postgres-decryption-fix-verification.db.test.js` - Verifies separate queries work
|
|
513
|
+
- ✅ `mongo-decryption-fix-verification.db.test.js` - Verifies fix for MongoDB
|
|
514
514
|
|
|
515
515
|
**Implementation Examples:**
|
|
516
516
|
|
|
@@ -174,26 +174,46 @@ function loadModuleEncryptionSchemas(integrations) {
|
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
const { getModulesDefinitionFromIntegrationClasses } = require('
|
|
177
|
+
const { getModulesDefinitionFromIntegrationClasses } = require('../../integrations/utils/map-integration-dto');
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
const
|
|
179
|
+
// Integrations without modules contribute no credential fields.
|
|
180
|
+
const integrationsWithModules = integrations.filter(
|
|
181
|
+
(integration) => integration?.Definition?.modules
|
|
182
|
+
);
|
|
183
|
+
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(
|
|
184
|
+
integrationsWithModules
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Fields the core schema already encrypts (e.g. data.api_key) are
|
|
188
|
+
// covered; registering them again would fail validation and drop every
|
|
189
|
+
// other module field with them.
|
|
190
|
+
const coreCredentialFields = CORE_ENCRYPTION_SCHEMA.Credential.fields;
|
|
191
|
+
const credentialFields = extractCredentialFieldsFromModules(
|
|
192
|
+
moduleDefinitions
|
|
193
|
+
).filter((field) => !coreCredentialFields.includes(field));
|
|
181
194
|
|
|
182
195
|
if (credentialFields.length === 0) {
|
|
183
196
|
return;
|
|
184
197
|
}
|
|
185
198
|
|
|
186
|
-
|
|
199
|
+
// Merge with whatever custom schema the app already registered rather
|
|
200
|
+
// than replacing it (registerCustomSchema overwrites).
|
|
201
|
+
const existingCredentialFields = customSchema.Credential?.fields || [];
|
|
202
|
+
const mergedSchema = {
|
|
203
|
+
...customSchema,
|
|
187
204
|
Credential: {
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
...customSchema.Credential,
|
|
206
|
+
fields: [
|
|
207
|
+
...new Set([...existingCredentialFields, ...credentialFields]),
|
|
208
|
+
],
|
|
209
|
+
},
|
|
190
210
|
};
|
|
191
211
|
|
|
192
212
|
logger.info(
|
|
193
213
|
`Registering module-level encryption for ${credentialFields.length} credential fields`
|
|
194
214
|
);
|
|
195
215
|
|
|
196
|
-
registerCustomSchema(
|
|
216
|
+
registerCustomSchema(mergedSchema);
|
|
197
217
|
}
|
|
198
218
|
|
|
199
219
|
/**
|
package/jest-setup.js
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Core's unit suites must not need a database, so this global setup only
|
|
2
|
+
// sets the test environment. It deliberately does not start an in-memory
|
|
3
|
+
// MongoDB (as @friggframework/test's globalSetup does): nothing in the default
|
|
4
|
+
// suite talks to a live server, and a binary that failed to start used to stop
|
|
5
|
+
// every core suite from running. Suites that need a real database are
|
|
6
|
+
// `*.db.test.js` files, run by `npm run test:db` (see jest.db.config.js).
|
|
7
|
+
const { overrideEnvironment } = require('@friggframework/test');
|
|
8
|
+
|
|
9
|
+
module.exports = async function () {
|
|
10
|
+
if (!process.env.STAGE) {
|
|
11
|
+
overrideEnvironment({ STAGE: 'dev' });
|
|
12
|
+
}
|
|
13
|
+
};
|
package/jest-teardown.js
CHANGED
package/jest.config.js
CHANGED
|
@@ -20,4 +20,8 @@ module.exports = {
|
|
|
20
20
|
globalTeardown: './jest-teardown.js',
|
|
21
21
|
|
|
22
22
|
setupFilesAfterEnv: ['./logs/jest-logger-setup.js'],
|
|
23
|
+
|
|
24
|
+
// `*.db.test.js` suites talk to a real MongoDB replica set / PostgreSQL
|
|
25
|
+
// and run separately via `npm run test:db` (jest.db.config.js).
|
|
26
|
+
testPathIgnorePatterns: ['/node_modules/', '\\.db\\.test\\.js$'],
|
|
23
27
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Database integration suites (`*.db.test.js`).
|
|
3
|
+
*
|
|
4
|
+
* These talk to real databases instead of mocking Prisma, so they are kept
|
|
5
|
+
* out of the default `npm test` run and executed with `npm run test:db`.
|
|
6
|
+
* They need generated Prisma clients (`npm run prisma:generate`) and:
|
|
7
|
+
* - a MongoDB replica set (Prisma requires one for MongoDB writes):
|
|
8
|
+
* FRIGG_TEST_MONGODB_URL, default
|
|
9
|
+
* mongodb://localhost:27017/frigg?replicaSet=rs0
|
|
10
|
+
* - PostgreSQL with the prisma-postgresql migrations applied:
|
|
11
|
+
* FRIGG_TEST_POSTGRES_URL, default
|
|
12
|
+
* postgresql://postgres:postgres@localhost:5432/frigg?schema=public
|
|
13
|
+
* The repository parity suites skip themselves unless their own URLs are
|
|
14
|
+
* set (INTEGRATION_ENTITIES_PARITY_* / QUERY_MAPPINGS_PARITY_*).
|
|
15
|
+
*/
|
|
16
|
+
const base = require('./jest.config');
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
...base,
|
|
20
|
+
coverageThreshold: undefined,
|
|
21
|
+
testMatch: ['**/*.db.test.js'],
|
|
22
|
+
testPathIgnorePatterns: ['/node_modules/'],
|
|
23
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.710.91495fa.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
52
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
53
|
-
"@friggframework/test": "2.0.0--canary.
|
|
51
|
+
"@friggframework/eslint-config": "2.0.0--canary.710.91495fa.0",
|
|
52
|
+
"@friggframework/prettier-config": "2.0.0--canary.710.91495fa.0",
|
|
53
|
+
"@friggframework/test": "2.0.0--canary.710.91495fa.0",
|
|
54
54
|
"@prisma/client": "^6.19.3",
|
|
55
55
|
"@types/lodash": "4.17.15",
|
|
56
56
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"scripts": {
|
|
69
69
|
"lint:fix": "prettier --write --loglevel error . && eslint . --fix",
|
|
70
70
|
"test": "jest --passWithNoTests # TODO",
|
|
71
|
+
"test:db": "jest --config jest.db.config.js --runInBand",
|
|
71
72
|
"prisma:generate:mongo": "npx prisma generate --schema ./prisma-mongodb/schema.prisma",
|
|
72
73
|
"prisma:generate:postgres": "npx prisma generate --schema ./prisma-postgresql/schema.prisma",
|
|
73
74
|
"prisma:generate": "npm run prisma:generate:mongo && npm run prisma:generate:postgres",
|
|
@@ -90,5 +91,5 @@
|
|
|
90
91
|
"publishConfig": {
|
|
91
92
|
"access": "public"
|
|
92
93
|
},
|
|
93
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "91495fa7fd4cc9e0da6709172c245e97ee06daf4"
|
|
94
95
|
}
|
package/queues/queuer-util.js
CHANGED
|
@@ -97,16 +97,17 @@ const QueuerUtil = {
|
|
|
97
97
|
Id: uuid(),
|
|
98
98
|
MessageBody: JSON.stringify(entry),
|
|
99
99
|
});
|
|
100
|
-
// Sends 10, then
|
|
100
|
+
// Sends 10, then starts a fresh buffer. Each command gets its own
|
|
101
|
+
// array: clearing the shared buffer in place would also empty the
|
|
102
|
+
// Entries of a command that was already sent.
|
|
101
103
|
if (buffer.length === batchSize) {
|
|
104
|
+
const batch = buffer.splice(0, buffer.length);
|
|
102
105
|
const command = new SendMessageBatchCommand({
|
|
103
|
-
Entries:
|
|
106
|
+
Entries: batch,
|
|
104
107
|
QueueUrl: queueUrl,
|
|
105
108
|
});
|
|
106
109
|
const result = await sqs.send(command);
|
|
107
|
-
inspectBatchResult(result, queueUrl,
|
|
108
|
-
// Purge the buffer
|
|
109
|
-
buffer.splice(0, buffer.length);
|
|
110
|
+
inspectBatchResult(result, queueUrl, batch);
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|