@friggframework/core 2.0.0--canary.667.f74cf3b.0 → 2.0.0--canary.710.7cfcf12.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.
Files changed (48) hide show
  1. package/CLAUDE.md +41 -1
  2. package/application/commands/README.md +17 -11
  3. package/application/commands/entity-commands.js +18 -60
  4. package/application/commands/integration-commands.js +63 -0
  5. package/database/documentdb-encryption-service.js +2 -33
  6. package/database/documentdb-utils.js +4 -1
  7. package/database/encryption/README.md +3 -3
  8. package/database/encryption/ciphertext-format.js +24 -0
  9. package/database/encryption/encryption-schema-registry.js +109 -29
  10. package/database/encryption/field-encryption-service.js +13 -16
  11. package/database/prisma.js +10 -1
  12. package/errors/index.js +4 -0
  13. package/errors/mapping-already-exists-error.js +14 -0
  14. package/handlers/WEBHOOKS.md +29 -0
  15. package/index.js +2 -0
  16. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
  17. package/integrations/integration-base.js +29 -0
  18. package/integrations/integration-router.js +22 -18
  19. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  20. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  21. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  22. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  23. package/integrations/repositories/integration-mapping-repository.js +29 -0
  24. package/integrations/repositories/integration-repository-documentdb.js +32 -0
  25. package/integrations/repositories/integration-repository-interface.js +30 -0
  26. package/integrations/repositories/integration-repository-mongo.js +53 -1
  27. package/integrations/repositories/integration-repository-postgres.js +58 -0
  28. package/integrations/repositories/process-repository-mongo.js +1 -1
  29. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  30. package/integrations/use-cases/create-integration.js +57 -19
  31. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  32. package/integrations/utils/assert-entity-ids-input.js +42 -0
  33. package/jest-setup.js +13 -2
  34. package/jest-teardown.js +5 -2
  35. package/jest.config.js +4 -0
  36. package/jest.db.config.js +23 -0
  37. package/modules/use-cases/get-entity-options-by-id.js +5 -7
  38. package/modules/use-cases/get-module.js +8 -29
  39. package/modules/use-cases/refresh-entity-options.js +5 -7
  40. package/modules/use-cases/test-module-auth.js +5 -7
  41. package/modules/utils/map-module-dto.js +5 -3
  42. package/modules/utils/to-credential-id.js +14 -0
  43. package/package.json +7 -5
  44. package/queues/queuer-util.js +6 -5
  45. package/user/tests/doubles/test-user-repository.js +1 -1
  46. package/user/use-cases/get-user-from-bearer-token.js +7 -2
  47. package/modules/use-cases/create-entity-from-credential.js +0 -105
  48. package/modules/utils/find-module-definition.js +0 -23
@@ -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
+
351
+ try {
352
+ await this.prisma.integration.update({
353
+ where: { id: integrationId },
354
+ data: {
355
+ entities: {
356
+ connect: entityIds.map((id) => ({ id })),
357
+ },
358
+ },
359
+ });
360
+ } catch (error) {
361
+ if (error.code === 'P2025') return null;
362
+ throw error;
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
+
377
+ try {
378
+ await this.prisma.integration.update({
379
+ where: { id: integrationId },
380
+ data: {
381
+ entities: {
382
+ disconnect: entityIds.map((id) => ({ id })),
383
+ },
384
+ },
385
+ });
386
+ } catch (error) {
387
+ if (error.code === 'P2025') return null;
388
+ throw error;
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')
@@ -419,7 +471,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
419
471
  for (const [key, value] of Object.entries(patch)) {
420
472
  $set[`config.${key}`] = value;
421
473
  }
422
- $set.updatedAt = new Date();
474
+ $set.updatedAt = { $date: new Date().toISOString() };
423
475
 
424
476
  const result = await this.prisma.$runCommandRaw({
425
477
  findAndModify: 'Integration',
@@ -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
+
379
+ try {
380
+ await this.prisma.integration.update({
381
+ where: { id: intId },
382
+ data: {
383
+ entities: {
384
+ connect: entityIds.map((id) => ({
385
+ id: this._convertId(id),
386
+ })),
387
+ },
388
+ },
389
+ });
390
+ } catch (error) {
391
+ if (error.code === 'P2025') return null;
392
+ throw error;
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
+
408
+ try {
409
+ await this.prisma.integration.update({
410
+ where: { id: intId },
411
+ data: {
412
+ entities: {
413
+ disconnect: entityIds.map((id) => ({
414
+ id: this._convertId(id),
415
+ })),
416
+ },
417
+ },
418
+ });
419
+ } catch (error) {
420
+ if (error.code === 'P2025') return null;
421
+ throw error;
422
+ }
423
+ return this.findIntegrationById(integrationId);
424
+ }
425
+
368
426
  /**
369
427
  * Find integration by user ID (returns single integration)
370
428
  *
@@ -120,7 +120,7 @@ class ProcessRepositoryMongo extends ProcessRepositoryInterface {
120
120
  if (normalized.newState !== null) {
121
121
  $set.state = normalized.newState;
122
122
  }
123
- $set.updatedAt = new Date();
123
+ $set.updatedAt = { $date: new Date().toISOString() };
124
124
  update.$set = $set;
125
125
 
126
126
  if (Object.keys(normalized.pushSlice).length > 0) {
@@ -0,0 +1,92 @@
1
+ const {
2
+ assertEntityIdsInput,
3
+ integrationNotFound,
4
+ loadIntegration,
5
+ } = require('../utils/assert-entity-ids-input');
6
+
7
+ /**
8
+ * Attaches entities to an existing integration, e.g. when a user grants
9
+ * access to more accounts after the integration was created. Every entity
10
+ * must exist, belong to the integration's user, and be of a module the
11
+ * integration already holds (so hydration can load it). Entities that are
12
+ * already attached are left as they are.
13
+ * @class AddEntitiesToIntegration
14
+ */
15
+ class AddEntitiesToIntegration {
16
+ /**
17
+ * @param {Object} params
18
+ * @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
19
+ * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
20
+ */
21
+ constructor({ integrationRepository, moduleRepository }) {
22
+ this.integrationRepository = integrationRepository;
23
+ this.moduleRepository = moduleRepository;
24
+ }
25
+
26
+ /**
27
+ * @param {string} integrationId
28
+ * @param {string[]} entityIds
29
+ * @returns {Promise<Object>} The updated integration record.
30
+ */
31
+ async execute(integrationId, entityIds) {
32
+ assertEntityIdsInput(integrationId, entityIds);
33
+
34
+ const integration = await loadIntegration(
35
+ this.integrationRepository,
36
+ integrationId
37
+ );
38
+
39
+ const entities = await this.moduleRepository.findEntitiesByIds(
40
+ entityIds
41
+ );
42
+ const foundIds = new Set(entities.map((e) => String(e.id)));
43
+ const missing = entityIds.filter((id) => !foundIds.has(String(id)));
44
+ if (missing.length > 0) {
45
+ const error = new Error(
46
+ `Entities not found: ${missing.join(', ')}`
47
+ );
48
+ error.code = 'ENTITIES_NOT_FOUND';
49
+ throw error;
50
+ }
51
+
52
+ const foreign = entities.filter(
53
+ (e) => String(e.userId) !== String(integration.userId)
54
+ );
55
+ if (foreign.length > 0) {
56
+ const error = new Error(
57
+ `Entities do not belong to the integration's user: ${foreign
58
+ .map((e) => e.id)
59
+ .join(', ')}`
60
+ );
61
+ error.code = 'ENTITY_USER_MISMATCH';
62
+ throw error;
63
+ }
64
+
65
+ const attached = await this.moduleRepository.findEntitiesByIds(
66
+ integration.entitiesIds ?? []
67
+ );
68
+ const heldModules = new Set(attached.map((e) => e.moduleName));
69
+ const otherModules = entities.filter(
70
+ (e) => !heldModules.has(e.moduleName)
71
+ );
72
+ if (otherModules.length > 0) {
73
+ const error = new Error(
74
+ `Entities belong to a module the integration does not hold: ${otherModules
75
+ .map((e) => `${e.id} (${e.moduleName})`)
76
+ .join(', ')}`
77
+ );
78
+ error.code = 'ENTITY_MODULE_MISMATCH';
79
+ throw error;
80
+ }
81
+
82
+ const updated =
83
+ await this.integrationRepository.addEntitiesToIntegration(
84
+ integrationId,
85
+ entityIds
86
+ );
87
+ if (!updated) throw integrationNotFound(integrationId);
88
+ return updated;
89
+ }
90
+ }
91
+
92
+ module.exports = { AddEntitiesToIntegration };
@@ -62,7 +62,7 @@ class CreateIntegration {
62
62
 
63
63
  // A concurrent request may have created a matching row between the
64
64
  // lookup above and this insert; re-check before any side effects run.
65
- const survivor = await this._findDuplicate(
65
+ const survivor = await this._findSurvivor(
66
66
  userId,
67
67
  config?.type,
68
68
  entities
@@ -135,30 +135,68 @@ class CreateIntegration {
135
135
  return mapIntegrationClassToIntegrationDTO(integrationInstance);
136
136
  }
137
137
 
138
- async _findDuplicate(userId, type, entityIds) {
138
+ async _findMatches(userId, type, entityIds) {
139
139
  const candidates =
140
140
  await this.integrationRepository.findIntegrationsByUserId(userId);
141
- const target = [...(entityIds ?? [])].map(String).sort();
142
-
143
- const matches = candidates.filter((integration) => {
144
- // An IN_DELETION row is either mid-teardown or a zombie left
145
- // behind by a failed deleteIntegrationById call — never a live
146
- // integration. Treating it as a duplicate would hand a reinstall
147
- // attempt a row nothing will ever move out of IN_DELETION,
148
- // silently discarded by the queue worker forever. Let a fresh
149
- // create proceed instead; the zombie is cleaned up out-of-band,
150
- // matching how teardown failures are already handled here.
151
- if (integration.status === 'IN_DELETION') return false;
152
- if (integration.config?.type !== type) return false;
153
- const current = [...(integration.entitiesIds ?? [])]
154
- .map(String)
155
- .sort();
141
+ const target = new Set((entityIds ?? []).map(String));
142
+
143
+ const live = candidates.filter(
144
+ (integration) =>
145
+ // An IN_DELETION row is either mid-teardown or a zombie left
146
+ // behind by a failed deleteIntegrationById call — never a live
147
+ // integration. Treating it as a duplicate would hand a
148
+ // reinstall attempt a row nothing will ever move out of
149
+ // IN_DELETION, silently discarded by the queue worker forever.
150
+ // Let a fresh create proceed instead; the zombie is cleaned up
151
+ // out-of-band, matching how teardown failures are already
152
+ // handled here.
153
+ integration.status !== 'IN_DELETION' &&
154
+ integration.config?.type === type
155
+ );
156
+ const entitySet = (integration) =>
157
+ new Set((integration.entitiesIds ?? []).map(String));
158
+
159
+ const exact = live.filter((integration) => {
160
+ const current = entitySet(integration);
156
161
  return (
157
- current.length === target.length &&
158
- current.every((id, index) => id === target[index])
162
+ current.size === target.size &&
163
+ [...target].every((id) => current.has(id))
159
164
  );
160
165
  });
161
166
 
167
+ // Entities may have been attached after create (addEntitiesToIntegration),
168
+ // so a reconnect that sends the original entities must still find the
169
+ // integration that now holds them, instead of creating a second one
170
+ // that would share those entities.
171
+ const supersets = live.filter((integration) => {
172
+ const current = entitySet(integration);
173
+ return [...target].every((id) => current.has(id));
174
+ });
175
+ return { exact, supersets: target.size ? supersets : [] };
176
+ }
177
+
178
+ async _findDuplicate(userId, type, entityIds) {
179
+ const { exact, supersets } = await this._findMatches(
180
+ userId,
181
+ type,
182
+ entityIds
183
+ );
184
+ if (exact.length) return this._pickOldest(exact);
185
+ return supersets.length ? this._pickOldest(supersets) : null;
186
+ }
187
+
188
+ // After an insert the new row is itself an exact match, so the survivor
189
+ // is the oldest of every exact or superset match (the new row included):
190
+ // an older integration that already holds these entities always wins.
191
+ async _findSurvivor(userId, type, entityIds) {
192
+ const { exact, supersets } = await this._findMatches(
193
+ userId,
194
+ type,
195
+ entityIds
196
+ );
197
+ // supersets includes the exact matches; it is empty only for an
198
+ // empty entity set, where exact matching alone applies.
199
+ const matches = supersets.length ? supersets : exact;
162
200
  return matches.length ? this._pickOldest(matches) : null;
163
201
  }
164
202
 
@@ -0,0 +1,70 @@
1
+ const {
2
+ assertEntityIdsInput,
3
+ integrationNotFound,
4
+ loadIntegration,
5
+ } = require('../utils/assert-entity-ids-input');
6
+
7
+ /**
8
+ * Detaches entities from an integration. Entities that are not attached are
9
+ * ignored, and detached entities are not deleted. Every module the
10
+ * integration holds must keep at least one entity, so the integration can
11
+ * still hydrate each of its modules.
12
+ * @class RemoveEntitiesFromIntegration
13
+ */
14
+ class RemoveEntitiesFromIntegration {
15
+ /**
16
+ * @param {Object} params
17
+ * @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
18
+ * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
19
+ */
20
+ constructor({ integrationRepository, moduleRepository }) {
21
+ this.integrationRepository = integrationRepository;
22
+ this.moduleRepository = moduleRepository;
23
+ }
24
+
25
+ /**
26
+ * @param {string} integrationId
27
+ * @param {string[]} entityIds
28
+ * @returns {Promise<Object>} The updated integration record.
29
+ */
30
+ async execute(integrationId, entityIds) {
31
+ assertEntityIdsInput(integrationId, entityIds);
32
+
33
+ const integration = await loadIntegration(
34
+ this.integrationRepository,
35
+ integrationId
36
+ );
37
+
38
+ const removing = new Set(entityIds.map(String));
39
+ const attached = await this.moduleRepository.findEntitiesByIds(
40
+ integration.entitiesIds ?? []
41
+ );
42
+ const emptied = [...new Set(attached.map((e) => e.moduleName))].filter(
43
+ (moduleName) =>
44
+ !attached.some(
45
+ (e) =>
46
+ e.moduleName === moduleName &&
47
+ !removing.has(String(e.id))
48
+ )
49
+ );
50
+ if (emptied.length > 0) {
51
+ const error = new Error(
52
+ `Removing these entities would leave no entity for module(s): ${emptied.join(
53
+ ', '
54
+ )}`
55
+ );
56
+ error.code = 'LAST_ENTITY_OF_MODULE';
57
+ throw error;
58
+ }
59
+
60
+ const updated =
61
+ await this.integrationRepository.removeEntitiesFromIntegration(
62
+ integrationId,
63
+ entityIds
64
+ );
65
+ if (!updated) throw integrationNotFound(integrationId);
66
+ return updated;
67
+ }
68
+ }
69
+
70
+ module.exports = { RemoveEntitiesFromIntegration };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Throws INVALID_INTEGRATION_DATA unless an integration id and a non-empty
3
+ * list of entity ids are given.
4
+ * @param {string} integrationId
5
+ * @param {string[]} entityIds
6
+ */
7
+ function assertEntityIdsInput(integrationId, entityIds) {
8
+ if (!integrationId || !Array.isArray(entityIds) || entityIds.length === 0) {
9
+ const error = new Error(
10
+ 'integrationId and a non-empty entityIds array are required'
11
+ );
12
+ error.code = 'INVALID_INTEGRATION_DATA';
13
+ throw error;
14
+ }
15
+ }
16
+
17
+ function integrationNotFound(integrationId) {
18
+ const error = new Error(`Integration ${integrationId} not found`);
19
+ error.code = 'INTEGRATION_NOT_FOUND';
20
+ return error;
21
+ }
22
+
23
+ /**
24
+ * Loads an integration, mapping the repositories' "not found" error to
25
+ * INTEGRATION_NOT_FOUND. Any other failure (connection, invalid id) is
26
+ * rethrown unchanged.
27
+ * @param {Object} integrationRepository
28
+ * @param {string} integrationId
29
+ * @returns {Promise<Object>} The integration record.
30
+ */
31
+ async function loadIntegration(integrationRepository, integrationId) {
32
+ try {
33
+ return await integrationRepository.findIntegrationById(integrationId);
34
+ } catch (error) {
35
+ if (/^Integration with id .* not found$/.test(error?.message)) {
36
+ throw integrationNotFound(integrationId);
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ module.exports = { assertEntityIdsInput, integrationNotFound, loadIntegration };
package/jest-setup.js CHANGED
@@ -1,2 +1,13 @@
1
- const {globalSetup} = require('@friggframework/test');
2
- module.exports = globalSetup;
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
@@ -1,2 +1,5 @@
1
- const {globalTeardown} = require('@friggframework/test');
2
- module.exports = globalTeardown;
1
+ const { restoreEnvironment } = require('@friggframework/test');
2
+
3
+ module.exports = async function () {
4
+ restoreEnvironment();
5
+ };
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
+ };
@@ -1,7 +1,4 @@
1
1
  const { Module } = require('../module');
2
- const {
3
- findModuleDefinitionForEntity,
4
- } = require('../utils/find-module-definition');
5
2
 
6
3
  class GetEntityOptionsById {
7
4
  /**
@@ -48,10 +45,11 @@ class GetEntityOptionsById {
48
45
  }
49
46
 
50
47
  const entityType = entity.moduleName;
51
- const moduleDefinition = findModuleDefinitionForEntity(
52
- this.moduleDefinitions,
53
- entity
54
- );
48
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
49
+ const modelName =
50
+ Module.getEntityModelFromDefinition(def).modelName;
51
+ return entityType === modelName;
52
+ });
55
53
 
56
54
  if (!moduleDefinition) {
57
55
  throw new Error(
@@ -1,7 +1,4 @@
1
1
  const { Module } = require('../module');
2
- const {
3
- findModuleDefinitionForEntity,
4
- } = require('../utils/find-module-definition');
5
2
 
6
3
  class GetModule {
7
4
  constructor({ moduleRepository, moduleDefinitions }) {
@@ -45,10 +42,10 @@ class GetModule {
45
42
  }
46
43
 
47
44
  const entityType = entity.moduleName;
48
- const moduleDefinition = findModuleDefinitionForEntity(
49
- this.moduleDefinitions,
50
- entity
51
- );
45
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
46
+ const modelName = Module.getEntityModelFromDefinition(def).modelName;
47
+ return entityType === modelName;
48
+ });
52
49
 
53
50
  if (!moduleDefinition) {
54
51
  throw new Error(
@@ -62,33 +59,15 @@ class GetModule {
62
59
  definition: moduleDefinition,
63
60
  });
64
61
 
65
- // The credential itself (tokens, API keys) never leaves the server:
66
- // the response names it and says whether it still works, as the v2
67
- // entity DTO does.
68
- const credential = module.credential;
69
- const credentialId =
70
- credential && typeof credential === 'object'
71
- ? credential.id
72
- : credential;
73
- const ownerId = module.entity.userId ?? module.entity.user;
62
+ // todo: this properties should be methods in the Module class
74
63
  return {
75
64
  id: module.entity.id,
76
65
  name: module.entity.name,
77
66
  moduleName: module.entity.moduleName,
78
- credentialId:
79
- credentialId === undefined || credentialId === null
80
- ? null
81
- : String(credentialId),
82
- authIsValid:
83
- typeof credential?.authIsValid === 'boolean'
84
- ? credential.authIsValid
85
- : null,
67
+ credential: module.credential,
86
68
  externalId: module.entity.externalId,
87
- userId:
88
- ownerId === undefined || ownerId === null
89
- ? null
90
- : String(ownerId),
91
- };
69
+ userId: module.entity.user.toString(),
70
+ }
92
71
  }
93
72
  }
94
73
 
@@ -1,7 +1,4 @@
1
1
  const { Module } = require('../module');
2
- const {
3
- findModuleDefinitionForEntity,
4
- } = require('../utils/find-module-definition');
5
2
 
6
3
  class RefreshEntityOptions {
7
4
  /**
@@ -49,10 +46,11 @@ class RefreshEntityOptions {
49
46
  }
50
47
 
51
48
  const entityType = entity.moduleName;
52
- const moduleDefinition = findModuleDefinitionForEntity(
53
- this.moduleDefinitions,
54
- entity
55
- );
49
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
50
+ const modelName =
51
+ Module.getEntityModelFromDefinition(def).modelName;
52
+ return entityType === modelName;
53
+ });
56
54
 
57
55
  if (!moduleDefinition) {
58
56
  throw new Error(