@friggframework/core 2.0.0-next.85 → 2.0.0-next.87

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.
@@ -25,7 +25,8 @@ const ERROR_CODE_MAP = {
25
25
  ENTITY_NOT_FOUND: 401,
26
26
  ENTITY_USER_NOT_FOUND: 401,
27
27
  INTEGRATION_NOT_FOUND: 404,
28
- EXTERNAL_ENTITY_ID_REQUIRED: 400,
28
+ EXTERNAL_ID_REQUIRED: 400,
29
+ TYPE_REQUIRED: 400,
29
30
  INTEGRATION_RECORD_NOT_FOUND: 404,
30
31
  };
31
32
 
@@ -83,14 +84,20 @@ function createIntegrationCommands({ integrationClass }) {
83
84
  });
84
85
 
85
86
  return {
86
- async findIntegrationContextByExternalEntityId(externalEntityId) {
87
+ /**
88
+ * Find integration context by external entity ID and type
89
+ * @param {Object} params
90
+ * @param {string} params.externalId - External ID of the entity
91
+ * @param {string} params.type - Integration type (config.type)
92
+ * @returns {Promise<Object>} Integration context, entity, and record
93
+ */
94
+ async findIntegrationContextByExternalEntityId({ externalId, type }) {
87
95
  try {
88
- const { context } = await findByExternalEntityIdUseCase.execute(
89
- {
90
- externalEntityId,
91
- }
92
- );
93
- return { context };
96
+ const result = await findByExternalEntityIdUseCase.execute({
97
+ externalId,
98
+ type,
99
+ });
100
+ return result;
94
101
  } catch (error) {
95
102
  return mapErrorToResponse(error);
96
103
  }
@@ -197,11 +204,12 @@ function createIntegrationCommands({ integrationClass }) {
197
204
 
198
205
  async function findIntegrationContextByExternalEntityId({
199
206
  integrationClass,
200
- externalEntityId,
207
+ externalId,
208
+ type,
201
209
  } = {}) {
202
210
  const commands = createIntegrationCommands({ integrationClass });
203
211
 
204
- return commands.findIntegrationContextByExternalEntityId(externalEntityId);
212
+ return commands.findIntegrationContextByExternalEntityId({ externalId, type });
205
213
  }
206
214
 
207
215
  module.exports = {
@@ -19,39 +19,43 @@ class FindIntegrationContextByExternalEntityIdUseCase {
19
19
  this.loadIntegrationContextUseCase = loadIntegrationContextUseCase;
20
20
  }
21
21
 
22
- async execute({ externalEntityId }) {
23
- if (!externalEntityId) {
24
- const error = new Error('externalEntityId is required');
25
- error.code = 'EXTERNAL_ENTITY_ID_REQUIRED';
22
+ async execute({ externalId, type }) {
23
+ if (!externalId) {
24
+ const error = new Error('externalId is required');
25
+ error.code = 'EXTERNAL_ID_REQUIRED';
26
+ throw error;
27
+ }
28
+
29
+ if (!type) {
30
+ const error = new Error('type is required');
31
+ error.code = 'TYPE_REQUIRED';
26
32
  throw error;
27
33
  }
28
34
 
29
35
  const entity = await this.moduleRepository.findEntity({
30
- externalId: externalEntityId,
36
+ externalId,
31
37
  });
32
38
 
33
39
  if (!entity) {
34
40
  const error = new Error(
35
- `Entity not found for externalId: ${externalEntityId}`
41
+ `Entity not found for externalId: ${externalId}`
36
42
  );
37
43
  error.code = 'ENTITY_NOT_FOUND';
38
44
  throw error;
39
45
  }
40
46
 
41
- if (!entity.userId) {
42
- const error = new Error('Entity does not have an associated user');
43
- error.code = 'ENTITY_USER_NOT_FOUND';
44
- throw error;
45
- }
46
-
47
- const integrationRecord =
48
- await this.integrationRepository.findIntegrationByUserId(
49
- entity.userId
47
+ const integrations =
48
+ await this.integrationRepository.findIntegrationsByEntityId(
49
+ entity.id
50
50
  );
51
51
 
52
+ const integrationRecord = integrations?.find(
53
+ (i) => i.config?.type === type
54
+ );
55
+
52
56
  if (!integrationRecord) {
53
57
  const error = new Error(
54
- `Integration not found for user: ${entity.userId}`
58
+ `Integration of type '${type}' not found for entity: ${entity.id}`
55
59
  );
56
60
  error.code = 'INTEGRATION_NOT_FOUND';
57
61
  throw error;
@@ -28,8 +28,9 @@
28
28
  * const updateMetrics = new UpdateProcessMetrics({ processRepository, websocketService });
29
29
  * await updateMetrics.execute(processId, {
30
30
  * processed: 100,
31
- * success: 95,
31
+ * success: 92,
32
32
  * errors: 5,
33
+ * skipped: 3,
33
34
  * errorDetails: [{ contactId: 'abc', error: 'Missing email', timestamp: '...' }]
34
35
  * });
35
36
  */
@@ -54,6 +55,11 @@ class UpdateProcessMetrics {
54
55
  * @param {number} [metricsUpdate.processed=0] - Records processed in this batch
55
56
  * @param {number} [metricsUpdate.success=0] - Successful records
56
57
  * @param {number} [metricsUpdate.errors=0] - Failed records
58
+ * @param {number} [metricsUpdate.skipped=0] - Intentionally-skipped
59
+ * records (hash-match, dedupe, loop protection, etc.). Increments
60
+ * `results.aggregateData.totalSkipped`. Distinct from errors so the
61
+ * UI can show `processed = synced + failed + skipped` without
62
+ * conflating intentional skips with failures.
57
63
  * @param {Array} [metricsUpdate.errorDetails=[]] - Error details array
58
64
  * @returns {Promise<Object>} Updated process record
59
65
  * @throws {Error} If process not found or update fails
@@ -71,9 +77,11 @@ class UpdateProcessMetrics {
71
77
  const processed = metricsUpdate.processed || 0;
72
78
  const success = metricsUpdate.success || 0;
73
79
  const errors = metricsUpdate.errors || 0;
80
+ const skipped = metricsUpdate.skipped || 0;
74
81
  if (processed) increment['context.processedRecords'] = processed;
75
82
  if (success) increment['results.aggregateData.totalSynced'] = success;
76
83
  if (errors) increment['results.aggregateData.totalFailed'] = errors;
84
+ if (skipped) increment['results.aggregateData.totalSkipped'] = skipped;
77
85
 
78
86
  const pushSlice = {};
79
87
  if (
@@ -191,6 +199,7 @@ class UpdateProcessMetrics {
191
199
  total: context.totalRecords || 0,
192
200
  successCount: aggregateData.totalSynced || 0,
193
201
  errorCount: aggregateData.totalFailed || 0,
202
+ skippedCount: aggregateData.totalSkipped || 0,
194
203
  recordsPerSecond: aggregateData.recordsPerSecond || 0,
195
204
  estimatedCompletion: context.estimatedCompletion || null,
196
205
  timestamp: new Date().toISOString(),
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.85",
4
+ "version": "2.0.0-next.87",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -38,9 +38,9 @@
38
38
  }
39
39
  },
40
40
  "devDependencies": {
41
- "@friggframework/eslint-config": "2.0.0-next.85",
42
- "@friggframework/prettier-config": "2.0.0-next.85",
43
- "@friggframework/test": "2.0.0-next.85",
41
+ "@friggframework/eslint-config": "2.0.0-next.87",
42
+ "@friggframework/prettier-config": "2.0.0-next.87",
43
+ "@friggframework/test": "2.0.0-next.87",
44
44
  "@prisma/client": "^6.17.0",
45
45
  "@types/lodash": "4.17.15",
46
46
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -80,5 +80,5 @@
80
80
  "publishConfig": {
81
81
  "access": "public"
82
82
  },
83
- "gitHead": "707cad86e779677700e84a9349791aca8db83348"
83
+ "gitHead": "ee72b6f5349ee764da595f67cc6f184a42f763f1"
84
84
  }