margin-ts 0.6.0 → 0.6.2
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/dist/adapters/express.d.ts +80 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +237 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/nextjs.d.ts +65 -0
- package/dist/adapters/nextjs.d.ts.map +1 -0
- package/dist/adapters/nextjs.js +233 -0
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/adapters/stream.d.ts +74 -0
- package/dist/adapters/stream.d.ts.map +1 -0
- package/dist/adapters/stream.js +187 -0
- package/dist/adapters/stream.js.map +1 -0
- package/dist/adapters/vitest.d.ts +36 -0
- package/dist/adapters/vitest.d.ts.map +1 -0
- package/dist/adapters/vitest.js +104 -0
- package/dist/adapters/vitest.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/express.ts +342 -0
- package/src/adapters/nextjs.ts +290 -0
- package/src/adapters/stream.ts +227 -0
- package/src/adapters/vitest.ts +141 -0
- package/src/index.ts +27 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express middleware adapter for margin.
|
|
3
|
+
*
|
|
4
|
+
* Tracks per-route health: latency, error rate, request rate.
|
|
5
|
+
* Exposes /margin/health endpoint with typed classifications.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import express from 'express';
|
|
9
|
+
* import { marginMiddleware, marginHealthRoute } from 'margin-ts/adapters/express';
|
|
10
|
+
*
|
|
11
|
+
* const app = express();
|
|
12
|
+
* app.use(marginMiddleware());
|
|
13
|
+
* app.get('/margin/health', marginHealthRoute());
|
|
14
|
+
*
|
|
15
|
+
* Zero dependencies beyond margin-ts core (express types are optional).
|
|
16
|
+
*/
|
|
17
|
+
import { Thresholds } from '../index.js';
|
|
18
|
+
interface RouteMetrics {
|
|
19
|
+
route: string;
|
|
20
|
+
totalRequests: number;
|
|
21
|
+
totalErrors: number;
|
|
22
|
+
total4xx: number;
|
|
23
|
+
latencies: number[];
|
|
24
|
+
timestamps: Date[];
|
|
25
|
+
maxWindow: number;
|
|
26
|
+
}
|
|
27
|
+
export interface EndpointThresholds {
|
|
28
|
+
p50Latency: Thresholds;
|
|
29
|
+
p99Latency: Thresholds;
|
|
30
|
+
errorRate: Thresholds;
|
|
31
|
+
requestRate: Thresholds;
|
|
32
|
+
}
|
|
33
|
+
export declare const DEFAULT_THRESHOLDS: EndpointThresholds;
|
|
34
|
+
export interface MarginMiddlewareOptions {
|
|
35
|
+
/** Track per-route or aggregate only (default: true) */
|
|
36
|
+
perRoute?: boolean;
|
|
37
|
+
/** Max latency samples to keep per route (default: 200) */
|
|
38
|
+
window?: number;
|
|
39
|
+
/** Custom thresholds */
|
|
40
|
+
thresholds?: EndpointThresholds;
|
|
41
|
+
/** Routes to ignore (e.g. ['/margin/health', '/favicon.ico']) */
|
|
42
|
+
ignore?: string[];
|
|
43
|
+
/** Normalize route paths: replace numeric segments with :id (default: true) */
|
|
44
|
+
normalizePaths?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** The shared state object — accessible for custom endpoints */
|
|
47
|
+
export interface MarginState {
|
|
48
|
+
routes: Map<string, RouteMetrics>;
|
|
49
|
+
aggregate: RouteMetrics;
|
|
50
|
+
options: Required<MarginMiddlewareOptions>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Express middleware that tracks request latency and error rates.
|
|
54
|
+
*
|
|
55
|
+
* Attach to your app before routes:
|
|
56
|
+
* app.use(marginMiddleware());
|
|
57
|
+
*/
|
|
58
|
+
export declare function marginMiddleware(options?: MarginMiddlewareOptions): any;
|
|
59
|
+
export interface HealthResponse {
|
|
60
|
+
status: string;
|
|
61
|
+
expression: string;
|
|
62
|
+
aggregate: Record<string, unknown>;
|
|
63
|
+
routes?: Record<string, Record<string, unknown>>;
|
|
64
|
+
drift?: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Express route handler for /margin/health.
|
|
68
|
+
*
|
|
69
|
+
* Returns typed health classification for all tracked routes.
|
|
70
|
+
*
|
|
71
|
+
* app.get('/margin/health', marginHealthRoute());
|
|
72
|
+
*
|
|
73
|
+
* Or pass the middleware directly:
|
|
74
|
+
* const mw = marginMiddleware();
|
|
75
|
+
* app.use(mw);
|
|
76
|
+
* app.get('/margin/health', marginHealthRoute(mw));
|
|
77
|
+
*/
|
|
78
|
+
export declare function marginHealthRoute(middleware?: any): any;
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAGL,UAAU,EAcX,MAAM,aAAa,CAAC;AAMrB,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,IAAI,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AA8BD,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,EAAE,kBAKhC,CAAC;AAmFF,MAAM,WAAW,uBAAuB;IACtC,wDAAwD;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClC,SAAS,EAAE,YAAY,CAAC;IACxB,OAAO,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC;CAC5C;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,GAAG,CAoD3E;AAMD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,GAAG,GAAG,GAAG,CA4DvD"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware adapter for margin.
|
|
4
|
+
*
|
|
5
|
+
* Tracks per-route health: latency, error rate, request rate.
|
|
6
|
+
* Exposes /margin/health endpoint with typed classifications.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import express from 'express';
|
|
10
|
+
* import { marginMiddleware, marginHealthRoute } from 'margin-ts/adapters/express';
|
|
11
|
+
*
|
|
12
|
+
* const app = express();
|
|
13
|
+
* app.use(marginMiddleware());
|
|
14
|
+
* app.get('/margin/health', marginHealthRoute());
|
|
15
|
+
*
|
|
16
|
+
* Zero dependencies beyond margin-ts core (express types are optional).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.DEFAULT_THRESHOLDS = void 0;
|
|
20
|
+
exports.marginMiddleware = marginMiddleware;
|
|
21
|
+
exports.marginHealthRoute = marginHealthRoute;
|
|
22
|
+
const index_js_1 = require("../index.js");
|
|
23
|
+
function createRouteMetrics(route, maxWindow = 200) {
|
|
24
|
+
return {
|
|
25
|
+
route,
|
|
26
|
+
totalRequests: 0,
|
|
27
|
+
totalErrors: 0,
|
|
28
|
+
total4xx: 0,
|
|
29
|
+
latencies: [],
|
|
30
|
+
timestamps: [],
|
|
31
|
+
maxWindow,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function recordRequest(rm, latencyMs, statusCode) {
|
|
35
|
+
rm.totalRequests++;
|
|
36
|
+
if (statusCode >= 500)
|
|
37
|
+
rm.totalErrors++;
|
|
38
|
+
if (statusCode >= 400 && statusCode < 500)
|
|
39
|
+
rm.total4xx++;
|
|
40
|
+
rm.latencies.push(latencyMs);
|
|
41
|
+
rm.timestamps.push(new Date());
|
|
42
|
+
if (rm.latencies.length > rm.maxWindow) {
|
|
43
|
+
rm.latencies.shift();
|
|
44
|
+
rm.timestamps.shift();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.DEFAULT_THRESHOLDS = {
|
|
48
|
+
p50Latency: (0, index_js_1.createThresholds)(100, 500, false), // ms, lower is better
|
|
49
|
+
p99Latency: (0, index_js_1.createThresholds)(500, 2000, false), // ms, lower is better
|
|
50
|
+
errorRate: (0, index_js_1.createThresholds)(0.01, 0.10, false), // ratio, lower is better
|
|
51
|
+
requestRate: (0, index_js_1.createThresholds)(1.0, 0.1, true), // req/s, higher is better
|
|
52
|
+
};
|
|
53
|
+
// -----------------------------------------------------------------------
|
|
54
|
+
// Metrics → Observations
|
|
55
|
+
// -----------------------------------------------------------------------
|
|
56
|
+
function percentile(sorted, p) {
|
|
57
|
+
if (sorted.length === 0)
|
|
58
|
+
return 0;
|
|
59
|
+
const idx = Math.ceil(sorted.length * p) - 1;
|
|
60
|
+
return sorted[Math.max(0, Math.min(idx, sorted.length - 1))];
|
|
61
|
+
}
|
|
62
|
+
function classifyRoute(rm, thresholds, now) {
|
|
63
|
+
if (rm.latencies.length < 3)
|
|
64
|
+
return [];
|
|
65
|
+
const sorted = [...rm.latencies].sort((a, b) => a - b);
|
|
66
|
+
const p50 = percentile(sorted, 0.5);
|
|
67
|
+
const p99 = percentile(sorted, 0.99);
|
|
68
|
+
const errorRate = rm.totalRequests > 0 ? rm.totalErrors / rm.totalRequests : 0;
|
|
69
|
+
// Request rate: requests in window / window duration
|
|
70
|
+
let requestRate = 0;
|
|
71
|
+
if (rm.timestamps.length >= 2) {
|
|
72
|
+
const windowMs = rm.timestamps[rm.timestamps.length - 1].getTime() - rm.timestamps[0].getTime();
|
|
73
|
+
if (windowMs > 0)
|
|
74
|
+
requestRate = (rm.timestamps.length / windowMs) * 1000;
|
|
75
|
+
}
|
|
76
|
+
const prefix = rm.route === '*' ? '' : `${rm.route}:`;
|
|
77
|
+
const observations = [
|
|
78
|
+
{
|
|
79
|
+
name: `${prefix}p50_latency`,
|
|
80
|
+
health: (0, index_js_1.classify)(p50, index_js_1.Confidence.HIGH, thresholds.p50Latency),
|
|
81
|
+
value: p50,
|
|
82
|
+
baseline: thresholds.p50Latency.intact,
|
|
83
|
+
confidence: index_js_1.Confidence.HIGH,
|
|
84
|
+
higherIsBetter: false,
|
|
85
|
+
provenance: [],
|
|
86
|
+
measuredAt: now,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: `${prefix}p99_latency`,
|
|
90
|
+
health: (0, index_js_1.classify)(p99, index_js_1.Confidence.HIGH, thresholds.p99Latency),
|
|
91
|
+
value: p99,
|
|
92
|
+
baseline: thresholds.p99Latency.intact,
|
|
93
|
+
confidence: index_js_1.Confidence.HIGH,
|
|
94
|
+
higherIsBetter: false,
|
|
95
|
+
provenance: [],
|
|
96
|
+
measuredAt: now,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: `${prefix}error_rate`,
|
|
100
|
+
health: (0, index_js_1.classify)(errorRate, index_js_1.Confidence.HIGH, thresholds.errorRate),
|
|
101
|
+
value: errorRate,
|
|
102
|
+
baseline: 0.001,
|
|
103
|
+
confidence: index_js_1.Confidence.HIGH,
|
|
104
|
+
higherIsBetter: false,
|
|
105
|
+
provenance: [],
|
|
106
|
+
measuredAt: now,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: `${prefix}request_rate`,
|
|
110
|
+
health: (0, index_js_1.classify)(requestRate, index_js_1.Confidence.MODERATE, thresholds.requestRate),
|
|
111
|
+
value: requestRate,
|
|
112
|
+
baseline: 10.0,
|
|
113
|
+
confidence: index_js_1.Confidence.MODERATE,
|
|
114
|
+
higherIsBetter: true,
|
|
115
|
+
provenance: [],
|
|
116
|
+
measuredAt: now,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
return observations;
|
|
120
|
+
}
|
|
121
|
+
function normalizePath(path) {
|
|
122
|
+
return path.replace(/\/\d+/g, '/:id').replace(/\/[0-9a-f]{24}/g, '/:id');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Express middleware that tracks request latency and error rates.
|
|
126
|
+
*
|
|
127
|
+
* Attach to your app before routes:
|
|
128
|
+
* app.use(marginMiddleware());
|
|
129
|
+
*/
|
|
130
|
+
function marginMiddleware(options = {}) {
|
|
131
|
+
const opts = {
|
|
132
|
+
perRoute: options.perRoute ?? true,
|
|
133
|
+
window: options.window ?? 200,
|
|
134
|
+
thresholds: options.thresholds ?? exports.DEFAULT_THRESHOLDS,
|
|
135
|
+
ignore: options.ignore ?? ['/margin/health', '/favicon.ico'],
|
|
136
|
+
normalizePaths: options.normalizePaths ?? true,
|
|
137
|
+
};
|
|
138
|
+
const state = {
|
|
139
|
+
routes: new Map(),
|
|
140
|
+
aggregate: createRouteMetrics('*', opts.window),
|
|
141
|
+
options: opts,
|
|
142
|
+
};
|
|
143
|
+
const middleware = (req, res, next) => {
|
|
144
|
+
const path = req.path || req.url || '/';
|
|
145
|
+
if (opts.ignore.includes(path)) {
|
|
146
|
+
next();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const start = Date.now();
|
|
150
|
+
// Hook into response finish
|
|
151
|
+
const onFinish = () => {
|
|
152
|
+
res.removeListener('finish', onFinish);
|
|
153
|
+
const latency = Date.now() - start;
|
|
154
|
+
const status = res.statusCode || 200;
|
|
155
|
+
// Aggregate
|
|
156
|
+
recordRequest(state.aggregate, latency, status);
|
|
157
|
+
// Per-route
|
|
158
|
+
if (opts.perRoute) {
|
|
159
|
+
const route = opts.normalizePaths ? normalizePath(path) : path;
|
|
160
|
+
if (!state.routes.has(route)) {
|
|
161
|
+
state.routes.set(route, createRouteMetrics(route, opts.window));
|
|
162
|
+
}
|
|
163
|
+
recordRequest(state.routes.get(route), latency, status);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
res.on('finish', onFinish);
|
|
167
|
+
next();
|
|
168
|
+
};
|
|
169
|
+
// Attach state to middleware for access by health route
|
|
170
|
+
middleware.__marginState = state;
|
|
171
|
+
return middleware;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Express route handler for /margin/health.
|
|
175
|
+
*
|
|
176
|
+
* Returns typed health classification for all tracked routes.
|
|
177
|
+
*
|
|
178
|
+
* app.get('/margin/health', marginHealthRoute());
|
|
179
|
+
*
|
|
180
|
+
* Or pass the middleware directly:
|
|
181
|
+
* const mw = marginMiddleware();
|
|
182
|
+
* app.use(mw);
|
|
183
|
+
* app.get('/margin/health', marginHealthRoute(mw));
|
|
184
|
+
*/
|
|
185
|
+
function marginHealthRoute(middleware) {
|
|
186
|
+
return (req, res) => {
|
|
187
|
+
// Find state from middleware
|
|
188
|
+
let state;
|
|
189
|
+
if (middleware && middleware.__marginState) {
|
|
190
|
+
state = middleware.__marginState;
|
|
191
|
+
}
|
|
192
|
+
else if (req.app) {
|
|
193
|
+
// Walk through app middleware stack to find ours
|
|
194
|
+
const stack = req.app._router?.stack || [];
|
|
195
|
+
for (const layer of stack) {
|
|
196
|
+
if (layer.handle?.__marginState) {
|
|
197
|
+
state = layer.handle.__marginState;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!state) {
|
|
203
|
+
res.status(500).json({ error: 'margin middleware not found' });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const now = new Date();
|
|
207
|
+
const thresholds = state.options.thresholds;
|
|
208
|
+
// Aggregate health
|
|
209
|
+
const aggObs = classifyRoute(state.aggregate, thresholds, now);
|
|
210
|
+
const aggExpr = (0, index_js_1.createExpression)(aggObs, [], 'aggregate');
|
|
211
|
+
// Determine overall status
|
|
212
|
+
const SEVERITY_MAP = {
|
|
213
|
+
[index_js_1.Health.INTACT]: 0, [index_js_1.Health.RECOVERING]: 1, [index_js_1.Health.DEGRADED]: 2,
|
|
214
|
+
[index_js_1.Health.ABLATED]: 3, [index_js_1.Health.OOD]: 4,
|
|
215
|
+
};
|
|
216
|
+
const worstHealth = aggObs.reduce((worst, o) => SEVERITY_MAP[o.health] > SEVERITY_MAP[worst] ? o.health : worst, index_js_1.Health.INTACT);
|
|
217
|
+
const response = {
|
|
218
|
+
status: worstHealth,
|
|
219
|
+
expression: (0, index_js_1.expressionToString)(aggExpr),
|
|
220
|
+
aggregate: (0, index_js_1.expressionToDict)(aggExpr),
|
|
221
|
+
};
|
|
222
|
+
// Per-route health
|
|
223
|
+
if (state.options.perRoute && state.routes.size > 0) {
|
|
224
|
+
const routes = {};
|
|
225
|
+
for (const [route, metrics] of state.routes) {
|
|
226
|
+
const obs = classifyRoute(metrics, thresholds, now);
|
|
227
|
+
if (obs.length > 0) {
|
|
228
|
+
const expr = (0, index_js_1.createExpression)(obs, [], route);
|
|
229
|
+
routes[route] = (0, index_js_1.expressionToDict)(expr);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
response.routes = routes;
|
|
233
|
+
}
|
|
234
|
+
res.json(response);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js API route adapter for margin.
|
|
3
|
+
*
|
|
4
|
+
* One-liner health tracking for any API route:
|
|
5
|
+
*
|
|
6
|
+
* // pages/api/users.ts (Pages Router)
|
|
7
|
+
* import { withMargin } from 'margin-ts/adapters/nextjs';
|
|
8
|
+
* export default withMargin(handler);
|
|
9
|
+
*
|
|
10
|
+
* // app/api/users/route.ts (App Router)
|
|
11
|
+
* import { withMarginApp } from 'margin-ts/adapters/nextjs';
|
|
12
|
+
* export const GET = withMarginApp(handler);
|
|
13
|
+
*
|
|
14
|
+
* // Health endpoint
|
|
15
|
+
* // pages/api/margin/health.ts
|
|
16
|
+
* import { marginHealthHandler } from 'margin-ts/adapters/nextjs';
|
|
17
|
+
* export default marginHealthHandler();
|
|
18
|
+
*
|
|
19
|
+
* Tracks latency, error rate, and request count per route.
|
|
20
|
+
* Zero dependencies beyond margin-ts core.
|
|
21
|
+
*/
|
|
22
|
+
import { Thresholds } from '../index.js';
|
|
23
|
+
export interface NextjsThresholds {
|
|
24
|
+
p50Latency: Thresholds;
|
|
25
|
+
p99Latency: Thresholds;
|
|
26
|
+
errorRate: Thresholds;
|
|
27
|
+
}
|
|
28
|
+
export declare const DEFAULT_NEXTJS_THRESHOLDS: NextjsThresholds;
|
|
29
|
+
export interface WithMarginOptions {
|
|
30
|
+
route?: string;
|
|
31
|
+
thresholds?: NextjsThresholds;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a Pages Router API handler with margin health tracking.
|
|
35
|
+
*
|
|
36
|
+
* export default withMargin(handler);
|
|
37
|
+
* export default withMargin(handler, { route: '/api/users' });
|
|
38
|
+
*/
|
|
39
|
+
export declare function withMargin(handler: (req: any, res: any) => any, options?: WithMarginOptions): (req: any, res: any) => any;
|
|
40
|
+
/**
|
|
41
|
+
* Wrap an App Router handler with margin health tracking.
|
|
42
|
+
*
|
|
43
|
+
* export const GET = withMarginApp(handler);
|
|
44
|
+
* export const POST = withMarginApp(handler, { route: '/api/users' });
|
|
45
|
+
*/
|
|
46
|
+
export declare function withMarginApp(handler: (req: any) => any, options?: WithMarginOptions): (req: any) => any;
|
|
47
|
+
/**
|
|
48
|
+
* Pages Router: health endpoint handler.
|
|
49
|
+
*
|
|
50
|
+
* // pages/api/margin/health.ts
|
|
51
|
+
* import { marginHealthHandler } from 'margin-ts/adapters/nextjs';
|
|
52
|
+
* export default marginHealthHandler();
|
|
53
|
+
*/
|
|
54
|
+
export declare function marginHealthHandler(thresholds?: NextjsThresholds): (req: any, res: any) => void;
|
|
55
|
+
/**
|
|
56
|
+
* App Router: health endpoint handler.
|
|
57
|
+
*
|
|
58
|
+
* // app/api/margin/health/route.ts
|
|
59
|
+
* import { marginHealthAppHandler } from 'margin-ts/adapters/nextjs';
|
|
60
|
+
* export const GET = marginHealthAppHandler();
|
|
61
|
+
*/
|
|
62
|
+
export declare function marginHealthAppHandler(thresholds?: NextjsThresholds): (req: any) => any;
|
|
63
|
+
/** Clear all tracked routes (for testing). */
|
|
64
|
+
export declare function resetRoutes(): void;
|
|
65
|
+
//# sourceMappingURL=nextjs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../../src/adapters/nextjs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAGL,UAAU,EAOX,MAAM,aAAa,CAAC;AAwCrB,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,eAAO,MAAM,yBAAyB,EAAE,gBAIvC,CAAC;AA8CF,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,GAAG,EACpC,OAAO,GAAE,iBAAsB,GAC9B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,GAAG,CAiB7B;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,CAiBnB;AAMD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAkC/F;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,CAAC,EAAE,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,CAqCvF;AAED,8CAA8C;AAC9C,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Next.js API route adapter for margin.
|
|
4
|
+
*
|
|
5
|
+
* One-liner health tracking for any API route:
|
|
6
|
+
*
|
|
7
|
+
* // pages/api/users.ts (Pages Router)
|
|
8
|
+
* import { withMargin } from 'margin-ts/adapters/nextjs';
|
|
9
|
+
* export default withMargin(handler);
|
|
10
|
+
*
|
|
11
|
+
* // app/api/users/route.ts (App Router)
|
|
12
|
+
* import { withMarginApp } from 'margin-ts/adapters/nextjs';
|
|
13
|
+
* export const GET = withMarginApp(handler);
|
|
14
|
+
*
|
|
15
|
+
* // Health endpoint
|
|
16
|
+
* // pages/api/margin/health.ts
|
|
17
|
+
* import { marginHealthHandler } from 'margin-ts/adapters/nextjs';
|
|
18
|
+
* export default marginHealthHandler();
|
|
19
|
+
*
|
|
20
|
+
* Tracks latency, error rate, and request count per route.
|
|
21
|
+
* Zero dependencies beyond margin-ts core.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.DEFAULT_NEXTJS_THRESHOLDS = void 0;
|
|
25
|
+
exports.withMargin = withMargin;
|
|
26
|
+
exports.withMarginApp = withMarginApp;
|
|
27
|
+
exports.marginHealthHandler = marginHealthHandler;
|
|
28
|
+
exports.marginHealthAppHandler = marginHealthAppHandler;
|
|
29
|
+
exports.resetRoutes = resetRoutes;
|
|
30
|
+
const index_js_1 = require("../index.js");
|
|
31
|
+
const _routes = new Map();
|
|
32
|
+
const _MAX_WINDOW = 200;
|
|
33
|
+
function getOrCreateRoute(route) {
|
|
34
|
+
if (!_routes.has(route)) {
|
|
35
|
+
_routes.set(route, {
|
|
36
|
+
totalRequests: 0,
|
|
37
|
+
totalErrors: 0,
|
|
38
|
+
latencies: [],
|
|
39
|
+
maxWindow: _MAX_WINDOW,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return _routes.get(route);
|
|
43
|
+
}
|
|
44
|
+
function record(route, latencyMs, isError) {
|
|
45
|
+
const stats = getOrCreateRoute(route);
|
|
46
|
+
stats.totalRequests++;
|
|
47
|
+
if (isError)
|
|
48
|
+
stats.totalErrors++;
|
|
49
|
+
stats.latencies.push(latencyMs);
|
|
50
|
+
if (stats.latencies.length > stats.maxWindow)
|
|
51
|
+
stats.latencies.shift();
|
|
52
|
+
}
|
|
53
|
+
exports.DEFAULT_NEXTJS_THRESHOLDS = {
|
|
54
|
+
p50Latency: (0, index_js_1.createThresholds)(100, 500, false),
|
|
55
|
+
p99Latency: (0, index_js_1.createThresholds)(500, 2000, false),
|
|
56
|
+
errorRate: (0, index_js_1.createThresholds)(0.01, 0.10, false),
|
|
57
|
+
};
|
|
58
|
+
// -----------------------------------------------------------------------
|
|
59
|
+
// Classification
|
|
60
|
+
// -----------------------------------------------------------------------
|
|
61
|
+
function percentile(sorted, p) {
|
|
62
|
+
if (sorted.length === 0)
|
|
63
|
+
return 0;
|
|
64
|
+
const idx = Math.ceil(sorted.length * p) - 1;
|
|
65
|
+
return sorted[Math.max(0, Math.min(idx, sorted.length - 1))];
|
|
66
|
+
}
|
|
67
|
+
function classifyRoute(route, stats, thresholds) {
|
|
68
|
+
if (stats.latencies.length < 3)
|
|
69
|
+
return null;
|
|
70
|
+
const sorted = [...stats.latencies].sort((a, b) => a - b);
|
|
71
|
+
const p50 = percentile(sorted, 0.5);
|
|
72
|
+
const p99 = percentile(sorted, 0.99);
|
|
73
|
+
const errorRate = stats.totalRequests > 0 ? stats.totalErrors / stats.totalRequests : 0;
|
|
74
|
+
const now = new Date();
|
|
75
|
+
const observations = [
|
|
76
|
+
{
|
|
77
|
+
name: `${route}:p50`, health: (0, index_js_1.classify)(p50, index_js_1.Confidence.HIGH, thresholds.p50Latency),
|
|
78
|
+
value: p50, baseline: thresholds.p50Latency.intact, confidence: index_js_1.Confidence.HIGH,
|
|
79
|
+
higherIsBetter: false, provenance: [], measuredAt: now,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: `${route}:p99`, health: (0, index_js_1.classify)(p99, index_js_1.Confidence.HIGH, thresholds.p99Latency),
|
|
83
|
+
value: p99, baseline: thresholds.p99Latency.intact, confidence: index_js_1.Confidence.HIGH,
|
|
84
|
+
higherIsBetter: false, provenance: [], measuredAt: now,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: `${route}:errors`, health: (0, index_js_1.classify)(errorRate, index_js_1.Confidence.HIGH, thresholds.errorRate),
|
|
88
|
+
value: errorRate, baseline: 0.001, confidence: index_js_1.Confidence.HIGH,
|
|
89
|
+
higherIsBetter: false, provenance: [], measuredAt: now,
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
return (0, index_js_1.createExpression)(observations, [], route);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Wrap a Pages Router API handler with margin health tracking.
|
|
96
|
+
*
|
|
97
|
+
* export default withMargin(handler);
|
|
98
|
+
* export default withMargin(handler, { route: '/api/users' });
|
|
99
|
+
*/
|
|
100
|
+
function withMargin(handler, options = {}) {
|
|
101
|
+
return async (req, res) => {
|
|
102
|
+
const route = options.route || req.url || '/api/unknown';
|
|
103
|
+
const start = Date.now();
|
|
104
|
+
try {
|
|
105
|
+
const result = await handler(req, res);
|
|
106
|
+
const latency = Date.now() - start;
|
|
107
|
+
const status = res.statusCode || 200;
|
|
108
|
+
record(route, latency, status >= 500);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const latency = Date.now() - start;
|
|
113
|
+
record(route, latency, true);
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// -----------------------------------------------------------------------
|
|
119
|
+
// App Router wrapper
|
|
120
|
+
// -----------------------------------------------------------------------
|
|
121
|
+
/**
|
|
122
|
+
* Wrap an App Router handler with margin health tracking.
|
|
123
|
+
*
|
|
124
|
+
* export const GET = withMarginApp(handler);
|
|
125
|
+
* export const POST = withMarginApp(handler, { route: '/api/users' });
|
|
126
|
+
*/
|
|
127
|
+
function withMarginApp(handler, options = {}) {
|
|
128
|
+
return async (req) => {
|
|
129
|
+
const route = options.route || new URL(req.url || '/', 'http://localhost').pathname;
|
|
130
|
+
const start = Date.now();
|
|
131
|
+
try {
|
|
132
|
+
const response = await handler(req);
|
|
133
|
+
const latency = Date.now() - start;
|
|
134
|
+
const status = response?.status || 200;
|
|
135
|
+
record(route, latency, status >= 500);
|
|
136
|
+
return response;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const latency = Date.now() - start;
|
|
140
|
+
record(route, latency, true);
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
// Health endpoint
|
|
147
|
+
// -----------------------------------------------------------------------
|
|
148
|
+
/**
|
|
149
|
+
* Pages Router: health endpoint handler.
|
|
150
|
+
*
|
|
151
|
+
* // pages/api/margin/health.ts
|
|
152
|
+
* import { marginHealthHandler } from 'margin-ts/adapters/nextjs';
|
|
153
|
+
* export default marginHealthHandler();
|
|
154
|
+
*/
|
|
155
|
+
function marginHealthHandler(thresholds) {
|
|
156
|
+
const t = thresholds || exports.DEFAULT_NEXTJS_THRESHOLDS;
|
|
157
|
+
return (_req, res) => {
|
|
158
|
+
const routeHealth = {};
|
|
159
|
+
let worstOverall = index_js_1.Health.INTACT;
|
|
160
|
+
const SEVERITY = {
|
|
161
|
+
[index_js_1.Health.INTACT]: 0, [index_js_1.Health.RECOVERING]: 1, [index_js_1.Health.DEGRADED]: 2,
|
|
162
|
+
[index_js_1.Health.ABLATED]: 3, [index_js_1.Health.OOD]: 4,
|
|
163
|
+
};
|
|
164
|
+
for (const [route, stats] of _routes) {
|
|
165
|
+
const expr = classifyRoute(route, stats, t);
|
|
166
|
+
if (expr) {
|
|
167
|
+
routeHealth[route] = {
|
|
168
|
+
expression: (0, index_js_1.expressionToString)(expr),
|
|
169
|
+
...(0, index_js_1.expressionToDict)(expr),
|
|
170
|
+
requests: stats.totalRequests,
|
|
171
|
+
errors: stats.totalErrors,
|
|
172
|
+
};
|
|
173
|
+
for (const obs of expr.observations) {
|
|
174
|
+
if (SEVERITY[obs.health] > SEVERITY[worstOverall]) {
|
|
175
|
+
worstOverall = obs.health;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
res.status(200).json({
|
|
181
|
+
status: worstOverall,
|
|
182
|
+
routes: routeHealth,
|
|
183
|
+
totalRoutes: _routes.size,
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* App Router: health endpoint handler.
|
|
189
|
+
*
|
|
190
|
+
* // app/api/margin/health/route.ts
|
|
191
|
+
* import { marginHealthAppHandler } from 'margin-ts/adapters/nextjs';
|
|
192
|
+
* export const GET = marginHealthAppHandler();
|
|
193
|
+
*/
|
|
194
|
+
function marginHealthAppHandler(thresholds) {
|
|
195
|
+
const t = thresholds || exports.DEFAULT_NEXTJS_THRESHOLDS;
|
|
196
|
+
return (_req) => {
|
|
197
|
+
const routeHealth = {};
|
|
198
|
+
let worstOverall = index_js_1.Health.INTACT;
|
|
199
|
+
const SEVERITY = {
|
|
200
|
+
[index_js_1.Health.INTACT]: 0, [index_js_1.Health.RECOVERING]: 1, [index_js_1.Health.DEGRADED]: 2,
|
|
201
|
+
[index_js_1.Health.ABLATED]: 3, [index_js_1.Health.OOD]: 4,
|
|
202
|
+
};
|
|
203
|
+
for (const [route, stats] of _routes) {
|
|
204
|
+
const expr = classifyRoute(route, stats, t);
|
|
205
|
+
if (expr) {
|
|
206
|
+
routeHealth[route] = {
|
|
207
|
+
expression: (0, index_js_1.expressionToString)(expr),
|
|
208
|
+
...(0, index_js_1.expressionToDict)(expr),
|
|
209
|
+
requests: stats.totalRequests,
|
|
210
|
+
errors: stats.totalErrors,
|
|
211
|
+
};
|
|
212
|
+
for (const obs of expr.observations) {
|
|
213
|
+
if (SEVERITY[obs.health] > SEVERITY[worstOverall]) {
|
|
214
|
+
worstOverall = obs.health;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return new Response(JSON.stringify({
|
|
220
|
+
status: worstOverall,
|
|
221
|
+
routes: routeHealth,
|
|
222
|
+
totalRoutes: _routes.size,
|
|
223
|
+
}), {
|
|
224
|
+
status: 200,
|
|
225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/** Clear all tracked routes (for testing). */
|
|
230
|
+
function resetRoutes() {
|
|
231
|
+
_routes.clear();
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=nextjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../src/adapters/nextjs.ts"],"names":[],"mappings":"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"}
|