@friggframework/core 2.0.0--canary.688.e850868.0 → 2.0.0--canary.689.8c6c948.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.
@@ -7,13 +7,6 @@ const ERROR_CODE_MAP = {
7
7
  INVALID_ENTITY_DATA: 400,
8
8
  };
9
9
 
10
- function toCredentialId(credential) {
11
- if (credential && typeof credential === 'object') {
12
- return (credential.id ?? credential._id)?.toString() ?? null;
13
- }
14
- return credential ?? null;
15
- }
16
-
17
10
  function mapErrorToResponse(error) {
18
11
  const status = ERROR_CODE_MAP[error?.code] || 500;
19
12
  return {
@@ -79,7 +72,9 @@ function createEntityCommands() {
79
72
  externalId: entity.externalId,
80
73
  name: entity.name,
81
74
  moduleName: entity.moduleName,
82
- credentialId: toCredentialId(entity.credential),
75
+ credentialId: entity.credential?._id
76
+ ? entity.credential._id.toString()
77
+ : entity.credential,
83
78
  };
84
79
  } catch (error) {
85
80
  return mapErrorToResponse(error);
@@ -120,7 +115,9 @@ function createEntityCommands() {
120
115
  externalId: entity.externalId,
121
116
  name: entity.name,
122
117
  moduleName: entity.moduleName,
123
- credentialId: toCredentialId(entity.credential),
118
+ credentialId: entity.credential?._id
119
+ ? entity.credential._id.toString()
120
+ : entity.credential,
124
121
  };
125
122
  } catch (error) {
126
123
  return mapErrorToResponse(error);
@@ -148,7 +145,9 @@ function createEntityCommands() {
148
145
  externalId: entity.externalId,
149
146
  name: entity.name,
150
147
  moduleName: entity.moduleName,
151
- credentialId: toCredentialId(entity.credential),
148
+ credentialId: entity.credential?._id
149
+ ? entity.credential._id.toString()
150
+ : entity.credential,
152
151
  }));
153
152
  } catch (error) {
154
153
  if (error.code) {
@@ -187,7 +186,9 @@ function createEntityCommands() {
187
186
  externalId: entity.externalId,
188
187
  name: entity.name,
189
188
  moduleName: entity.moduleName,
190
- credentialId: toCredentialId(entity.credential),
189
+ credentialId: entity.credential?._id
190
+ ? entity.credential._id.toString()
191
+ : entity.credential,
191
192
  }));
192
193
  } catch (error) {
193
194
  if (error.code) {
@@ -218,7 +219,9 @@ function createEntityCommands() {
218
219
  externalId: entity.externalId,
219
220
  name: entity.name,
220
221
  moduleName: entity.moduleName,
221
- credentialId: toCredentialId(entity.credential),
222
+ credentialId: entity.credential?._id
223
+ ? entity.credential._id.toString()
224
+ : entity.credential,
222
225
  };
223
226
  } catch (error) {
224
227
  return mapErrorToResponse(error);
@@ -253,7 +256,9 @@ function createEntityCommands() {
253
256
  externalId: entity.externalId,
254
257
  name: entity.name,
255
258
  moduleName: entity.moduleName,
256
- credentialId: toCredentialId(entity.credential),
259
+ credentialId: entity.credential?._id
260
+ ? entity.credential._id.toString()
261
+ : entity.credential,
257
262
  };
258
263
  } catch (error) {
259
264
  return mapErrorToResponse(error);
@@ -29,6 +29,10 @@ const {
29
29
  const {
30
30
  PatchIntegrationConfig,
31
31
  } = require('../../integrations/use-cases/patch-integration-config');
32
+ const {
33
+ AddEntitiesToIntegration,
34
+ RemoveEntitiesFromIntegration,
35
+ } = require('../../integrations/use-cases/update-integration-entities');
32
36
  const {
33
37
  getModulesDefinitionFromIntegrationClasses,
34
38
  } = require('../../integrations/utils/map-integration-dto');
@@ -40,6 +44,7 @@ const ERROR_CODE_MAP = {
40
44
  EXTERNAL_ID_REQUIRED: 400,
41
45
  TYPE_REQUIRED: 400,
42
46
  INTEGRATION_RECORD_NOT_FOUND: 404,
47
+ INVALID_INTEGRATION_DATA: 400,
43
48
  };
44
49
 
45
50
  function mapErrorToResponse(error) {
@@ -163,6 +168,15 @@ function createIntegrationCommands({ integrationClass } = {}) {
163
168
  integrationRepository,
164
169
  });
165
170
 
171
+ const addEntitiesToIntegrationUseCase = new AddEntitiesToIntegration({
172
+ integrationRepository,
173
+ });
174
+
175
+ const removeEntitiesFromIntegrationUseCase =
176
+ new RemoveEntitiesFromIntegration({
177
+ integrationRepository,
178
+ });
179
+
166
180
  return {
167
181
  findIntegrationById,
168
182
  listIntegrations,
@@ -307,6 +321,44 @@ function createIntegrationCommands({ integrationClass } = {}) {
307
321
  }
308
322
  },
309
323
 
324
+ /**
325
+ * Attach entities to an existing integration. Entities that are
326
+ * already attached are left as they are.
327
+ * @param {Object} params
328
+ * @param {string} params.integrationId - Integration ID
329
+ * @param {Array<string>} params.entityIds - Entity IDs to attach
330
+ * @returns {Promise<Object>} Updated integration
331
+ */
332
+ async addEntitiesToIntegration({ integrationId, entityIds }) {
333
+ try {
334
+ return await addEntitiesToIntegrationUseCase.execute(
335
+ integrationId,
336
+ entityIds
337
+ );
338
+ } catch (error) {
339
+ return mapErrorToResponse(error);
340
+ }
341
+ },
342
+
343
+ /**
344
+ * Detach entities from an integration. Entities that are not
345
+ * attached are ignored; detached entities are not deleted.
346
+ * @param {Object} params
347
+ * @param {string} params.integrationId - Integration ID
348
+ * @param {Array<string>} params.entityIds - Entity IDs to detach
349
+ * @returns {Promise<Object>} Updated integration
350
+ */
351
+ async removeEntitiesFromIntegration({ integrationId, entityIds }) {
352
+ try {
353
+ return await removeEntitiesFromIntegrationUseCase.execute(
354
+ integrationId,
355
+ entityIds
356
+ );
357
+ } catch (error) {
358
+ return mapErrorToResponse(error);
359
+ }
360
+ },
361
+
310
362
  /**
311
363
  * Delete an integration by ID
312
364
  * @param {string} integrationId - Integration ID to delete
@@ -164,6 +164,42 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
164
164
  return this._mapIntegration(created);
165
165
  }
166
166
 
167
+ async addEntitiesToIntegration(integrationId, entityIds) {
168
+ const objectId = toObjectId(integrationId);
169
+ if (!objectId) return null;
170
+ const existing = await findOne(this.prisma, 'Integration', { _id: objectId });
171
+ if (!existing) return null;
172
+
173
+ await updateOne(
174
+ this.prisma,
175
+ 'Integration',
176
+ { _id: objectId },
177
+ {
178
+ $addToSet: { entityIds: { $each: toObjectIdArray(entityIds) } },
179
+ $set: { updatedAt: new Date() },
180
+ }
181
+ );
182
+ return this.findIntegrationById(integrationId);
183
+ }
184
+
185
+ async removeEntitiesFromIntegration(integrationId, entityIds) {
186
+ const objectId = toObjectId(integrationId);
187
+ if (!objectId) return null;
188
+ const existing = await findOne(this.prisma, 'Integration', { _id: objectId });
189
+ if (!existing) return null;
190
+
191
+ await updateOne(
192
+ this.prisma,
193
+ 'Integration',
194
+ { _id: objectId },
195
+ {
196
+ $pullAll: { entityIds: toObjectIdArray(entityIds) },
197
+ $set: { updatedAt: new Date() },
198
+ }
199
+ );
200
+ return this.findIntegrationById(integrationId);
201
+ }
202
+
167
203
  async findIntegrationByUserId(userId) {
168
204
  const objectId = toObjectId(userId);
169
205
  if (!objectId) return null;
@@ -184,6 +184,36 @@ class IntegrationRepositoryInterface {
184
184
  'Method findIntegrationsByEntityId must be implemented by subclass'
185
185
  );
186
186
  }
187
+
188
+ /**
189
+ * Attach entities to an existing integration. Entities that are already
190
+ * attached are left as they are.
191
+ *
192
+ * @param {string|number} integrationId - Integration ID
193
+ * @param {Array<string|number>} entityIds - Entity IDs to attach
194
+ * @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
195
+ * @abstract
196
+ */
197
+ async addEntitiesToIntegration(integrationId, entityIds) {
198
+ throw new Error(
199
+ 'Method addEntitiesToIntegration must be implemented by subclass'
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Detach entities from an integration. Entities that are not attached
205
+ * are ignored; the entities themselves are not deleted.
206
+ *
207
+ * @param {string|number} integrationId - Integration ID
208
+ * @param {Array<string|number>} entityIds - Entity IDs to detach
209
+ * @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
210
+ * @abstract
211
+ */
212
+ async removeEntitiesFromIntegration(integrationId, entityIds) {
213
+ throw new Error(
214
+ 'Method removeEntitiesFromIntegration must be implemented by subclass'
215
+ );
216
+ }
187
217
  }
188
218
 
189
219
  module.exports = { IntegrationRepositoryInterface };
@@ -338,6 +338,58 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
338
338
  };
339
339
  }
340
340
 
341
+ /**
342
+ * Attach entities to an existing integration. Entities already attached
343
+ * are left as they are.
344
+ *
345
+ * @param {string} integrationId - Integration ID
346
+ * @param {Array<string>} entityIds - Entity IDs to attach
347
+ * @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
348
+ */
349
+ async addEntitiesToIntegration(integrationId, entityIds) {
350
+ const existing = await this.prisma.integration.findUnique({
351
+ where: { id: integrationId },
352
+ select: { id: true },
353
+ });
354
+ if (!existing) return null;
355
+
356
+ await this.prisma.integration.update({
357
+ where: { id: integrationId },
358
+ data: {
359
+ entities: {
360
+ connect: entityIds.map((id) => ({ id })),
361
+ },
362
+ },
363
+ });
364
+ return this.findIntegrationById(integrationId);
365
+ }
366
+
367
+ /**
368
+ * Detach entities from an integration. Entities that are not attached
369
+ * are ignored. The entities themselves are not deleted.
370
+ *
371
+ * @param {string} integrationId - Integration ID
372
+ * @param {Array<string>} entityIds - Entity IDs to detach
373
+ * @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
374
+ */
375
+ async removeEntitiesFromIntegration(integrationId, entityIds) {
376
+ const existing = await this.prisma.integration.findUnique({
377
+ where: { id: integrationId },
378
+ select: { id: true },
379
+ });
380
+ if (!existing) return null;
381
+
382
+ await this.prisma.integration.update({
383
+ where: { id: integrationId },
384
+ data: {
385
+ entities: {
386
+ disconnect: entityIds.map((id) => ({ id })),
387
+ },
388
+ },
389
+ });
390
+ return this.findIntegrationById(integrationId);
391
+ }
392
+
341
393
  /**
342
394
  * Find integration by user ID (returns single integration)
343
395
  * Replaces: IntegrationModel.findOne({ user: userId }).populate('entities')
@@ -365,6 +365,64 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
365
365
  };
366
366
  }
367
367
 
368
+ /**
369
+ * Attach entities to an existing integration. Entities already attached
370
+ * are left as they are.
371
+ *
372
+ * @param {string} integrationId - Integration ID (string from application layer)
373
+ * @param {Array<string>} entityIds - Entity IDs to attach
374
+ * @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
375
+ */
376
+ async addEntitiesToIntegration(integrationId, entityIds) {
377
+ const intId = this._convertId(integrationId);
378
+ const existing = await this.prisma.integration.findUnique({
379
+ where: { id: intId },
380
+ select: { id: true },
381
+ });
382
+ if (!existing) return null;
383
+
384
+ await this.prisma.integration.update({
385
+ where: { id: intId },
386
+ data: {
387
+ entities: {
388
+ connect: entityIds.map((id) => ({
389
+ id: this._convertId(id),
390
+ })),
391
+ },
392
+ },
393
+ });
394
+ return this.findIntegrationById(integrationId);
395
+ }
396
+
397
+ /**
398
+ * Detach entities from an integration. Entities that are not attached
399
+ * are ignored. The entities themselves are not deleted.
400
+ *
401
+ * @param {string} integrationId - Integration ID (string from application layer)
402
+ * @param {Array<string>} entityIds - Entity IDs to detach
403
+ * @returns {Promise<Object|null>} Updated integration, or null when the integration does not exist
404
+ */
405
+ async removeEntitiesFromIntegration(integrationId, entityIds) {
406
+ const intId = this._convertId(integrationId);
407
+ const existing = await this.prisma.integration.findUnique({
408
+ where: { id: intId },
409
+ select: { id: true },
410
+ });
411
+ if (!existing) return null;
412
+
413
+ await this.prisma.integration.update({
414
+ where: { id: intId },
415
+ data: {
416
+ entities: {
417
+ disconnect: entityIds.map((id) => ({
418
+ id: this._convertId(id),
419
+ })),
420
+ },
421
+ },
422
+ });
423
+ return this.findIntegrationById(integrationId);
424
+ }
425
+
368
426
  /**
369
427
  * Find integration by user ID (returns single integration)
370
428
  *
@@ -0,0 +1,80 @@
1
+ function validate(integrationId, entityIds) {
2
+ if (!integrationId || !Array.isArray(entityIds) || entityIds.length === 0) {
3
+ const error = new Error(
4
+ 'integrationId and a non-empty entityIds array are required'
5
+ );
6
+ error.code = 'INVALID_INTEGRATION_DATA';
7
+ throw error;
8
+ }
9
+ }
10
+
11
+ function notFound(integrationId) {
12
+ const error = new Error(`Integration ${integrationId} not found`);
13
+ error.code = 'INTEGRATION_NOT_FOUND';
14
+ return error;
15
+ }
16
+
17
+ /**
18
+ * Attaches entities to an existing integration, e.g. when a user grants
19
+ * access to more accounts after the integration was created. Entities that
20
+ * are already attached are left as they are.
21
+ * @class AddEntitiesToIntegration
22
+ */
23
+ class AddEntitiesToIntegration {
24
+ /**
25
+ * @param {Object} params
26
+ * @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
27
+ */
28
+ constructor({ integrationRepository }) {
29
+ this.integrationRepository = integrationRepository;
30
+ }
31
+
32
+ /**
33
+ * @param {string} integrationId
34
+ * @param {string[]} entityIds
35
+ * @returns {Promise<Object>} The updated integration record.
36
+ */
37
+ async execute(integrationId, entityIds) {
38
+ validate(integrationId, entityIds);
39
+ const integration =
40
+ await this.integrationRepository.addEntitiesToIntegration(
41
+ integrationId,
42
+ entityIds
43
+ );
44
+ if (!integration) throw notFound(integrationId);
45
+ return integration;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Detaches entities from an integration. Entities that are not attached are
51
+ * ignored, and detached entities are not deleted.
52
+ * @class RemoveEntitiesFromIntegration
53
+ */
54
+ class RemoveEntitiesFromIntegration {
55
+ /**
56
+ * @param {Object} params
57
+ * @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
58
+ */
59
+ constructor({ integrationRepository }) {
60
+ this.integrationRepository = integrationRepository;
61
+ }
62
+
63
+ /**
64
+ * @param {string} integrationId
65
+ * @param {string[]} entityIds
66
+ * @returns {Promise<Object>} The updated integration record.
67
+ */
68
+ async execute(integrationId, entityIds) {
69
+ validate(integrationId, entityIds);
70
+ const integration =
71
+ await this.integrationRepository.removeEntitiesFromIntegration(
72
+ integrationId,
73
+ entityIds
74
+ );
75
+ if (!integration) throw notFound(integrationId);
76
+ return integration;
77
+ }
78
+ }
79
+
80
+ module.exports = { AddEntitiesToIntegration, RemoveEntitiesFromIntegration };
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.688.e850868.0",
4
+ "version": "2.0.0--canary.689.8c6c948.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -47,9 +47,9 @@
47
47
  }
48
48
  },
49
49
  "devDependencies": {
50
- "@friggframework/eslint-config": "2.0.0--canary.688.e850868.0",
51
- "@friggframework/prettier-config": "2.0.0--canary.688.e850868.0",
52
- "@friggframework/test": "2.0.0--canary.688.e850868.0",
50
+ "@friggframework/eslint-config": "2.0.0--canary.689.8c6c948.0",
51
+ "@friggframework/prettier-config": "2.0.0--canary.689.8c6c948.0",
52
+ "@friggframework/test": "2.0.0--canary.689.8c6c948.0",
53
53
  "@prisma/client": "^6.19.3",
54
54
  "@types/lodash": "4.17.15",
55
55
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -89,5 +89,5 @@
89
89
  "publishConfig": {
90
90
  "access": "public"
91
91
  },
92
- "gitHead": "e850868f19706ee89566fd9724603dee1e03a711"
92
+ "gitHead": "8c6c948c740134af02e7dc17da7bc41c43d0bea3"
93
93
  }