autotel-tanstack 1.13.34 → 1.13.36
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/package.json +4 -5
- package/src/auto.test.ts +0 -114
- package/src/auto.ts +0 -60
- package/src/browser/context.ts +0 -88
- package/src/browser/debug-headers.ts +0 -19
- package/src/browser/error-reporting.ts +0 -64
- package/src/browser/handlers.ts +0 -23
- package/src/browser/index.ts +0 -66
- package/src/browser/loaders.ts +0 -62
- package/src/browser/metrics.ts +0 -86
- package/src/browser/middleware.ts +0 -77
- package/src/browser/server-functions.ts +0 -31
- package/src/browser/testing.ts +0 -130
- package/src/browser/types.ts +0 -100
- package/src/context.test.ts +0 -90
- package/src/context.ts +0 -145
- package/src/debug-headers.ts +0 -109
- package/src/env.ts +0 -56
- package/src/error-reporting.ts +0 -204
- package/src/handlers.ts +0 -306
- package/src/index.ts +0 -97
- package/src/instrument.test.ts +0 -131
- package/src/instrument.ts +0 -97
- package/src/loaders.test.ts +0 -123
- package/src/loaders.ts +0 -356
- package/src/metrics.ts +0 -184
- package/src/middleware.test.ts +0 -198
- package/src/middleware.ts +0 -435
- package/src/route-filter.test.ts +0 -28
- package/src/route-filter.ts +0 -40
- package/src/server-functions.test.ts +0 -86
- package/src/server-functions.ts +0 -188
- package/src/testing.test.ts +0 -205
- package/src/testing.ts +0 -430
- package/src/types.test.ts +0 -46
- package/src/types.ts +0 -182
package/src/metrics.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance metrics collection for TanStack Start
|
|
3
|
-
*
|
|
4
|
-
* Provides utilities to collect and expose performance metrics
|
|
5
|
-
* following the patterns from TanStack Start observability guide.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Performance timing data
|
|
10
|
-
*/
|
|
11
|
-
export interface TimingStats {
|
|
12
|
-
count: number;
|
|
13
|
-
avg: number;
|
|
14
|
-
p50: number;
|
|
15
|
-
p95: number;
|
|
16
|
-
min: number;
|
|
17
|
-
max: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Metrics collector for performance data
|
|
22
|
-
*
|
|
23
|
-
* Collects timing metrics and provides statistical analysis.
|
|
24
|
-
* Thread-safe for concurrent access.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```typescript
|
|
28
|
-
* import { metricsCollector } from 'autotel-tanstack/metrics';
|
|
29
|
-
*
|
|
30
|
-
* // Record a timing
|
|
31
|
-
* metricsCollector.recordTiming('serverFn.getUser', 150);
|
|
32
|
-
*
|
|
33
|
-
* // Get stats
|
|
34
|
-
* const stats = metricsCollector.getStats('serverFn.getUser');
|
|
35
|
-
* console.log(`Average: ${stats.avg}ms, P95: ${stats.p95}ms`);
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
class MetricsCollector {
|
|
39
|
-
private metrics = new Map<string, number[]>();
|
|
40
|
-
private readonly maxSamples = 1000; // Limit memory usage
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Record a timing measurement
|
|
44
|
-
*/
|
|
45
|
-
recordTiming(name: string, duration: number): void {
|
|
46
|
-
if (!this.metrics.has(name)) {
|
|
47
|
-
this.metrics.set(name, []);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const timings = this.metrics.get(name)!;
|
|
51
|
-
timings.push(duration);
|
|
52
|
-
|
|
53
|
-
// Limit samples to prevent memory issues
|
|
54
|
-
if (timings.length > this.maxSamples) {
|
|
55
|
-
timings.shift(); // Remove oldest
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get statistics for a metric
|
|
61
|
-
*/
|
|
62
|
-
getStats(name: string): TimingStats | null {
|
|
63
|
-
const timings = this.metrics.get(name);
|
|
64
|
-
if (!timings || timings.length === 0) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const sorted = [...timings].toSorted((a, b) => a - b);
|
|
69
|
-
const sum = timings.reduce((a, b) => a + b, 0);
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
count: timings.length,
|
|
73
|
-
avg: sum / timings.length,
|
|
74
|
-
p50: sorted.at(Math.floor(sorted.length * 0.5)) ?? 0,
|
|
75
|
-
p95: sorted.at(Math.floor(sorted.length * 0.95)) ?? 0,
|
|
76
|
-
min: sorted[0] ?? 0,
|
|
77
|
-
max: sorted.at(-1) ?? 0,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Get all collected metrics
|
|
83
|
-
*/
|
|
84
|
-
getAllStats(): Record<string, TimingStats> {
|
|
85
|
-
const stats: Record<string, TimingStats> = {};
|
|
86
|
-
for (const [name] of this.metrics) {
|
|
87
|
-
const stat = this.getStats(name);
|
|
88
|
-
if (stat) {
|
|
89
|
-
stats[name] = stat;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return stats;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Reset all metrics
|
|
97
|
-
*/
|
|
98
|
-
reset(): void {
|
|
99
|
-
this.metrics.clear();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Reset a specific metric
|
|
104
|
-
*/
|
|
105
|
-
resetMetric(name: string): void {
|
|
106
|
-
this.metrics.delete(name);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Global metrics collector instance
|
|
112
|
-
*/
|
|
113
|
-
export const metricsCollector = new MetricsCollector();
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Helper to create a metrics endpoint handler
|
|
117
|
-
*
|
|
118
|
-
* Returns a handler that exposes metrics in JSON format.
|
|
119
|
-
* Use this to create a `/metrics` endpoint.
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```typescript
|
|
123
|
-
* // routes/metrics.ts
|
|
124
|
-
* import { createFileRoute } from '@tanstack/react-router';
|
|
125
|
-
* import { json } from '@tanstack/react-start';
|
|
126
|
-
* import { createMetricsHandler } from 'autotel-tanstack/metrics';
|
|
127
|
-
*
|
|
128
|
-
* export const Route = createFileRoute('/metrics')({
|
|
129
|
-
* server: {
|
|
130
|
-
* handlers: {
|
|
131
|
-
* GET: createMetricsHandler(),
|
|
132
|
-
* },
|
|
133
|
-
* },
|
|
134
|
-
* });
|
|
135
|
-
* ```
|
|
136
|
-
*/
|
|
137
|
-
export function createMetricsHandler() {
|
|
138
|
-
return async () => {
|
|
139
|
-
const { json } = await import('@tanstack/react-start');
|
|
140
|
-
|
|
141
|
-
return json({
|
|
142
|
-
system: {
|
|
143
|
-
uptime: process.uptime(),
|
|
144
|
-
memory: process.memoryUsage(),
|
|
145
|
-
timestamp: new Date().toISOString(),
|
|
146
|
-
},
|
|
147
|
-
application: metricsCollector.getAllStats(),
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Auto-record timing from a function execution
|
|
154
|
-
*
|
|
155
|
-
* Wraps a function to automatically record its execution time.
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```typescript
|
|
159
|
-
* import { recordTiming } from 'autotel-tanstack/metrics';
|
|
160
|
-
*
|
|
161
|
-
* const getUser = createServerFn({ method: 'GET' })
|
|
162
|
-
* .handler(recordTiming('serverFn.getUser', async ({ data: id }) => {
|
|
163
|
-
* return await db.users.findUnique({ where: { id } });
|
|
164
|
-
* }));
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
export function recordTiming<T extends (...args: any[]) => any>(
|
|
168
|
-
metricName: string,
|
|
169
|
-
fn: T,
|
|
170
|
-
): T {
|
|
171
|
-
return (async (...args: Parameters<T>) => {
|
|
172
|
-
const startTime = Date.now();
|
|
173
|
-
try {
|
|
174
|
-
const result = await fn(...args);
|
|
175
|
-
const duration = Date.now() - startTime;
|
|
176
|
-
metricsCollector.recordTiming(metricName, duration);
|
|
177
|
-
return result as ReturnType<T>;
|
|
178
|
-
} catch (error) {
|
|
179
|
-
const duration = Date.now() - startTime;
|
|
180
|
-
metricsCollector.recordTiming(`${metricName}.error`, duration);
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
}) as T;
|
|
184
|
-
}
|
package/src/middleware.test.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
createTracingMiddleware,
|
|
4
|
-
tracingMiddleware,
|
|
5
|
-
functionTracingMiddleware,
|
|
6
|
-
} from './middleware';
|
|
7
|
-
|
|
8
|
-
// Mock autotel
|
|
9
|
-
vi.mock('autotel', () => ({
|
|
10
|
-
trace: vi.fn((name, fn) =>
|
|
11
|
-
fn({
|
|
12
|
-
setAttributes: vi.fn(),
|
|
13
|
-
setAttribute: vi.fn(),
|
|
14
|
-
setStatus: vi.fn(),
|
|
15
|
-
recordException: vi.fn(),
|
|
16
|
-
}),
|
|
17
|
-
),
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
// Mock context module
|
|
21
|
-
vi.mock('./context.js', () => ({
|
|
22
|
-
extractContextFromRequest: vi.fn(() => ({})),
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
describe('middleware', () => {
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('createTracingMiddleware', () => {
|
|
31
|
-
it('should create request middleware by default', async () => {
|
|
32
|
-
const middleware = createTracingMiddleware();
|
|
33
|
-
const request = new Request('http://localhost/api/users');
|
|
34
|
-
const next = vi.fn().mockResolvedValue({ status: 200 });
|
|
35
|
-
|
|
36
|
-
await middleware({
|
|
37
|
-
next,
|
|
38
|
-
request,
|
|
39
|
-
pathname: '/api/users',
|
|
40
|
-
context: {},
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(next).toHaveBeenCalled();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should create function middleware when type is "function"', async () => {
|
|
47
|
-
const middleware = createTracingMiddleware({ type: 'function' });
|
|
48
|
-
const next = vi.fn().mockResolvedValue({ data: 'test' });
|
|
49
|
-
|
|
50
|
-
await middleware({
|
|
51
|
-
next,
|
|
52
|
-
context: {},
|
|
53
|
-
data: { id: '123' },
|
|
54
|
-
functionId: 'getUser',
|
|
55
|
-
method: 'GET',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(next).toHaveBeenCalled();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should skip excluded paths', async () => {
|
|
62
|
-
const middleware = createTracingMiddleware({
|
|
63
|
-
excludePaths: ['/health', '/metrics'],
|
|
64
|
-
});
|
|
65
|
-
const request = new Request('http://localhost/health');
|
|
66
|
-
const next = vi.fn().mockResolvedValue({ status: 200 });
|
|
67
|
-
|
|
68
|
-
await middleware({
|
|
69
|
-
next,
|
|
70
|
-
request,
|
|
71
|
-
pathname: '/health',
|
|
72
|
-
context: {},
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
expect(next).toHaveBeenCalled();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should handle glob patterns in excludePaths', async () => {
|
|
79
|
-
const middleware = createTracingMiddleware({
|
|
80
|
-
excludePaths: ['/api/internal/*'],
|
|
81
|
-
});
|
|
82
|
-
const request = new Request('http://localhost/api/internal/debug');
|
|
83
|
-
const next = vi.fn().mockResolvedValue({ status: 200 });
|
|
84
|
-
|
|
85
|
-
await middleware({
|
|
86
|
-
next,
|
|
87
|
-
request,
|
|
88
|
-
pathname: '/api/internal/debug',
|
|
89
|
-
context: {},
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
expect(next).toHaveBeenCalled();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should handle regex patterns in excludePaths', async () => {
|
|
96
|
-
const middleware = createTracingMiddleware({
|
|
97
|
-
excludePaths: [/^\/api\/v\d+\/health$/],
|
|
98
|
-
});
|
|
99
|
-
const request = new Request('http://localhost/api/v1/health');
|
|
100
|
-
const next = vi.fn().mockResolvedValue({ status: 200 });
|
|
101
|
-
|
|
102
|
-
await middleware({
|
|
103
|
-
next,
|
|
104
|
-
request,
|
|
105
|
-
pathname: '/api/v1/health',
|
|
106
|
-
context: {},
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(next).toHaveBeenCalled();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should propagate errors from next()', async () => {
|
|
113
|
-
const middleware = createTracingMiddleware();
|
|
114
|
-
const error = new Error('Handler error');
|
|
115
|
-
const next = vi.fn().mockRejectedValue(error);
|
|
116
|
-
const request = new Request('http://localhost/api/users');
|
|
117
|
-
// Middleware reports the rejected error via console.error in
|
|
118
|
-
// error-reporting.ts — that's the contract under test (errors don't
|
|
119
|
-
// get silently swallowed). Silence the noise; the rejection assertion
|
|
120
|
-
// proves the propagation.
|
|
121
|
-
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
122
|
-
|
|
123
|
-
await expect(
|
|
124
|
-
middleware({
|
|
125
|
-
next,
|
|
126
|
-
request,
|
|
127
|
-
pathname: '/api/users',
|
|
128
|
-
context: {},
|
|
129
|
-
}),
|
|
130
|
-
).rejects.toThrow('Handler error');
|
|
131
|
-
|
|
132
|
-
errSpy.mockRestore();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should pass through when no request is available', async () => {
|
|
136
|
-
const middleware = createTracingMiddleware();
|
|
137
|
-
const next = vi.fn().mockResolvedValue({ data: 'test' });
|
|
138
|
-
|
|
139
|
-
const result = await middleware({
|
|
140
|
-
next,
|
|
141
|
-
context: {},
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(next).toHaveBeenCalled();
|
|
145
|
-
expect(result).toEqual({ data: 'test' });
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('tracingMiddleware', () => {
|
|
150
|
-
it('should create middleware with sensible defaults', async () => {
|
|
151
|
-
const middleware = tracingMiddleware();
|
|
152
|
-
expect(middleware).toBeDefined();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should exclude common health check paths', async () => {
|
|
156
|
-
const middleware = tracingMiddleware();
|
|
157
|
-
const request = new Request('http://localhost/healthz');
|
|
158
|
-
const next = vi.fn().mockResolvedValue({ status: 200 });
|
|
159
|
-
|
|
160
|
-
await middleware({
|
|
161
|
-
next,
|
|
162
|
-
request,
|
|
163
|
-
pathname: '/healthz',
|
|
164
|
-
context: {},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
expect(next).toHaveBeenCalled();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should allow config overrides', async () => {
|
|
171
|
-
const middleware = tracingMiddleware({
|
|
172
|
-
captureHeaders: ['x-custom-header'],
|
|
173
|
-
});
|
|
174
|
-
expect(middleware).toBeDefined();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe('functionTracingMiddleware', () => {
|
|
179
|
-
it('should create function middleware', async () => {
|
|
180
|
-
const middleware = functionTracingMiddleware();
|
|
181
|
-
const next = vi.fn().mockResolvedValue({ data: 'test' });
|
|
182
|
-
|
|
183
|
-
await middleware({
|
|
184
|
-
next,
|
|
185
|
-
context: {},
|
|
186
|
-
data: { id: '123' },
|
|
187
|
-
functionId: 'testFn',
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
expect(next).toHaveBeenCalled();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('should not require type in config', () => {
|
|
194
|
-
const middleware = functionTracingMiddleware({ captureArgs: false });
|
|
195
|
-
expect(middleware).toBeDefined();
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
});
|