@friggframework/core 2.0.0--canary.459.51231dd.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 = (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.51231dd.0",
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.51231dd.0",
27
- "@friggframework/prettier-config": "2.0.0--canary.459.51231dd.0",
28
- "@friggframework/test": "2.0.0--canary.459.51231dd.0",
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": "51231ddd4b2d47e29b81294b303d1953f768f327"
67
+ "gitHead": "c38865e45dc6b0ca4ac79a49d25e0971a19ad021"
68
68
  }
@@ -46,6 +46,7 @@ model User {
46
46
  credentials Credential[]
47
47
  entities Entity[]
48
48
  integrations Integration[]
49
+ processes Process[]
49
50
 
50
51
  @@unique([email])
51
52
  @@unique([username])
@@ -164,6 +165,7 @@ model Integration {
164
165
  associations Association[]
165
166
  syncs Sync[]
166
167
  mappings IntegrationMapping[]
168
+ processes Process[]
167
169
 
168
170
  @@index([userId])
169
171
  @@index([status])
@@ -281,6 +283,49 @@ enum AssociationType {
281
283
  MANY_TO_ONE
282
284
  }
283
285
 
286
+ // ============================================================================
287
+ // PROCESS MODELS
288
+ // ============================================================================
289
+
290
+ /// Generic Process Model - tracks any long-running operation
291
+ /// Used for: CRM syncs, data migrations, bulk operations, etc.
292
+ model Process {
293
+ id Int @id @default(autoincrement())
294
+
295
+ // Core references
296
+ userId Int
297
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
298
+ integrationId Int
299
+ integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
300
+
301
+ // Process identification
302
+ name String // e.g., "zoho-crm-contact-sync", "pipedrive-lead-sync"
303
+ type String // e.g., "CRM_SYNC", "DATA_MIGRATION", "BULK_OPERATION"
304
+
305
+ // State machine
306
+ state String // Current state (integration-defined states)
307
+
308
+ // Flexible storage
309
+ context Json @default("{}") // Process-specific data (pagination, metadata, etc.)
310
+ results Json @default("{}") // Process results and metrics
311
+
312
+ // Hierarchy support - self-referential relation
313
+ parentProcessId Int?
314
+ parentProcess Process? @relation("ProcessHierarchy", fields: [parentProcessId], references: [id], onDelete: SetNull)
315
+ childProcesses Process[] @relation("ProcessHierarchy")
316
+
317
+ // Timestamps
318
+ createdAt DateTime @default(now())
319
+ updatedAt DateTime @updatedAt
320
+
321
+ @@index([userId])
322
+ @@index([integrationId])
323
+ @@index([type])
324
+ @@index([state])
325
+ @@index([name])
326
+ @@index([parentProcessId])
327
+ }
328
+
284
329
  // ============================================================================
285
330
  // UTILITY MODELS
286
331
  // ============================================================================