ai.matey.http.core 0.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.
- package/LICENSE +21 -0
- package/dist/cjs/auth.js +173 -0
- package/dist/cjs/auth.js.map +1 -0
- package/dist/cjs/cors.js +140 -0
- package/dist/cjs/cors.js.map +1 -0
- package/dist/cjs/error-handler.js +147 -0
- package/dist/cjs/error-handler.js.map +1 -0
- package/dist/cjs/handler.js +335 -0
- package/dist/cjs/handler.js.map +1 -0
- package/dist/cjs/health.js +218 -0
- package/dist/cjs/health.js.map +1 -0
- package/dist/cjs/index.js +83 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/rate-limiter.js +163 -0
- package/dist/cjs/rate-limiter.js.map +1 -0
- package/dist/cjs/request-parser.js +141 -0
- package/dist/cjs/request-parser.js.map +1 -0
- package/dist/cjs/response-formatter.js +218 -0
- package/dist/cjs/response-formatter.js.map +1 -0
- package/dist/cjs/route-matcher.js +178 -0
- package/dist/cjs/route-matcher.js.map +1 -0
- package/dist/cjs/streaming-handler.js +120 -0
- package/dist/cjs/streaming-handler.js.map +1 -0
- package/dist/cjs/types.js +11 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/auth.js +163 -0
- package/dist/esm/auth.js.map +1 -0
- package/dist/esm/cors.js +134 -0
- package/dist/esm/cors.js.map +1 -0
- package/dist/esm/error-handler.js +137 -0
- package/dist/esm/error-handler.js.map +1 -0
- package/dist/esm/handler.js +331 -0
- package/dist/esm/handler.js.map +1 -0
- package/dist/esm/health.js +210 -0
- package/dist/esm/health.js.map +1 -0
- package/dist/esm/index.js +30 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/rate-limiter.js +156 -0
- package/dist/esm/rate-limiter.js.map +1 -0
- package/dist/esm/request-parser.js +136 -0
- package/dist/esm/request-parser.js.map +1 -0
- package/dist/esm/response-formatter.js +206 -0
- package/dist/esm/response-formatter.js.map +1 -0
- package/dist/esm/route-matcher.js +171 -0
- package/dist/esm/route-matcher.js.map +1 -0
- package/dist/esm/streaming-handler.js +112 -0
- package/dist/esm/streaming-handler.js.map +1 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/auth.d.ts +37 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/cors.d.ts +26 -0
- package/dist/types/cors.d.ts.map +1 -0
- package/dist/types/error-handler.d.ts +38 -0
- package/dist/types/error-handler.d.ts.map +1 -0
- package/dist/types/handler.d.ts +45 -0
- package/dist/types/handler.d.ts.map +1 -0
- package/dist/types/health.d.ts +164 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/rate-limiter.d.ts +56 -0
- package/dist/types/rate-limiter.d.ts.map +1 -0
- package/dist/types/request-parser.d.ts +22 -0
- package/dist/types/request-parser.d.ts.map +1 -0
- package/dist/types/response-formatter.d.ts +49 -0
- package/dist/types/response-formatter.d.ts.map +1 -0
- package/dist/types/route-matcher.d.ts +45 -0
- package/dist/types/route-matcher.d.ts.map +1 -0
- package/dist/types/streaming-handler.d.ts +40 -0
- package/dist/types/streaming-handler.d.ts.map +1 -0
- package/dist/types/types.d.ts +398 -0
- package/dist/types/types.d.ts.map +1 -0
- package/package.json +73 -0
- package/readme.md +60 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Utilities
|
|
3
|
+
*
|
|
4
|
+
* Health check endpoint for monitoring and load balancers.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Health check handler
|
|
10
|
+
*/
|
|
11
|
+
export class HealthCheck {
|
|
12
|
+
config;
|
|
13
|
+
customChecks;
|
|
14
|
+
metadata;
|
|
15
|
+
startTime;
|
|
16
|
+
router;
|
|
17
|
+
constructor(bridgeOrRouter, config = {}) {
|
|
18
|
+
this.config = {
|
|
19
|
+
serviceName: 'ai.matey',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
includeUptime: true,
|
|
22
|
+
includeChecks: true,
|
|
23
|
+
checkBackends: true,
|
|
24
|
+
...config,
|
|
25
|
+
};
|
|
26
|
+
this.customChecks = config.customChecks || {};
|
|
27
|
+
this.metadata = config.metadata || {};
|
|
28
|
+
this.startTime = Date.now();
|
|
29
|
+
// Detect if bridge or router
|
|
30
|
+
if ('getBackends' in bridgeOrRouter) {
|
|
31
|
+
this.router = bridgeOrRouter;
|
|
32
|
+
}
|
|
33
|
+
// Bridge support can be added in the future
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Perform health check
|
|
37
|
+
*/
|
|
38
|
+
async check() {
|
|
39
|
+
const checks = {};
|
|
40
|
+
// Basic system check
|
|
41
|
+
if (this.config.includeChecks) {
|
|
42
|
+
checks.system = {
|
|
43
|
+
status: 'healthy',
|
|
44
|
+
message: 'System operational',
|
|
45
|
+
};
|
|
46
|
+
// Check backend health (router only)
|
|
47
|
+
if (this.router && this.config.checkBackends) {
|
|
48
|
+
try {
|
|
49
|
+
// For now, assume healthy if router exists
|
|
50
|
+
// In the future, we can add a getBackends() method to Router
|
|
51
|
+
checks.backends = {
|
|
52
|
+
status: 'healthy',
|
|
53
|
+
message: 'Router operational',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
checks.backends = {
|
|
58
|
+
status: 'unhealthy',
|
|
59
|
+
message: 'Unable to check backends',
|
|
60
|
+
details: { error: error.message },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Run custom checks
|
|
65
|
+
for (const [name, checkFn] of Object.entries(this.customChecks)) {
|
|
66
|
+
try {
|
|
67
|
+
checks[name] = await checkFn();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
checks[name] = {
|
|
71
|
+
status: 'unhealthy',
|
|
72
|
+
message: `Check failed: ${error.message}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Determine overall status
|
|
78
|
+
const statuses = Object.values(checks).map((c) => c.status);
|
|
79
|
+
let overallStatus = 'healthy';
|
|
80
|
+
if (statuses.includes('unhealthy')) {
|
|
81
|
+
overallStatus = 'unhealthy';
|
|
82
|
+
}
|
|
83
|
+
else if (statuses.includes('degraded')) {
|
|
84
|
+
overallStatus = 'degraded';
|
|
85
|
+
}
|
|
86
|
+
const result = {
|
|
87
|
+
status: overallStatus,
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
service: this.config.serviceName,
|
|
90
|
+
version: this.config.version,
|
|
91
|
+
};
|
|
92
|
+
if (this.config.includeUptime) {
|
|
93
|
+
result.uptime = Date.now() - this.startTime;
|
|
94
|
+
}
|
|
95
|
+
if (this.config.includeChecks && Object.keys(checks).length > 0) {
|
|
96
|
+
result.checks = checks;
|
|
97
|
+
}
|
|
98
|
+
if (Object.keys(this.metadata).length > 0) {
|
|
99
|
+
result.metadata = this.metadata;
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Handle health check request
|
|
105
|
+
*/
|
|
106
|
+
async handle(_req, res) {
|
|
107
|
+
const result = await this.check();
|
|
108
|
+
// Set status code based on health
|
|
109
|
+
const statusCode = result.status === 'healthy' ? 200 : result.status === 'degraded' ? 200 : 503;
|
|
110
|
+
res.status(statusCode);
|
|
111
|
+
res.header('Content-Type', 'application/json');
|
|
112
|
+
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
113
|
+
res.send(result);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create health check handler
|
|
118
|
+
*
|
|
119
|
+
* @param bridgeOrRouter - Bridge or Router instance
|
|
120
|
+
* @param config - Health check configuration
|
|
121
|
+
* @returns Health check handler
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* import { createHealthCheck } from 'ai.matey.http';
|
|
126
|
+
*
|
|
127
|
+
* const healthCheck = createHealthCheck(bridge, {
|
|
128
|
+
* serviceName: 'my-ai-service',
|
|
129
|
+
* version: '1.0.0',
|
|
130
|
+
* });
|
|
131
|
+
*
|
|
132
|
+
* // In your HTTP server:
|
|
133
|
+
* if (req.url === '/health') {
|
|
134
|
+
* await healthCheck.handle(req, res);
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function createHealthCheck(bridgeOrRouter, config) {
|
|
139
|
+
return new HealthCheck(bridgeOrRouter, config);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create simple health check middleware
|
|
143
|
+
*
|
|
144
|
+
* @param bridgeOrRouter - Bridge or Router instance
|
|
145
|
+
* @param path - Health check path (default: '/health')
|
|
146
|
+
* @param config - Health check configuration
|
|
147
|
+
* @returns Middleware function
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* import { createHealthCheckMiddleware } from 'ai.matey.http';
|
|
152
|
+
*
|
|
153
|
+
* const healthMiddleware = createHealthCheckMiddleware(bridge);
|
|
154
|
+
*
|
|
155
|
+
* // Use in HTTP server
|
|
156
|
+
* app.use(healthMiddleware);
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function createHealthCheckMiddleware(bridgeOrRouter, path = '/health', config) {
|
|
160
|
+
const healthCheck = new HealthCheck(bridgeOrRouter, config);
|
|
161
|
+
return async (req, res) => {
|
|
162
|
+
if (req.url === path || req.url?.startsWith(`${path}?`)) {
|
|
163
|
+
await healthCheck.handle(req, res);
|
|
164
|
+
return true; // Handled
|
|
165
|
+
}
|
|
166
|
+
return false; // Not handled
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Readiness check (for Kubernetes)
|
|
171
|
+
*
|
|
172
|
+
* Returns 200 when service is ready to accept traffic.
|
|
173
|
+
*/
|
|
174
|
+
export function createReadinessCheck(bridgeOrRouter) {
|
|
175
|
+
const healthCheck = new HealthCheck(bridgeOrRouter, {
|
|
176
|
+
includeUptime: false,
|
|
177
|
+
includeChecks: true,
|
|
178
|
+
checkBackends: true,
|
|
179
|
+
});
|
|
180
|
+
return async (_req, res) => {
|
|
181
|
+
const result = await healthCheck.check();
|
|
182
|
+
// Only ready if all checks are healthy
|
|
183
|
+
const isReady = result.status === 'healthy';
|
|
184
|
+
res.status(isReady ? 200 : 503);
|
|
185
|
+
res.header('Content-Type', 'application/json');
|
|
186
|
+
res.send({
|
|
187
|
+
ready: isReady,
|
|
188
|
+
timestamp: result.timestamp,
|
|
189
|
+
checks: result.checks,
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Liveness check (for Kubernetes)
|
|
195
|
+
*
|
|
196
|
+
* Returns 200 if service is alive (even if degraded).
|
|
197
|
+
*/
|
|
198
|
+
export function createLivenessCheck() {
|
|
199
|
+
const startTime = Date.now();
|
|
200
|
+
return (_req, res) => {
|
|
201
|
+
res.status(200);
|
|
202
|
+
res.header('Content-Type', 'application/json');
|
|
203
|
+
res.send({
|
|
204
|
+
alive: true,
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
uptime: Date.now() - startTime,
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Core Module
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic HTTP utilities for the Universal AI Adapter System.
|
|
5
|
+
* This package provides the core logic for HTTP request handling that can
|
|
6
|
+
* be adapted to any HTTP framework.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
// Core handler
|
|
11
|
+
export { CoreHTTPHandler } from './handler.js';
|
|
12
|
+
// Request parsing
|
|
13
|
+
export { parseRequest, extractBearerToken, getClientIP } from './request-parser.js';
|
|
14
|
+
// Response formatting
|
|
15
|
+
export { sendJSON, sendError, sendSSEHeaders, sendSSEChunk, sendSSEEvent, sendSSEDone, sendSSEError, sendText, sendNoContent, detectProviderFormat, } from './response-formatter.js';
|
|
16
|
+
// CORS
|
|
17
|
+
export { normalizeCORSOptions, handleCORS, handlePreflight, isPreflight } from './cors.js';
|
|
18
|
+
// Authentication
|
|
19
|
+
export { defaultAuthValidator, createBearerTokenValidator, createAPIKeyValidator, createBasicAuthValidator, combineAuthValidators, requireAllAuth, skipAuthForPaths, } from './auth.js';
|
|
20
|
+
// Rate limiting
|
|
21
|
+
export { RateLimiter, userIDKeyGenerator, tokenKeyGenerator, combineKeyGenerators, } from './rate-limiter.js';
|
|
22
|
+
// Error handling
|
|
23
|
+
export { defaultErrorHandler, createLoggingErrorHandler, createReportingErrorHandler, wrapErrorHandler, isRetryableError, isClientError, isServerError, } from './error-handler.js';
|
|
24
|
+
// Streaming
|
|
25
|
+
export { handleStreamingRequest, SSEKeepAlive, onClientDisconnect, supportsStreaming, createAbortController, } from './streaming-handler.js';
|
|
26
|
+
// Routing
|
|
27
|
+
export { RouteMatcher, createDefaultRoutes, normalizePath, applyPathPrefix, } from './route-matcher.js';
|
|
28
|
+
// Health checks
|
|
29
|
+
export { HealthCheck, createHealthCheck, createHealthCheckMiddleware, createReadinessCheck, createLivenessCheck, } from './health.js';
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAe;AACf,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AA2B/C,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEpF,sBAAsB;AACtB,OAAO,EACL,QAAQ,EACR,SAAS,EACT,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO;AACP,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE3F,iBAAiB;AACjB,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAEnB,gBAAgB;AAChB,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,iBAAiB;AACjB,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,2BAA2B,EAC3B,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,YAAY;AACZ,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,UAAU;AACV,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAE5B,gBAAgB;AAChB,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,GAKpB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter
|
|
3
|
+
*
|
|
4
|
+
* Implements rate limiting for HTTP requests using a sliding window algorithm.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { getClientIP } from './request-parser.js';
|
|
9
|
+
import { sendJSON } from './response-formatter.js';
|
|
10
|
+
/**
|
|
11
|
+
* Rate limiter class
|
|
12
|
+
*/
|
|
13
|
+
export class RateLimiter {
|
|
14
|
+
options;
|
|
15
|
+
store;
|
|
16
|
+
cleanupIntervalId;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = {
|
|
19
|
+
max: options.max,
|
|
20
|
+
windowMs: options.windowMs ?? 60000, // 1 minute default
|
|
21
|
+
keyGenerator: options.keyGenerator ?? defaultKeyGenerator,
|
|
22
|
+
handler: options.handler ?? defaultRateLimitHandler,
|
|
23
|
+
skip: options.skip ?? (() => false),
|
|
24
|
+
headers: options.headers ?? true,
|
|
25
|
+
};
|
|
26
|
+
this.store = new Map();
|
|
27
|
+
// Cleanup old entries periodically
|
|
28
|
+
this.cleanupIntervalId = setInterval(() => this.cleanup(), this.options.windowMs);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Dispose of the rate limiter and clean up resources.
|
|
32
|
+
* Call this when the rate limiter is no longer needed to prevent memory leaks.
|
|
33
|
+
*/
|
|
34
|
+
dispose() {
|
|
35
|
+
if (this.cleanupIntervalId) {
|
|
36
|
+
clearInterval(this.cleanupIntervalId);
|
|
37
|
+
this.cleanupIntervalId = undefined;
|
|
38
|
+
}
|
|
39
|
+
this.store.clear();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if request should be rate limited
|
|
43
|
+
*/
|
|
44
|
+
async check(req, res) {
|
|
45
|
+
// Check if rate limiting should be skipped
|
|
46
|
+
if (await this.options.skip(req)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const key = this.options.keyGenerator(req);
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
// Get or create state for this key
|
|
52
|
+
let state = this.store.get(key);
|
|
53
|
+
if (!state || now >= state.resetTime) {
|
|
54
|
+
// Create new window
|
|
55
|
+
state = {
|
|
56
|
+
count: 0,
|
|
57
|
+
resetTime: now + this.options.windowMs,
|
|
58
|
+
};
|
|
59
|
+
this.store.set(key, state);
|
|
60
|
+
}
|
|
61
|
+
// Increment request count
|
|
62
|
+
state.count++;
|
|
63
|
+
// Add rate limit headers
|
|
64
|
+
if (this.options.headers) {
|
|
65
|
+
res.setHeader('X-RateLimit-Limit', String(this.options.max));
|
|
66
|
+
res.setHeader('X-RateLimit-Remaining', String(Math.max(0, this.options.max - state.count)));
|
|
67
|
+
res.setHeader('X-RateLimit-Reset', String(Math.ceil(state.resetTime / 1000)));
|
|
68
|
+
}
|
|
69
|
+
// Check if limit exceeded
|
|
70
|
+
if (state.count > this.options.max) {
|
|
71
|
+
const retryAfter = Math.ceil((state.resetTime - now) / 1000);
|
|
72
|
+
if (this.options.headers) {
|
|
73
|
+
res.setHeader('Retry-After', String(retryAfter));
|
|
74
|
+
}
|
|
75
|
+
// Call custom handler
|
|
76
|
+
await this.options.handler(req, res, retryAfter);
|
|
77
|
+
return true; // Rate limited
|
|
78
|
+
}
|
|
79
|
+
return false; // Not rate limited
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Clean up expired entries
|
|
83
|
+
*/
|
|
84
|
+
cleanup() {
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
for (const [key, state] of this.store.entries()) {
|
|
87
|
+
if (now >= state.resetTime) {
|
|
88
|
+
this.store.delete(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reset rate limit for a specific key
|
|
94
|
+
*/
|
|
95
|
+
reset(key) {
|
|
96
|
+
this.store.delete(key);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Reset all rate limits
|
|
100
|
+
*/
|
|
101
|
+
resetAll() {
|
|
102
|
+
this.store.clear();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get current state for a key
|
|
106
|
+
*/
|
|
107
|
+
getState(key) {
|
|
108
|
+
return this.store.get(key);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Default key generator (uses IP address)
|
|
113
|
+
*/
|
|
114
|
+
function defaultKeyGenerator(req) {
|
|
115
|
+
return getClientIP(req);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Default rate limit handler
|
|
119
|
+
*/
|
|
120
|
+
function defaultRateLimitHandler(_req, res, retryAfter) {
|
|
121
|
+
sendJSON(res, {
|
|
122
|
+
error: {
|
|
123
|
+
message: 'Too many requests, please try again later.',
|
|
124
|
+
type: 'rate_limit_exceeded',
|
|
125
|
+
retryAfter,
|
|
126
|
+
},
|
|
127
|
+
}, 429);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create key generator from user ID in request body
|
|
131
|
+
*/
|
|
132
|
+
export function userIDKeyGenerator(req) {
|
|
133
|
+
// This would need to be called after body parsing
|
|
134
|
+
// For now, fall back to IP
|
|
135
|
+
return getClientIP(req);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create key generator from authorization token
|
|
139
|
+
*/
|
|
140
|
+
export function tokenKeyGenerator(req) {
|
|
141
|
+
const auth = req.headers.authorization;
|
|
142
|
+
if (auth) {
|
|
143
|
+
return auth;
|
|
144
|
+
}
|
|
145
|
+
return getClientIP(req);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create key generator that combines multiple factors
|
|
149
|
+
*/
|
|
150
|
+
export function combineKeyGenerators(...generators) {
|
|
151
|
+
return (req) => {
|
|
152
|
+
const parts = generators.map((gen) => gen(req));
|
|
153
|
+
return parts.join(':');
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Request Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses incoming HTTP requests into a standardized format for processing.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Parse incoming HTTP request
|
|
10
|
+
*/
|
|
11
|
+
export async function parseRequest(req, maxBodySize = 10 * 1024 * 1024 // 10MB default
|
|
12
|
+
) {
|
|
13
|
+
// Parse URL
|
|
14
|
+
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
15
|
+
const path = url.pathname;
|
|
16
|
+
const method = req.method?.toUpperCase() || 'GET';
|
|
17
|
+
// Parse query parameters
|
|
18
|
+
const query = {};
|
|
19
|
+
url.searchParams.forEach((value, key) => {
|
|
20
|
+
query[key] = value;
|
|
21
|
+
});
|
|
22
|
+
// Normalize headers
|
|
23
|
+
const headers = {};
|
|
24
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
headers[key.toLowerCase()] = value;
|
|
27
|
+
}
|
|
28
|
+
else if (Array.isArray(value)) {
|
|
29
|
+
headers[key.toLowerCase()] = value[0] || '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Parse body (if present)
|
|
33
|
+
let body = null;
|
|
34
|
+
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
|
35
|
+
const rawBody = await readBody(req, maxBodySize);
|
|
36
|
+
if (rawBody) {
|
|
37
|
+
const contentType = headers['content-type'] || '';
|
|
38
|
+
if (contentType.includes('application/json')) {
|
|
39
|
+
try {
|
|
40
|
+
body = JSON.parse(rawBody);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error(`Invalid JSON body: ${error instanceof Error ? error.message : String(error)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
47
|
+
body = parseFormData(rawBody);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Unknown content type, try JSON anyway
|
|
51
|
+
try {
|
|
52
|
+
body = JSON.parse(rawBody);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
body = rawBody;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Detect streaming request
|
|
61
|
+
const stream = body?.stream === true || headers['accept'] === 'text/event-stream';
|
|
62
|
+
return {
|
|
63
|
+
body,
|
|
64
|
+
headers,
|
|
65
|
+
path,
|
|
66
|
+
method,
|
|
67
|
+
query,
|
|
68
|
+
stream,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read request body as string
|
|
73
|
+
*/
|
|
74
|
+
function readBody(req, maxSize) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const chunks = [];
|
|
77
|
+
let totalSize = 0;
|
|
78
|
+
req.on('data', (chunk) => {
|
|
79
|
+
totalSize += chunk.length;
|
|
80
|
+
if (totalSize > maxSize) {
|
|
81
|
+
// Destroy the stream to stop receiving data and free resources
|
|
82
|
+
req.destroy();
|
|
83
|
+
reject(new Error(`Request body too large (max ${maxSize} bytes)`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
chunks.push(chunk);
|
|
87
|
+
});
|
|
88
|
+
req.on('end', () => {
|
|
89
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
90
|
+
resolve(body);
|
|
91
|
+
});
|
|
92
|
+
req.on('error', (error) => {
|
|
93
|
+
reject(error);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Parse form-encoded data
|
|
99
|
+
*/
|
|
100
|
+
function parseFormData(data) {
|
|
101
|
+
const params = new URLSearchParams(data);
|
|
102
|
+
const result = {};
|
|
103
|
+
params.forEach((value, key) => {
|
|
104
|
+
result[key] = value;
|
|
105
|
+
});
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Extract bearer token from Authorization header
|
|
110
|
+
*/
|
|
111
|
+
export function extractBearerToken(req) {
|
|
112
|
+
const auth = req.headers.authorization;
|
|
113
|
+
if (!auth) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const match = auth.match(/^Bearer\s+(.+)$/i);
|
|
117
|
+
return match ? match[1] || null : null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get client IP address from request
|
|
121
|
+
*/
|
|
122
|
+
export function getClientIP(req) {
|
|
123
|
+
// Check common proxy headers
|
|
124
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
125
|
+
if (forwarded) {
|
|
126
|
+
const ips = typeof forwarded === 'string' ? forwarded.split(',') : forwarded;
|
|
127
|
+
return ips[0]?.trim() || 'unknown';
|
|
128
|
+
}
|
|
129
|
+
const realIP = req.headers['x-real-ip'];
|
|
130
|
+
if (realIP && typeof realIP === 'string') {
|
|
131
|
+
return realIP;
|
|
132
|
+
}
|
|
133
|
+
// Fallback to socket address
|
|
134
|
+
return req.socket.remoteAddress || 'unknown';
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=request-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-parser.js","sourceRoot":"","sources":["../../src/request-parser.ts"],"names":[],"mappings":"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"}
|