@friggframework/core 2.0.0-next.123 → 2.0.0-next.125

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.
@@ -1,6 +1,9 @@
1
1
  const { findNearestBackendPackageJson } = require('@friggframework/core/utils');
2
2
  const path = require('node:path');
3
3
  const fs = require('fs-extra');
4
+ const {
5
+ applyAppDefinitionDefaults,
6
+ } = require('@friggframework/schemas/app-definition');
4
7
  const { resolveTelemetryConfig } = require('../telemetry/telemetry-config');
5
8
  const { registerDeniedKeys } = require('../logs/denied-keys');
6
9
  const {
@@ -33,7 +36,9 @@ function registerCredentialLogKeys(appDefinition, integrations) {
33
36
  * @function loadAppDefinition
34
37
  * @description Searches for the nearest backend package.json, loads the corresponding index.js file,
35
38
  * and extracts the application definition containing integrations and user configuration.
36
- * @returns {{integrations: Array<object>, userConfig: object | null, adminScripts: Array<object>, reports: Array<object>, admin: object, telemetry: object, logging: object | null}} An object containing the application definition.
39
+ * The defaults declared in the app-definition schema are applied first (ADR-051), so
40
+ * `userConfig` always holds a complete user config, also when the app omits `user`.
41
+ * @returns {{integrations: Array<object>, userConfig: object, adminScripts: Array<object>, reports: Array<object>, admin: object, telemetry: object, logging: object | null}} An object containing the application definition.
37
42
  * @throws {Error} Throws error if backend package.json cannot be found.
38
43
  * @throws {Error} Throws error if index.js file cannot be found in the backend directory.
39
44
  * @example
@@ -53,7 +58,9 @@ function loadAppDefinition() {
53
58
  }
54
59
 
55
60
  const backendJsFile = require(backendFilePath);
56
- const appDefinition = backendJsFile.Definition;
61
+ // The one place schema defaults are applied at runtime (ADR-051): a
62
+ // default in app-definition.schema.json is the value core sees.
63
+ const appDefinition = applyAppDefinitionDefaults(backendJsFile.Definition);
57
64
 
58
65
  const {
59
66
  integrations = [],
@@ -557,17 +557,30 @@ class IntegrationBase {
557
557
  }
558
558
  }
559
559
  }
560
- const dynamicUserActions = await this.loadDynamicUserActions();
561
- const filteredDynamicActions = actionType
562
- ? Object.fromEntries(
563
- Object.entries(dynamicUserActions).filter(
564
- ([_, event]) => event.userActionType === actionType
565
- )
566
- )
567
- : dynamicUserActions;
560
+ const dynamicUserActions = await this._loadDynamicUserActionEvents();
561
+ const filteredDynamicActions = Object.fromEntries(
562
+ Object.entries(dynamicUserActions).filter(
563
+ ([_, event]) =>
564
+ event.type === constantsToBeMigrated.types.USER_ACTION &&
565
+ (!actionType || event.userActionType === actionType)
566
+ )
567
+ );
568
568
  return { ...userActions, ...filteredDynamicActions };
569
569
  }
570
570
 
571
+ async _loadDynamicUserActionEvents() {
572
+ const dynamicUserActions = await this.loadDynamicUserActions();
573
+ return Object.fromEntries(
574
+ Object.entries(dynamicUserActions).map(([key, event]) => [
575
+ key,
576
+ {
577
+ ...event,
578
+ type: event.type ?? constantsToBeMigrated.types.USER_ACTION,
579
+ },
580
+ ])
581
+ );
582
+ }
583
+
571
584
  async getActionOptions(actionId, data) {
572
585
  const options = {
573
586
  jsonSchema: {},
@@ -847,7 +860,8 @@ class IntegrationBase {
847
860
 
848
861
  async initialize() {
849
862
  try {
850
- const additionalUserActions = await this.loadDynamicUserActions();
863
+ const additionalUserActions =
864
+ await this._loadDynamicUserActionEvents();
851
865
  this.events = { ...this.events, ...additionalUserActions };
852
866
  } catch (e) {
853
867
  this.addError(e);
@@ -257,6 +257,15 @@ function checkRequiredParams(params, requiredKeys) {
257
257
  return returnDict;
258
258
  }
259
259
 
260
+ function assertUserAction(integration, actionId) {
261
+ if (
262
+ !Object.hasOwn(integration.events, actionId) ||
263
+ integration.events[actionId].type !== 'USER_ACTION'
264
+ ) {
265
+ throw Boom.notFound(`User action ${actionId} does not exist`);
266
+ }
267
+ }
268
+
260
269
  /**
261
270
  * Sets up integration-related routes on the provided Express router
262
271
  * @param {express.Router} router - Express router instance to add routes to
@@ -393,6 +402,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
393
402
  params.integrationId,
394
403
  user.getId()
395
404
  );
405
+ assertUserAction(integration, params.actionId);
396
406
 
397
407
  res.json(
398
408
  await integration.send('GET_USER_ACTION_OPTIONS', {
@@ -418,6 +428,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
418
428
  params.integrationId,
419
429
  user.getId()
420
430
  );
431
+ assertUserAction(integration, params.actionId);
421
432
 
422
433
  res.json(
423
434
  await integration.send('REFRESH_USER_ACTION_OPTIONS', {
@@ -439,6 +450,7 @@ function setIntegrationRoutes(router, authenticateUser, useCases) {
439
450
  params.integrationId,
440
451
  user.getId()
441
452
  );
453
+ assertUserAction(integration, params.actionId);
442
454
  res.json(await integration.send(params.actionId, req.body));
443
455
  })
444
456
  );
@@ -31,14 +31,17 @@ class DeleteIntegrationForUser {
31
31
  * @param {string} integrationId - ID of the integration to delete.
32
32
  * @param {string} userId - ID of the user requesting the deletion.
33
33
  * @returns {Promise<void>} Resolves when the integration is successfully deleted.
34
- * @throws {Boom.notFound} When integration with the specified ID does not exist.
35
- * @throws {Error} When the integration doesn't belong to the specified user.
34
+ * @throws {Boom.notFound} When integration does not exist or doesn't belong to the specified user.
36
35
  */
37
36
  async execute(integrationId, userId) {
38
- const integrationRecord =
39
- await this.integrationRepository.findIntegrationById(integrationId);
37
+ const integrationRecord = await this.integrationRepository
38
+ .findIntegrationById(integrationId)
39
+ .catch((error) => {
40
+ if (error.message?.includes('not found')) return null;
41
+ throw error;
42
+ });
40
43
 
41
- if (!integrationRecord) {
44
+ if (!integrationRecord || integrationRecord.userId !== userId) {
42
45
  throw Boom.notFound(
43
46
  `Integration with id of ${integrationId} does not exist`
44
47
  );
@@ -50,12 +53,6 @@ class DeleteIntegrationForUser {
50
53
  integrationRecord.config.type
51
54
  );
52
55
 
53
- if (integrationRecord.userId !== userId) {
54
- throw new Error(
55
- `Integration ${integrationId} does not belong to User ${userId}`
56
- );
57
- }
58
-
59
56
  // Load modules with API clients for webhook deletion
60
57
  const modules = [];
61
58
  const failedModuleLoads = [];
@@ -1,3 +1,4 @@
1
+ const Boom = require('@hapi/boom');
1
2
  // Removed Integration wrapper - using IntegrationBase directly
2
3
 
3
4
  /**
@@ -24,15 +25,20 @@ class GetIntegrationInstance {
24
25
  * @param {string} integrationId - ID of the integration to retrieve.
25
26
  * @param {string} userId - ID of the user requesting the integration.
26
27
  * @returns {Promise<Integration>} The fully initialized integration instance.
27
- * @throws {Error} When integration is not found, doesn't belong to user, or integration class is not found.
28
+ * @throws {Boom.notFound} When integration is not found or doesn't belong to user.
29
+ * @throws {Error} When integration class is not found.
28
30
  */
29
31
  async execute(integrationId, userId) {
30
- const integrationRecord =
31
- await this.integrationRepository.findIntegrationById(integrationId);
32
+ const integrationRecord = await this.integrationRepository
33
+ .findIntegrationById(integrationId)
34
+ .catch((error) => {
35
+ if (error.message?.includes('not found')) return null;
36
+ throw error;
37
+ });
32
38
 
33
- if (!integrationRecord) {
34
- throw new Error(
35
- `No integration found by the ID of ${integrationId}`
39
+ if (!integrationRecord || integrationRecord.userId !== userId) {
40
+ throw Boom.notFound(
41
+ `Integration with id of ${integrationId} does not exist`
36
42
  );
37
43
  }
38
44
 
@@ -48,12 +54,6 @@ class GetIntegrationInstance {
48
54
  );
49
55
  }
50
56
 
51
- if (integrationRecord.userId !== userId) {
52
- throw new Error(
53
- `Integration ${integrationId} does not belong to User ${userId}`
54
- );
55
- }
56
-
57
57
  const modules = [];
58
58
  for (const entityId of integrationRecord.entitiesIds) {
59
59
  const moduleInstance = await this.moduleFactory.getModuleInstance(
@@ -1,3 +1,4 @@
1
+ const Boom = require('@hapi/boom');
1
2
  const {
2
3
  mapIntegrationClassToIntegrationDTO,
3
4
  } = require('../utils/map-integration-dto');
@@ -27,16 +28,21 @@ class UpdateIntegration {
27
28
  * @param {string} userId - ID of the user requesting the update.
28
29
  * @param {Object} config - New configuration object for the integration.
29
30
  * @returns {Promise<Object>} The updated integration DTO.
30
- * @throws {Error} When integration is not found, doesn't belong to user, or integration class is not found.
31
+ * @throws {Boom.notFound} When integration is not found or doesn't belong to user.
32
+ * @throws {Error} When integration class is not found.
31
33
  */
32
34
  async execute(integrationId, userId, config) {
33
35
  // 1. Get integration record from repository
34
- const integrationRecord =
35
- await this.integrationRepository.findIntegrationById(integrationId);
36
+ const integrationRecord = await this.integrationRepository
37
+ .findIntegrationById(integrationId)
38
+ .catch((error) => {
39
+ if (error.message?.includes('not found')) return null;
40
+ throw error;
41
+ });
36
42
 
37
- if (!integrationRecord) {
38
- throw new Error(
39
- `No integration found by the ID of ${integrationId}`
43
+ if (!integrationRecord || integrationRecord.userId !== userId) {
44
+ throw Boom.notFound(
45
+ `Integration with id of ${integrationId} does not exist`
40
46
  );
41
47
  }
42
48
 
@@ -53,12 +59,6 @@ class UpdateIntegration {
53
59
  );
54
60
  }
55
61
 
56
- if (integrationRecord.userId !== userId) {
57
- throw new Error(
58
- `Integration ${integrationId} does not belong to User ${userId}`
59
- );
60
- }
61
-
62
62
  // 3. Load modules based on entity references
63
63
  const modules = [];
64
64
  for (const entityId of integrationRecord.entitiesIds) {
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-next.123",
4
+ "version": "2.0.0-next.125",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -11,6 +11,7 @@
11
11
  "@aws-sdk/client-sqs": "^3.588.0",
12
12
  "@aws-sdk/client-ssm": "^3.588.0",
13
13
  "@aws-sdk/s3-request-presigner": "^3.588.0",
14
+ "@friggframework/schemas": "2.0.0-next.125",
14
15
  "@hapi/boom": "^10.0.1",
15
16
  "@opentelemetry/api": "^1.9.1",
16
17
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -48,9 +49,9 @@
48
49
  }
49
50
  },
50
51
  "devDependencies": {
51
- "@friggframework/eslint-config": "2.0.0-next.123",
52
- "@friggframework/prettier-config": "2.0.0-next.123",
53
- "@friggframework/test": "2.0.0-next.123",
52
+ "@friggframework/eslint-config": "2.0.0-next.125",
53
+ "@friggframework/prettier-config": "2.0.0-next.125",
54
+ "@friggframework/test": "2.0.0-next.125",
54
55
  "@prisma/client": "^6.19.3",
55
56
  "@types/lodash": "4.17.15",
56
57
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -91,5 +92,5 @@
91
92
  "publishConfig": {
92
93
  "access": "public"
93
94
  },
94
- "gitHead": "1194ad79bbe3faa0372cbbeb632eea89e6ea5af4"
95
+ "gitHead": "a64e5964dd1a945ce6439df062d79d637d861caf"
95
96
  }