@friggframework/core 2.0.0--canary.710.91495fa.0 → 2.0.0--canary.667.f74cf3b.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 (44) hide show
  1. package/CLAUDE.md +1 -41
  2. package/application/commands/README.md +11 -17
  3. package/application/commands/entity-commands.js +60 -18
  4. package/application/commands/integration-commands.js +0 -63
  5. package/database/documentdb-utils.js +1 -4
  6. package/database/encryption/README.md +3 -3
  7. package/database/encryption/encryption-schema-registry.js +7 -27
  8. package/errors/index.js +0 -4
  9. package/handlers/WEBHOOKS.md +0 -29
  10. package/index.js +0 -2
  11. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +13 -67
  12. package/integrations/integration-base.js +0 -29
  13. package/integrations/integration-router.js +18 -22
  14. package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
  15. package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
  16. package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
  17. package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
  18. package/integrations/repositories/integration-mapping-repository.js +0 -29
  19. package/integrations/repositories/integration-repository-documentdb.js +0 -32
  20. package/integrations/repositories/integration-repository-interface.js +0 -30
  21. package/integrations/repositories/integration-repository-mongo.js +1 -53
  22. package/integrations/repositories/integration-repository-postgres.js +0 -58
  23. package/integrations/repositories/process-repository-mongo.js +1 -1
  24. package/integrations/use-cases/create-integration.js +19 -57
  25. package/jest-setup.js +2 -13
  26. package/jest-teardown.js +2 -5
  27. package/jest.config.js +0 -4
  28. package/modules/use-cases/create-entity-from-credential.js +105 -0
  29. package/modules/use-cases/get-entity-options-by-id.js +7 -5
  30. package/modules/use-cases/get-module.js +29 -8
  31. package/modules/use-cases/refresh-entity-options.js +7 -5
  32. package/modules/use-cases/test-module-auth.js +7 -5
  33. package/modules/utils/find-module-definition.js +23 -0
  34. package/modules/utils/map-module-dto.js +3 -5
  35. package/package.json +5 -7
  36. package/queues/queuer-util.js +5 -6
  37. package/user/tests/doubles/test-user-repository.js +1 -1
  38. package/user/use-cases/get-user-from-bearer-token.js +2 -7
  39. package/errors/mapping-already-exists-error.js +0 -14
  40. package/integrations/use-cases/add-entities-to-integration.js +0 -92
  41. package/integrations/use-cases/remove-entities-from-integration.js +0 -70
  42. package/integrations/utils/assert-entity-ids-input.js +0 -42
  43. package/jest.db.config.js +0 -23
  44. package/modules/utils/to-credential-id.js +0 -14
@@ -42,6 +42,9 @@ const {
42
42
  } = require('../modules/use-cases/get-entity-options-by-type');
43
43
  const { TestModuleAuth } = require('../modules/use-cases/test-module-auth');
44
44
  const { GetModule } = require('../modules/use-cases/get-module');
45
+ const {
46
+ CreateEntityFromCredential,
47
+ } = require('../modules/use-cases/create-entity-from-credential');
45
48
  const {
46
49
  GetEntityOptionsById,
47
50
  } = require('../modules/use-cases/get-entity-options-by-id');
@@ -198,6 +201,13 @@ function createIntegrationRouter() {
198
201
  getModulesDefinitionFromIntegrationClasses(integrationClasses),
199
202
  });
200
203
 
204
+ const createEntityFromCredential = new CreateEntityFromCredential({
205
+ processAuthorizationCallback,
206
+ credentialRepository,
207
+ moduleDefinitions:
208
+ getModulesDefinitionFromIntegrationClasses(integrationClasses),
209
+ });
210
+
201
211
  const router = express();
202
212
 
203
213
  setIntegrationRoutes(router, authenticateUser, {
@@ -218,6 +228,7 @@ function createIntegrationRouter() {
218
228
  getEntityOptionsById,
219
229
  refreshEntityOptions,
220
230
  processAuthorizationCallback,
231
+ createEntityFromCredential,
221
232
  });
222
233
  return router;
223
234
  }
@@ -502,6 +513,7 @@ function setEntityRoutes(router, authenticateUser, useCases) {
502
513
  getEntityOptionsById,
503
514
  refreshEntityOptions,
504
515
  processAuthorizationCallback,
516
+ createEntityFromCredential,
505
517
  } = useCases;
506
518
 
507
519
  router.route('/api/authorize').get(
@@ -566,35 +578,19 @@ function setEntityRoutes(router, authenticateUser, useCases) {
566
578
  router.route('/api/entity').post(
567
579
  catchAsyncError(async (req, res) => {
568
580
  const user = await authenticateUser.execute(req);
569
- const userId = user.getId();
570
581
  const params = checkRequiredParams(req.body, [
571
582
  'entityType',
572
583
  'data',
573
584
  ]);
574
585
  checkRequiredParams(req.body.data, ['credential_id']);
575
586
 
576
- // May want to pass along the user ID as well so credential ID's can't be fished???
577
- const credential = await getCredentialForUser.execute(
578
- params.data.credential_id,
579
- userId
580
- );
581
-
582
- if (!credential) {
583
- throw Boom.badRequest('Invalid credential ID');
584
- }
585
-
586
- const module = await getModuleInstanceFromType.execute(
587
- userId,
588
- params.entityType
587
+ res.json(
588
+ await createEntityFromCredential.execute({
589
+ user,
590
+ entityType: params.entityType,
591
+ credentialId: params.data.credential_id,
592
+ })
589
593
  );
590
- const entityDetails = await module.getEntityDetails(
591
- module.api,
592
- null,
593
- null,
594
- userId
595
- );
596
-
597
- res.json(await module.findOrCreateEntity(entityDetails));
598
594
  })
599
595
  );
600
596
 
@@ -1,5 +1,4 @@
1
1
  const { prisma } = require('../../database/prisma');
2
- const { MappingAlreadyExistsError } = require('../../errors');
3
2
  const {
4
3
  toObjectId,
5
4
  fromObjectId,
@@ -181,55 +180,6 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
181
180
  return this._mapMapping(decryptedMapping);
182
181
  }
183
182
 
184
- async createMapping(integrationId, sourceId, mapping) {
185
- const now = new Date();
186
- const encryptedDocument = await this.encryptionService.encryptFields(
187
- 'IntegrationMapping',
188
- {
189
- integrationId,
190
- sourceId:
191
- sourceId === null || sourceId === undefined
192
- ? null
193
- : String(sourceId),
194
- mapping,
195
- createdAt: now,
196
- updatedAt: now,
197
- }
198
- );
199
-
200
- let insertedId;
201
- try {
202
- insertedId = await insertOne(
203
- this.prisma,
204
- 'IntegrationMapping',
205
- encryptedDocument
206
- );
207
- } catch (error) {
208
- if (error.code === 11000) {
209
- throw new MappingAlreadyExistsError(integrationId, sourceId, {
210
- cause: error,
211
- });
212
- }
213
- throw error;
214
- }
215
-
216
- const created = await findOne(this.prisma, 'IntegrationMapping', {
217
- _id: insertedId,
218
- });
219
- if (!created) {
220
- throw new Error(
221
- 'Failed to create mapping: Document not found after insert. ' +
222
- 'This indicates a database consistency issue.'
223
- );
224
- }
225
- return this._mapMapping(
226
- await this.encryptionService.decryptFields(
227
- 'IntegrationMapping',
228
- created
229
- )
230
- );
231
- }
232
-
233
183
  async findMappingsByIntegration(integrationId) {
234
184
  const filter = {};
235
185
  if (integrationId) filter.integrationId = integrationId;
@@ -39,21 +39,6 @@ class IntegrationMappingRepositoryInterface {
39
39
  throw new Error('Method upsertMapping must be implemented by subclass');
40
40
  }
41
41
 
42
- /**
43
- * Insert a mapping, failing if one already exists for the
44
- * (integrationId, sourceId) pair. Atomic: usable as a claim/lock row.
45
- *
46
- * @param {string|number} integrationId - The integration ID
47
- * @param {string} sourceId - The source ID for lookup
48
- * @param {Object} mapping - The mapping data
49
- * @returns {Promise<Object>} The created mapping document
50
- * @throws {MappingAlreadyExistsError} If the pair already exists
51
- * @abstract
52
- */
53
- async createMapping(integrationId, sourceId, mapping) {
54
- throw new Error('createMapping must be implemented by subclass');
55
- }
56
-
57
42
  /**
58
43
  * Find all mappings for an integration
59
44
  *
@@ -1,5 +1,4 @@
1
1
  const { prisma } = require('../../database/prisma');
2
- const { MappingAlreadyExistsError } = require('../../errors');
3
2
  const {
4
3
  assertMappingWrittenUnencrypted,
5
4
  decryptQueriedMappings,
@@ -94,34 +93,6 @@ class IntegrationMappingRepositoryMongo extends IntegrationMappingRepositoryInte
94
93
  });
95
94
  }
96
95
 
97
- /**
98
- * Insert a mapping; fails if (integrationId, sourceId) already exists
99
- *
100
- * @param {string} integrationId - The integration ID
101
- * @param {string} sourceId - The source ID
102
- * @param {Object} mapping - The mapping data
103
- * @returns {Promise<Object>} The created mapping document
104
- * @throws {MappingAlreadyExistsError} If the pair already exists
105
- */
106
- async createMapping(integrationId, sourceId, mapping) {
107
- try {
108
- return await this.prisma.integrationMapping.create({
109
- data: {
110
- integrationId,
111
- sourceId: this._toString(sourceId),
112
- mapping,
113
- },
114
- });
115
- } catch (error) {
116
- if (error.code === 'P2002') {
117
- throw new MappingAlreadyExistsError(integrationId, sourceId, {
118
- cause: error,
119
- });
120
- }
121
- throw error;
122
- }
123
- }
124
-
125
96
  /**
126
97
  * Find all mappings for an integration
127
98
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -1,5 +1,4 @@
1
1
  const { prisma } = require('../../database/prisma');
2
- const { MappingAlreadyExistsError } = require('../../errors');
3
2
  const {
4
3
  assertMappingWrittenUnencrypted,
5
4
  decryptQueriedMappings,
@@ -151,35 +150,6 @@ class IntegrationMappingRepositoryPostgres extends IntegrationMappingRepositoryI
151
150
  return this._convertMappingIds(result);
152
151
  }
153
152
 
154
- /**
155
- * Insert a mapping; fails if (integrationId, sourceId) already exists
156
- *
157
- * @param {string} integrationId - The integration ID
158
- * @param {string} sourceId - The source ID
159
- * @param {Object} mapping - The mapping data
160
- * @returns {Promise<Object>} The created mapping document
161
- * @throws {MappingAlreadyExistsError} If the pair already exists
162
- */
163
- async createMapping(integrationId, sourceId, mapping) {
164
- try {
165
- const created = await this.prisma.integrationMapping.create({
166
- data: {
167
- integrationId: this._stringToInt(integrationId),
168
- sourceId: this._toString(sourceId),
169
- mapping,
170
- },
171
- });
172
- return this._convertMappingIds(created);
173
- } catch (error) {
174
- if (error.code === 'P2002') {
175
- throw new MappingAlreadyExistsError(integrationId, sourceId, {
176
- cause: error,
177
- });
178
- }
179
- throw error;
180
- }
181
- }
182
-
183
153
  /**
184
154
  * Find all mappings for an integration
185
155
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -1,5 +1,4 @@
1
1
  const { prisma } = require('../../database/prisma');
2
- const { MappingAlreadyExistsError } = require('../../errors');
3
2
  const {
4
3
  IntegrationMappingRepositoryInterface,
5
4
  } = require('./integration-mapping-repository-interface');
@@ -70,34 +69,6 @@ class IntegrationMappingRepository extends IntegrationMappingRepositoryInterface
70
69
  });
71
70
  }
72
71
 
73
- /**
74
- * Insert a mapping; fails if (integrationId, sourceId) already exists
75
- *
76
- * @param {string} integrationId - The integration ID
77
- * @param {string} sourceId - The source ID
78
- * @param {Object} mapping - The mapping data
79
- * @returns {Promise<Object>} The created mapping document
80
- * @throws {MappingAlreadyExistsError} If the pair already exists
81
- */
82
- async createMapping(integrationId, sourceId, mapping) {
83
- try {
84
- return await this.prisma.integrationMapping.create({
85
- data: {
86
- integrationId,
87
- sourceId,
88
- mapping,
89
- },
90
- });
91
- } catch (error) {
92
- if (error.code === 'P2002') {
93
- throw new MappingAlreadyExistsError(integrationId, sourceId, {
94
- cause: error,
95
- });
96
- }
97
- throw error;
98
- }
99
- }
100
-
101
72
  /**
102
73
  * Find all mappings for an integration
103
74
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -164,38 +164,6 @@ 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 result = await updateOne(
171
- this.prisma,
172
- 'Integration',
173
- { _id: objectId },
174
- {
175
- $addToSet: { entityIds: { $each: toObjectIdArray(entityIds) } },
176
- $set: { updatedAt: { $date: new Date().toISOString() } },
177
- }
178
- );
179
- if (!result?.n) return null;
180
- return this.findIntegrationById(integrationId);
181
- }
182
-
183
- async removeEntitiesFromIntegration(integrationId, entityIds) {
184
- const objectId = toObjectId(integrationId);
185
- if (!objectId) return null;
186
- const result = await updateOne(
187
- this.prisma,
188
- 'Integration',
189
- { _id: objectId },
190
- {
191
- $pullAll: { entityIds: toObjectIdArray(entityIds) },
192
- $set: { updatedAt: { $date: new Date().toISOString() } },
193
- }
194
- );
195
- if (!result?.n) return null;
196
- return this.findIntegrationById(integrationId);
197
- }
198
-
199
167
  async findIntegrationByUserId(userId) {
200
168
  const objectId = toObjectId(userId);
201
169
  if (!objectId) return null;
@@ -184,36 +184,6 @@ 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
- }
217
187
  }
218
188
 
219
189
  module.exports = { IntegrationRepositoryInterface };
@@ -338,58 +338,6 @@ 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
-
393
341
  /**
394
342
  * Find integration by user ID (returns single integration)
395
343
  * Replaces: IntegrationModel.findOne({ user: userId }).populate('entities')
@@ -471,7 +419,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
471
419
  for (const [key, value] of Object.entries(patch)) {
472
420
  $set[`config.${key}`] = value;
473
421
  }
474
- $set.updatedAt = { $date: new Date().toISOString() };
422
+ $set.updatedAt = new Date();
475
423
 
476
424
  const result = await this.prisma.$runCommandRaw({
477
425
  findAndModify: 'Integration',
@@ -365,64 +365,6 @@ 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
-
426
368
  /**
427
369
  * Find integration by user ID (returns single integration)
428
370
  *
@@ -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 = { $date: new Date().toISOString() };
123
+ $set.updatedAt = new Date();
124
124
  update.$set = $set;
125
125
 
126
126
  if (Object.keys(normalized.pushSlice).length > 0) {
@@ -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._findSurvivor(
65
+ const survivor = await this._findDuplicate(
66
66
  userId,
67
67
  config?.type,
68
68
  entities
@@ -135,68 +135,30 @@ class CreateIntegration {
135
135
  return mapIntegrationClassToIntegrationDTO(integrationInstance);
136
136
  }
137
137
 
138
- async _findMatches(userId, type, entityIds) {
138
+ async _findDuplicate(userId, type, entityIds) {
139
139
  const candidates =
140
140
  await this.integrationRepository.findIntegrationsByUserId(userId);
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);
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();
161
156
  return (
162
- current.size === target.size &&
163
- [...target].every((id) => current.has(id))
157
+ current.length === target.length &&
158
+ current.every((id, index) => id === target[index])
164
159
  );
165
160
  });
166
161
 
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;
200
162
  return matches.length ? this._pickOldest(matches) : null;
201
163
  }
202
164
 
package/jest-setup.js CHANGED
@@ -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
- };
1
+ const {globalSetup} = require('@friggframework/test');
2
+ module.exports = globalSetup;
package/jest-teardown.js CHANGED
@@ -1,5 +1,2 @@
1
- const { restoreEnvironment } = require('@friggframework/test');
2
-
3
- module.exports = async function () {
4
- restoreEnvironment();
5
- };
1
+ const {globalTeardown} = require('@friggframework/test');
2
+ module.exports = globalTeardown;
package/jest.config.js CHANGED
@@ -20,8 +20,4 @@ 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$'],
27
23
  };