@jackchuka/gql-ingest 1.0.2 → 1.2.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.
@@ -0,0 +1,207 @@
1
+ import { MetricsCollector } from './metrics';
2
+
3
+ describe('MetricsCollector', () => {
4
+ let metrics: MetricsCollector;
5
+
6
+ beforeEach(() => {
7
+ metrics = new MetricsCollector();
8
+ });
9
+
10
+ describe('initialization', () => {
11
+ it('should initialize with zero counts', () => {
12
+ expect(metrics.getTotalProcessed()).toBe(0);
13
+ expect(metrics.getSuccessRate()).toBe(0);
14
+ });
15
+
16
+ it('should have a start time', () => {
17
+ expect(metrics.getDurationMs()).toBeGreaterThanOrEqual(0);
18
+ });
19
+ });
20
+
21
+ describe('entity processing', () => {
22
+ it('should start entity processing', () => {
23
+ metrics.startEntityProcessing('testEntity');
24
+ const entityMetric = metrics.getEntityMetrics('testEntity');
25
+
26
+ expect(entityMetric).toBeDefined();
27
+ expect(entityMetric?.entityName).toBe('testEntity');
28
+ expect(entityMetric?.successCount).toBe(0);
29
+ expect(entityMetric?.failureCount).toBe(0);
30
+ expect(entityMetric?.startTime).toBeGreaterThan(0);
31
+ expect(entityMetric?.endTime).toBeUndefined();
32
+ });
33
+
34
+ it('should not create duplicate entity metrics', () => {
35
+ metrics.startEntityProcessing('testEntity');
36
+ metrics.startEntityProcessing('testEntity');
37
+
38
+ const entityMetric = metrics.getEntityMetrics('testEntity');
39
+ expect(entityMetric).toBeDefined();
40
+ });
41
+
42
+ it('should finish entity processing', () => {
43
+ metrics.startEntityProcessing('testEntity');
44
+ metrics.finishEntityProcessing('testEntity');
45
+
46
+ const entityMetric = metrics.getEntityMetrics('testEntity');
47
+ expect(entityMetric?.endTime).toBeGreaterThan(0);
48
+ });
49
+ });
50
+
51
+ describe('success and failure recording', () => {
52
+ beforeEach(() => {
53
+ metrics.startEntityProcessing('testEntity');
54
+ });
55
+
56
+ it('should record successes', () => {
57
+ metrics.recordSuccess('testEntity');
58
+ metrics.recordSuccess('testEntity');
59
+
60
+ const entityMetric = metrics.getEntityMetrics('testEntity');
61
+ expect(entityMetric?.successCount).toBe(2);
62
+ expect(entityMetric?.failureCount).toBe(0);
63
+ expect(metrics.getTotalProcessed()).toBe(2);
64
+ });
65
+
66
+ it('should record failures', () => {
67
+ metrics.recordFailure('testEntity');
68
+ metrics.recordFailure('testEntity');
69
+
70
+ const entityMetric = metrics.getEntityMetrics('testEntity');
71
+ expect(entityMetric?.successCount).toBe(0);
72
+ expect(entityMetric?.failureCount).toBe(2);
73
+ expect(metrics.getTotalProcessed()).toBe(2);
74
+ });
75
+
76
+ it('should record mixed results', () => {
77
+ metrics.recordSuccess('testEntity');
78
+ metrics.recordFailure('testEntity');
79
+ metrics.recordSuccess('testEntity');
80
+
81
+ const entityMetric = metrics.getEntityMetrics('testEntity');
82
+ expect(entityMetric?.successCount).toBe(2);
83
+ expect(entityMetric?.failureCount).toBe(1);
84
+ expect(metrics.getTotalProcessed()).toBe(3);
85
+ });
86
+
87
+ it('should handle unknown entity gracefully', () => {
88
+ expect(() => {
89
+ metrics.recordSuccess('unknownEntity');
90
+ }).not.toThrow();
91
+
92
+ expect(metrics.getTotalProcessed()).toBe(0);
93
+ });
94
+ });
95
+
96
+ describe('multiple entities', () => {
97
+ beforeEach(() => {
98
+ metrics.startEntityProcessing('entity1');
99
+ metrics.startEntityProcessing('entity2');
100
+ });
101
+
102
+ it('should track multiple entities separately', () => {
103
+ metrics.recordSuccess('entity1');
104
+ metrics.recordSuccess('entity1');
105
+ metrics.recordFailure('entity2');
106
+
107
+ const entity1Metrics = metrics.getEntityMetrics('entity1');
108
+ const entity2Metrics = metrics.getEntityMetrics('entity2');
109
+
110
+ expect(entity1Metrics?.successCount).toBe(2);
111
+ expect(entity1Metrics?.failureCount).toBe(0);
112
+ expect(entity2Metrics?.successCount).toBe(0);
113
+ expect(entity2Metrics?.failureCount).toBe(1);
114
+ expect(metrics.getTotalProcessed()).toBe(3);
115
+ });
116
+ });
117
+
118
+ describe('success rate calculation', () => {
119
+ beforeEach(() => {
120
+ metrics.startEntityProcessing('testEntity');
121
+ });
122
+
123
+ it('should calculate 100% success rate', () => {
124
+ metrics.recordSuccess('testEntity');
125
+ metrics.recordSuccess('testEntity');
126
+
127
+ expect(metrics.getSuccessRate()).toBe(100);
128
+ });
129
+
130
+ it('should calculate 0% success rate', () => {
131
+ metrics.recordFailure('testEntity');
132
+ metrics.recordFailure('testEntity');
133
+
134
+ expect(metrics.getSuccessRate()).toBe(0);
135
+ });
136
+
137
+ it('should calculate partial success rate', () => {
138
+ metrics.recordSuccess('testEntity');
139
+ metrics.recordFailure('testEntity');
140
+ metrics.recordFailure('testEntity');
141
+
142
+ expect(metrics.getSuccessRate()).toBeCloseTo(33.3, 1);
143
+ });
144
+
145
+ it('should return 0% for no processed items', () => {
146
+ expect(metrics.getSuccessRate()).toBe(0);
147
+ });
148
+ });
149
+
150
+ describe('summary generation', () => {
151
+ it('should generate summary for empty metrics', () => {
152
+ const summary = metrics.generateSummary();
153
+
154
+ expect(summary).toContain('Total Processed: 0');
155
+ expect(summary).toContain('Successes: 0');
156
+ expect(summary).toContain('Failures: 0');
157
+ expect(summary).toContain('Success Rate: 0.0%');
158
+ expect(summary).toContain('Duration:');
159
+ });
160
+
161
+ it('should generate summary with single entity', () => {
162
+ metrics.startEntityProcessing('testEntity');
163
+ metrics.recordSuccess('testEntity');
164
+ metrics.recordFailure('testEntity');
165
+ metrics.finishEntityProcessing('testEntity');
166
+
167
+ const summary = metrics.generateSummary();
168
+
169
+ expect(summary).toContain('Total Processed: 2');
170
+ expect(summary).toContain('Successes: 1');
171
+ expect(summary).toContain('Failures: 1');
172
+ expect(summary).toContain('Success Rate: 50.0%');
173
+ });
174
+
175
+ it('should generate summary with multiple entities', () => {
176
+ metrics.startEntityProcessing('entity1');
177
+ metrics.recordSuccess('entity1');
178
+ metrics.finishEntityProcessing('entity1');
179
+
180
+ metrics.startEntityProcessing('entity2');
181
+ metrics.recordFailure('entity2');
182
+ metrics.finishEntityProcessing('entity2');
183
+
184
+ const summary = metrics.generateSummary();
185
+
186
+ expect(summary).toContain('Total Processed: 2');
187
+ expect(summary).toContain('Per-Entity Breakdown');
188
+ expect(summary).toContain('entity1:');
189
+ expect(summary).toContain('entity2:');
190
+ });
191
+ });
192
+
193
+ describe('finish processing', () => {
194
+ it('should set end time and return metrics', () => {
195
+ metrics.startEntityProcessing('testEntity');
196
+ metrics.recordSuccess('testEntity');
197
+
198
+ const finalMetrics = metrics.finishProcessing();
199
+
200
+ expect(finalMetrics.endTime).toBeGreaterThan(0);
201
+ expect(finalMetrics.totalEntities).toBe(1);
202
+ expect(finalMetrics.totalSuccesses).toBe(1);
203
+ expect(finalMetrics.totalFailures).toBe(0);
204
+ expect(finalMetrics.entityMetrics.size).toBe(1);
205
+ });
206
+ });
207
+ });
package/src/metrics.ts ADDED
@@ -0,0 +1,131 @@
1
+ export interface EntityMetrics {
2
+ entityName: string;
3
+ successCount: number;
4
+ failureCount: number;
5
+ startTime: number;
6
+ endTime?: number;
7
+ }
8
+
9
+ export interface ProcessingMetrics {
10
+ totalEntities: number;
11
+ totalSuccesses: number;
12
+ totalFailures: number;
13
+ entityMetrics: Map<string, EntityMetrics>;
14
+ requestDurations: number[];
15
+ startTime: number;
16
+ endTime?: number;
17
+ }
18
+
19
+ export class MetricsCollector {
20
+ private metrics: ProcessingMetrics;
21
+
22
+ constructor() {
23
+ this.metrics = {
24
+ totalEntities: 0,
25
+ totalSuccesses: 0,
26
+ totalFailures: 0,
27
+ entityMetrics: new Map(),
28
+ requestDurations: [],
29
+ startTime: Date.now(),
30
+ };
31
+ }
32
+
33
+ startEntityProcessing(entityName: string): void {
34
+ if (!this.metrics.entityMetrics.has(entityName)) {
35
+ this.metrics.entityMetrics.set(entityName, {
36
+ entityName,
37
+ successCount: 0,
38
+ failureCount: 0,
39
+ startTime: Date.now(),
40
+ });
41
+ }
42
+ }
43
+
44
+ recordSuccess(entityName: string): void {
45
+ const entityMetric = this.metrics.entityMetrics.get(entityName);
46
+ if (entityMetric) {
47
+ entityMetric.successCount++;
48
+ this.metrics.totalSuccesses++;
49
+ this.metrics.totalEntities++;
50
+ }
51
+ }
52
+
53
+ recordFailure(entityName: string): void {
54
+ const entityMetric = this.metrics.entityMetrics.get(entityName);
55
+ if (entityMetric) {
56
+ entityMetric.failureCount++;
57
+ this.metrics.totalFailures++;
58
+ this.metrics.totalEntities++;
59
+ }
60
+ }
61
+
62
+ finishEntityProcessing(entityName: string): void {
63
+ const entityMetric = this.metrics.entityMetrics.get(entityName);
64
+ if (entityMetric) {
65
+ entityMetric.endTime = Date.now();
66
+ }
67
+ }
68
+
69
+ finishProcessing(): ProcessingMetrics {
70
+ this.metrics.endTime = Date.now();
71
+ return { ...this.metrics };
72
+ }
73
+
74
+ getEntityMetrics(entityName: string): EntityMetrics | undefined {
75
+ return this.metrics.entityMetrics.get(entityName);
76
+ }
77
+
78
+ getTotalProcessed(): number {
79
+ return this.metrics.totalEntities;
80
+ }
81
+
82
+ getSuccessRate(): number {
83
+ if (this.metrics.totalEntities === 0) return 0;
84
+ return (this.metrics.totalSuccesses / this.metrics.totalEntities) * 100;
85
+ }
86
+
87
+ recordRequestDuration(duration: number): void {
88
+ this.metrics.requestDurations.push(duration);
89
+ }
90
+
91
+ getAverageRequestDuration(): number {
92
+ if (this.metrics.requestDurations.length === 0) return 0;
93
+ const sum = this.metrics.requestDurations.reduce((a, b) => a + b, 0);
94
+ return sum / this.metrics.requestDurations.length;
95
+ }
96
+
97
+ getDurationMs(): number {
98
+ const endTime = this.metrics.endTime || Date.now();
99
+ return endTime - this.metrics.startTime;
100
+ }
101
+
102
+ generateSummary(): string {
103
+ const duration = this.getDurationMs();
104
+ const successRate = this.getSuccessRate();
105
+ const avgRequestDuration = this.getAverageRequestDuration();
106
+
107
+ let summary = `\nšŸ“Š Processing Summary:\n`;
108
+ summary += ` Total Processed: ${this.metrics.totalEntities}\n`;
109
+ summary += ` āœ“ Successes: ${this.metrics.totalSuccesses}\n`;
110
+ summary += ` āœ— Failures: ${this.metrics.totalFailures}\n`;
111
+ summary += ` Success Rate: ${successRate.toFixed(1)}%\n`;
112
+ summary += ` Duration: ${(duration / 1000).toFixed(2)}s\n`;
113
+
114
+ if (this.metrics.requestDurations.length > 0) {
115
+ summary += ` Avg Request Time: ${avgRequestDuration.toFixed(0)}ms\n`;
116
+ }
117
+
118
+ if (this.metrics.entityMetrics.size > 1) {
119
+ summary += `\nšŸ“‹ Per-Entity Breakdown:\n`;
120
+ for (const [entityName, entityMetric] of this.metrics.entityMetrics) {
121
+ const entityTotal = entityMetric.successCount + entityMetric.failureCount;
122
+ const entityRate = entityTotal > 0 ? (entityMetric.successCount / entityTotal) * 100 : 0;
123
+ const entityDuration = entityMetric.endTime ? entityMetric.endTime - entityMetric.startTime : 0;
124
+
125
+ summary += ` ${entityName}: ${entityTotal} total (${entityMetric.successCount} āœ“, ${entityMetric.failureCount} āœ—) - ${entityRate.toFixed(1)}% success - ${(entityDuration / 1000).toFixed(2)}s\n`;
126
+ }
127
+ }
128
+
129
+ return summary;
130
+ }
131
+ }