@git-stunts/alfred 0.2.0 → 0.3.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.
package/src/telemetry.js CHANGED
@@ -5,8 +5,9 @@
5
5
 
6
6
  /**
7
7
  * @typedef {Object} TelemetryEvent
8
- * @property {string} type - Event type (e.g. 'retry', 'circuit.open')
8
+ * @property {string} type - Event type (e.g. 'retry.failure', 'circuit.open')
9
9
  * @property {number} timestamp - Event timestamp
10
+ * @property {Record<string, number>} [metrics] - Metric increments (counters)
10
11
  * @property {Object} [metadata] - Additional event data
11
12
  */
12
13
 
@@ -73,3 +74,85 @@ export class MultiSink {
73
74
  }
74
75
  }
75
76
  }
77
+
78
+ /**
79
+ * Sink that aggregates metrics in memory based on the `metrics` field in events.
80
+ * @implements {TelemetrySink}
81
+ */
82
+ export class MetricsSink {
83
+ constructor() {
84
+ this.clear();
85
+ }
86
+
87
+ /**
88
+ * Processes a telemetry event and updates internal counters.
89
+ * @param {TelemetryEvent} event
90
+ */
91
+ emit(event) {
92
+ const { duration, metrics } = event;
93
+
94
+ if (metrics && typeof metrics === 'object') {
95
+ this._updateMetrics(metrics);
96
+ }
97
+
98
+ if (typeof duration === 'number' && Number.isFinite(duration) && duration >= 0) {
99
+ this._updateLatency(duration);
100
+ }
101
+ }
102
+
103
+ _updateMetrics(metrics) {
104
+ for (const [key, value] of Object.entries(metrics)) {
105
+ if (typeof value !== 'number') {
106
+ continue;
107
+ }
108
+
109
+ const current = this.metrics[key];
110
+ if (current === undefined || typeof current === 'number') {
111
+ this.metrics[key] = (current || 0) + value;
112
+ }
113
+ }
114
+ }
115
+
116
+ _updateLatency(ms) {
117
+ const { latency } = this.metrics;
118
+ latency.count++;
119
+ latency.sum += ms;
120
+ latency.min = Math.min(latency.min, ms);
121
+ latency.max = Math.max(latency.max, ms);
122
+ }
123
+
124
+ /**
125
+ * Returns a snapshot of the current metrics.
126
+ */
127
+ get stats() {
128
+ const { latency, ...rest } = this.metrics;
129
+ const hasData = latency.count > 0;
130
+
131
+ return {
132
+ ...rest,
133
+ latency: {
134
+ ...latency,
135
+ min: hasData ? latency.min : 0,
136
+ max: hasData ? latency.max : 0,
137
+ avg: hasData ? latency.sum / latency.count : 0,
138
+ },
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Resets all metrics to zero.
144
+ */
145
+ clear() {
146
+ this.metrics = {
147
+ retries: 0,
148
+ failures: 0,
149
+ successes: 0,
150
+ circuitBreaks: 0,
151
+ circuitRejections: 0,
152
+ bulkheadRejections: 0,
153
+ timeouts: 0,
154
+ hedges: 0,
155
+ latency: { count: 0, sum: 0, min: Infinity, max: 0 },
156
+ };
157
+ }
158
+ }
package/src/testing.d.ts CHANGED
@@ -37,6 +37,7 @@ export interface BulkheadOptions {
37
37
  export interface TelemetryEvent {
38
38
  type: string;
39
39
  timestamp: number;
40
+ metrics?: Record<string, number>;
40
41
  [key: string]: any;
41
42
  }
42
43
 
@@ -96,7 +97,11 @@ export interface CircuitBreaker {
96
97
 
97
98
  export function circuitBreaker(options: CircuitBreakerOptions): CircuitBreaker;
98
99
 
99
- export function timeout<T>(ms: number, fn: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>), options?: TimeoutOptions): Promise<T>;
100
+ export function timeout<T>(
101
+ ms: number,
102
+ fn: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>),
103
+ options?: TimeoutOptions
104
+ ): Promise<T>;
100
105
 
101
106
  export interface Bulkhead {
102
107
  execute<T>(fn: () => Promise<T>): Promise<T>;
@@ -106,8 +111,14 @@ export interface Bulkhead {
106
111
  export function bulkhead(options: BulkheadOptions): Bulkhead;
107
112
 
108
113
  export function compose(...policies: any[]): { execute<T>(fn: () => Promise<T>): Promise<T> };
109
- export function fallback(primary: any, secondary: any): { execute<T>(fn: () => Promise<T>): Promise<T> };
110
- export function race(primary: any, secondary: any): { execute<T>(fn: () => Promise<T>): Promise<T> };
114
+ export function fallback(
115
+ primary: any,
116
+ secondary: any
117
+ ): { execute<T>(fn: () => Promise<T>): Promise<T> };
118
+ export function race(
119
+ primary: any,
120
+ secondary: any
121
+ ): { execute<T>(fn: () => Promise<T>): Promise<T> };
111
122
 
112
123
  export class Policy {
113
124
  constructor(executor: (fn: () => Promise<any>) => Promise<any>);
@@ -7,7 +7,7 @@ export class SystemClock {
7
7
  }
8
8
 
9
9
  async sleep(ms) {
10
- return new Promise(resolve => {
10
+ return new Promise((resolve) => {
11
11
  const timer = setTimeout(resolve, ms);
12
12
  if (typeof timer === 'object' && typeof timer.unref === 'function') {
13
13
  timer.unref();
@@ -39,10 +39,10 @@ export class TestClock {
39
39
  * @returns {Promise<void>}
40
40
  */
41
41
  sleep(ms) {
42
- return new Promise(resolve => {
42
+ return new Promise((resolve) => {
43
43
  this._pendingTimers.push({
44
44
  triggerAt: this._time + ms,
45
- resolve
45
+ resolve,
46
46
  });
47
47
  // Sort by trigger time
48
48
  this._pendingTimers.sort((a, b) => a.triggerAt - b.triggerAt);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Resolves a value that might be dynamic.
3
+ *
4
+ * @template T
5
+ * @param {T | (() => T)} value - The value or a function returning the value.
6
+ * @returns {T} The resolved value.
7
+ */
8
+ export function resolve(value) {
9
+ return typeof value === 'function' ? value() : value;
10
+ }