@friggframework/core 2.0.0--canary.459.47085f6.0 → 2.0.0--canary.459.32c3f4b.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.
@@ -170,16 +170,20 @@ class ProcessRepositoryPostgres extends ProcessRepositoryInterface {
170
170
  */
171
171
  _toPlainObject(process) {
172
172
  return {
173
- id: process.id,
174
- userId: process.userId,
175
- integrationId: process.integrationId,
173
+ id: String(process.id),
174
+ userId: String(process.userId),
175
+ integrationId: String(process.integrationId),
176
176
  name: process.name,
177
177
  type: process.type,
178
178
  state: process.state,
179
179
  context: process.context,
180
180
  results: process.results,
181
- childProcesses: process.childProcesses,
182
- parentProcessId: process.parentProcessId,
181
+ childProcesses: Array.isArray(process.childProcesses)
182
+ ? (process.childProcesses.length > 0 && typeof process.childProcesses[0] === 'object' && process.childProcesses[0] !== null
183
+ ? process.childProcesses.map(child => String(child.id))
184
+ : process.childProcesses)
185
+ : [],
186
+ parentProcessId: process.parentProcessId !== null ? String(process.parentProcessId) : null,
183
187
  createdAt: process.createdAt,
184
188
  updatedAt: process.updatedAt,
185
189
  };
@@ -1,16 +1,38 @@
1
+ /**
2
+ TODO:
3
+ This implementation contains a race condition in the `execute` method. When multiple concurrent processes call this method on the same process record, they'll each read the current state, modify it independently, and then save - potentially overwriting each other's changes.
4
+
5
+ For example:
6
+ ```
7
+ Thread 1: reads process with totalSynced=100
8
+ Thread 2: reads process with totalSynced=100
9
+ Thread 1: adds 50 → writes totalSynced=150
10
+ Thread 2: adds 30 → writes totalSynced=130 (overwrites Thread 1's update!)
11
+ ```
12
+
13
+ Consider implementing one of these patterns:
14
+ 1. Database transactions with row locking
15
+ 2. Optimistic concurrency control with version numbers
16
+ 3. Atomic update operations (e.g., `$inc` in MongoDB)
17
+ 4. A FIFO queue for process updates (as described in the PROCESS_MANAGEMENT_QUEUE_SPEC.md)
18
+
19
+ The current approach will lead to lost updates and inconsistent metrics during concurrent processing.
20
+
21
+ */
22
+
1
23
  /**
2
24
  * UpdateProcessMetrics Use Case
3
- *
25
+ *
4
26
  * Updates process metrics, calculates aggregates, and computes estimated completion time.
5
27
  * Optionally broadcasts progress via WebSocket service if provided.
6
- *
28
+ *
7
29
  * Design Philosophy:
8
30
  * - Metrics are cumulative (add to existing counts)
9
31
  * - Performance metrics calculated automatically (duration, records/sec)
10
32
  * - ETA computed based on current progress
11
33
  * - Error history limited to last 100 entries
12
34
  * - WebSocket broadcasting is optional (DI pattern)
13
- *
35
+ *
14
36
  * @example
15
37
  * const updateMetrics = new UpdateProcessMetrics({ processRepository, websocketService });
16
38
  * await updateMetrics.execute(processId, {
@@ -70,17 +92,25 @@ class UpdateProcessMetrics {
70
92
  }
71
93
 
72
94
  // Update context counters (cumulative)
73
- context.processedRecords = (context.processedRecords || 0) + (metricsUpdate.processed || 0);
95
+ context.processedRecords =
96
+ (context.processedRecords || 0) + (metricsUpdate.processed || 0);
74
97
 
75
98
  // Update results aggregates (cumulative)
76
- results.aggregateData.totalSynced = (results.aggregateData.totalSynced || 0) + (metricsUpdate.success || 0);
77
- results.aggregateData.totalFailed = (results.aggregateData.totalFailed || 0) + (metricsUpdate.errors || 0);
99
+ results.aggregateData.totalSynced =
100
+ (results.aggregateData.totalSynced || 0) +
101
+ (metricsUpdate.success || 0);
102
+ results.aggregateData.totalFailed =
103
+ (results.aggregateData.totalFailed || 0) +
104
+ (metricsUpdate.errors || 0);
78
105
 
79
106
  // Append error details (limited to last 100)
80
- if (metricsUpdate.errorDetails && metricsUpdate.errorDetails.length > 0) {
107
+ if (
108
+ metricsUpdate.errorDetails &&
109
+ metricsUpdate.errorDetails.length > 0
110
+ ) {
81
111
  results.aggregateData.errors = [
82
112
  ...(results.aggregateData.errors || []),
83
- ...metricsUpdate.errorDetails
113
+ ...metricsUpdate.errorDetails,
84
114
  ].slice(-100); // Keep only last 100 errors
85
115
  }
86
116
 
@@ -90,7 +120,8 @@ class UpdateProcessMetrics {
90
120
  results.aggregateData.duration = elapsed;
91
121
 
92
122
  if (elapsed > 0 && context.processedRecords > 0) {
93
- results.aggregateData.recordsPerSecond = context.processedRecords / (elapsed / 1000);
123
+ results.aggregateData.recordsPerSecond =
124
+ context.processedRecords / (elapsed / 1000);
94
125
  } else {
95
126
  results.aggregateData.recordsPerSecond = 0;
96
127
  }
@@ -99,7 +130,8 @@ class UpdateProcessMetrics {
99
130
  if (context.totalRecords > 0 && context.processedRecords > 0) {
100
131
  const remaining = context.totalRecords - context.processedRecords;
101
132
  if (results.aggregateData.recordsPerSecond > 0) {
102
- const etaMs = (remaining / results.aggregateData.recordsPerSecond) * 1000;
133
+ const etaMs =
134
+ (remaining / results.aggregateData.recordsPerSecond) * 1000;
103
135
  const eta = new Date(Date.now() + etaMs);
104
136
  context.estimatedCompletion = eta.toISOString();
105
137
  }
@@ -114,9 +146,14 @@ class UpdateProcessMetrics {
114
146
  // Persist updates
115
147
  let updatedProcess;
116
148
  try {
117
- updatedProcess = await this.processRepository.update(processId, updates);
149
+ updatedProcess = await this.processRepository.update(
150
+ processId,
151
+ updates
152
+ );
118
153
  } catch (error) {
119
- throw new Error(`Failed to update process metrics: ${error.message}`);
154
+ throw new Error(
155
+ `Failed to update process metrics: ${error.message}`
156
+ );
120
157
  }
121
158
 
122
159
  // Broadcast progress via WebSocket (if service provided)
@@ -152,7 +189,7 @@ class UpdateProcessMetrics {
152
189
  recordsPerSecond: aggregateData.recordsPerSecond || 0,
153
190
  estimatedCompletion: context.estimatedCompletion || null,
154
191
  timestamp: new Date().toISOString(),
155
- }
192
+ },
156
193
  });
157
194
  } catch (error) {
158
195
  // Log but don't fail the update if WebSocket broadcast fails
@@ -162,4 +199,3 @@ class UpdateProcessMetrics {
162
199
  }
163
200
 
164
201
  module.exports = { UpdateProcessMetrics };
165
-
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.459.47085f6.0",
4
+ "version": "2.0.0--canary.459.32c3f4b.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "@prisma/client": "^6.16.3",
@@ -23,9 +23,9 @@
23
23
  "uuid": "^9.0.1"
24
24
  },
25
25
  "devDependencies": {
26
- "@friggframework/eslint-config": "2.0.0--canary.459.47085f6.0",
27
- "@friggframework/prettier-config": "2.0.0--canary.459.47085f6.0",
28
- "@friggframework/test": "2.0.0--canary.459.47085f6.0",
26
+ "@friggframework/eslint-config": "2.0.0--canary.459.32c3f4b.0",
27
+ "@friggframework/prettier-config": "2.0.0--canary.459.32c3f4b.0",
28
+ "@friggframework/test": "2.0.0--canary.459.32c3f4b.0",
29
29
  "@types/lodash": "4.17.15",
30
30
  "@typescript-eslint/eslint-plugin": "^8.0.0",
31
31
  "chai": "^4.3.6",
@@ -64,5 +64,5 @@
64
64
  "publishConfig": {
65
65
  "access": "public"
66
66
  },
67
- "gitHead": "47085f6facf01bcd5a67cabc7b47c21d5e881e70"
67
+ "gitHead": "32c3f4bf28280b643a866fcb7811a82cdcc6004e"
68
68
  }