@friggframework/core 2.0.0-next.106 → 2.0.0-next.107

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.
@@ -16,11 +16,13 @@
16
16
  *
17
17
  * 2. Derived-fields phase — duration, recordsPerSecond,
18
18
  * estimatedCompletion. Computed from the post-atomic snapshot and
19
- * written via the legacy (non-atomic) `update()` method.
20
- * Intentionally best-effort: under concurrent writers they reflect
21
- * "whichever handler wrote last" — the same semantics they had
22
- * before and all they've ever guaranteed. Preserved for backward
23
- * compatibility with consumers (UI, WebSocket listeners).
19
+ * written through the same atomic primitive, scoped to only those
20
+ * three paths. The values themselves stay best-effort (under
21
+ * concurrent writers they reflect "whichever handler wrote last"),
22
+ * but the write can no longer clobber another caller's counters.
23
+ * Writing the full context/results blob here — as the legacy
24
+ * `update()` path did — replayed phase 1's snapshot over the row and
25
+ * permanently discarded increments landed in between.
24
26
  *
25
27
  * Optionally broadcasts progress via WebSocket service if provided.
26
28
  *
@@ -124,44 +126,48 @@ class UpdateProcessMetrics {
124
126
  throw processNotFound(`Process not found: ${processId}`);
125
127
  }
126
128
 
127
- // Phase 2: derived metrics (non-atomic, best-effort). Preserved
128
- // for backward compatibility these were always stale under
129
- // concurrent writers even before this refactor.
129
+ // Phase 2: derived metrics. Written through the SAME atomic path
130
+ // as phase 1, touching ONLY the paths this phase owns. Writing the
131
+ // whole context/results blob here (as the legacy `update()` did)
132
+ // replayed phase 1's snapshot over the row and silently discarded
133
+ // any increment a concurrent caller had landed in between.
130
134
  const context = updatedProcess.context || {};
131
- const results = updatedProcess.results || { aggregateData: {} };
132
- if (!results.aggregateData) results.aggregateData = {};
133
135
 
134
136
  if (context.processedRecords > 0 || context.totalRecords > 0) {
135
137
  const startTime = new Date(
136
138
  context.startTime || updatedProcess.createdAt
137
139
  );
138
140
  const elapsed = Date.now() - startTime.getTime();
139
- results.aggregateData.duration = elapsed;
140
141
 
141
- if (elapsed > 0 && context.processedRecords > 0) {
142
- results.aggregateData.recordsPerSecond =
143
- context.processedRecords / (elapsed / 1000);
144
- } else {
145
- results.aggregateData.recordsPerSecond = 0;
146
- }
142
+ const recordsPerSecond =
143
+ elapsed > 0 && context.processedRecords > 0
144
+ ? context.processedRecords / (elapsed / 1000)
145
+ : 0;
146
+
147
+ const set = {
148
+ 'results.aggregateData.duration': elapsed,
149
+ 'results.aggregateData.recordsPerSecond': recordsPerSecond,
150
+ };
147
151
 
148
- if (context.totalRecords > 0 && context.processedRecords > 0) {
152
+ if (
153
+ context.totalRecords > 0 &&
154
+ context.processedRecords > 0 &&
155
+ recordsPerSecond > 0
156
+ ) {
149
157
  const remaining =
150
158
  context.totalRecords - context.processedRecords;
151
- if (results.aggregateData.recordsPerSecond > 0) {
152
- const etaMs =
153
- (remaining / results.aggregateData.recordsPerSecond) *
154
- 1000;
155
- const eta = new Date(Date.now() + etaMs);
156
- context.estimatedCompletion = eta.toISOString();
157
- }
159
+ const etaMs = (remaining / recordsPerSecond) * 1000;
160
+ set['context.estimatedCompletion'] = new Date(
161
+ Date.now() + etaMs
162
+ ).toISOString();
158
163
  }
159
164
 
160
165
  try {
161
- updatedProcess = await this.processRepository.update(
162
- processId,
163
- { context, results }
164
- );
166
+ const withDerived =
167
+ await this.processRepository.applyProcessUpdate(processId, {
168
+ set,
169
+ });
170
+ if (withDerived) updatedProcess = withDerived;
165
171
  } catch (error) {
166
172
  // Derived-field write failures are NON-FATAL — atomic
167
173
  // counters from phase 1 already landed. Log and return the
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.106",
4
+ "version": "2.0.0-next.107",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -48,9 +48,9 @@
48
48
  }
49
49
  },
50
50
  "devDependencies": {
51
- "@friggframework/eslint-config": "2.0.0-next.106",
52
- "@friggframework/prettier-config": "2.0.0-next.106",
53
- "@friggframework/test": "2.0.0-next.106",
51
+ "@friggframework/eslint-config": "2.0.0-next.107",
52
+ "@friggframework/prettier-config": "2.0.0-next.107",
53
+ "@friggframework/test": "2.0.0-next.107",
54
54
  "@prisma/client": "^6.19.3",
55
55
  "@types/lodash": "4.17.15",
56
56
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -90,5 +90,5 @@
90
90
  "publishConfig": {
91
91
  "access": "public"
92
92
  },
93
- "gitHead": "979c1a62fe226206ec3801d1d7aacbfe2a8fac78"
93
+ "gitHead": "27ac136ac4fc2da4c9d9806ee548a8f8c49eaa15"
94
94
  }