@friggframework/core 2.0.0--canary.459.47085f6.0 → 2.0.0--canary.459.c38865e.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.
|
@@ -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 =
|
|
95
|
+
context.processedRecords =
|
|
96
|
+
(context.processedRecords || 0) + (metricsUpdate.processed || 0);
|
|
74
97
|
|
|
75
98
|
// Update results aggregates (cumulative)
|
|
76
|
-
results.aggregateData.totalSynced =
|
|
77
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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(
|
|
149
|
+
updatedProcess = await this.processRepository.update(
|
|
150
|
+
processId,
|
|
151
|
+
updates
|
|
152
|
+
);
|
|
118
153
|
} catch (error) {
|
|
119
|
-
throw new Error(
|
|
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.
|
|
4
|
+
"version": "2.0.0--canary.459.c38865e.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.
|
|
27
|
-
"@friggframework/prettier-config": "2.0.0--canary.459.
|
|
28
|
-
"@friggframework/test": "2.0.0--canary.459.
|
|
26
|
+
"@friggframework/eslint-config": "2.0.0--canary.459.c38865e.0",
|
|
27
|
+
"@friggframework/prettier-config": "2.0.0--canary.459.c38865e.0",
|
|
28
|
+
"@friggframework/test": "2.0.0--canary.459.c38865e.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": "
|
|
67
|
+
"gitHead": "c38865e45dc6b0ca4ac79a49d25e0971a19ad021"
|
|
68
68
|
}
|