@parsrun/server 0.1.27 → 0.1.29
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/app.js.map +1 -1
- package/dist/context.d.ts +21 -1
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.d.ts +7 -771
- package/dist/middleware/index.js.map +1 -1
- package/dist/module-loader.js.map +1 -1
- package/dist/quota-enforcement-CN3z5bfc.d.ts +744 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/validation/index.js.map +1 -1
- package/package.json +25 -15
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
import { HonoContext, HonoNext, CorsConfig, ApiResponse } from './context.js';
|
|
2
|
+
import * as hono from 'hono';
|
|
3
|
+
import * as hono_utils_types from 'hono/utils/types';
|
|
4
|
+
import { ErrorTransport } from '@parsrun/core/transports';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @parsrun/server - Auth Middleware
|
|
8
|
+
* JWT authentication middleware
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* JWT payload structure
|
|
13
|
+
*/
|
|
14
|
+
interface JwtPayload {
|
|
15
|
+
sub: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
tenantId?: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
permissions?: string[];
|
|
20
|
+
iat?: number;
|
|
21
|
+
exp?: number;
|
|
22
|
+
jti?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* JWT verification function type
|
|
26
|
+
*/
|
|
27
|
+
type JwtVerifier = (token: string) => Promise<JwtPayload | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Auth middleware options
|
|
30
|
+
*/
|
|
31
|
+
interface AuthMiddlewareOptions {
|
|
32
|
+
/** JWT verification function */
|
|
33
|
+
verify: JwtVerifier;
|
|
34
|
+
/** Header name for token (default: Authorization) */
|
|
35
|
+
header?: string;
|
|
36
|
+
/** Token prefix (default: Bearer) */
|
|
37
|
+
prefix?: string;
|
|
38
|
+
/** Cookie name for token (alternative to header) */
|
|
39
|
+
cookie?: string;
|
|
40
|
+
/** Skip auth for certain requests */
|
|
41
|
+
skip?: (c: HonoContext) => boolean;
|
|
42
|
+
/** Custom error message */
|
|
43
|
+
message?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Auth middleware - requires valid JWT
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { verifyJwt } from '@parsrun/auth';
|
|
51
|
+
*
|
|
52
|
+
* const authMiddleware = auth({
|
|
53
|
+
* verify: (token) => verifyJwt(token, secret),
|
|
54
|
+
* cookie: 'auth_token',
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* app.use('/api/*', authMiddleware);
|
|
58
|
+
*
|
|
59
|
+
* // Access user in handlers
|
|
60
|
+
* app.get('/api/me', (c) => {
|
|
61
|
+
* const user = c.get('user');
|
|
62
|
+
* return c.json({ user });
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function auth(options: AuthMiddlewareOptions): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Optional auth middleware - sets user if token present, but doesn't require it
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* app.use('/api/public/*', optionalAuth({
|
|
73
|
+
* verify: (token) => verifyJwt(token, secret),
|
|
74
|
+
* }));
|
|
75
|
+
*
|
|
76
|
+
* // User may or may not be present
|
|
77
|
+
* app.get('/api/public/items', (c) => {
|
|
78
|
+
* const user = c.get('user'); // may be undefined
|
|
79
|
+
* // Return different data based on auth status
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function optionalAuth(options: Omit<AuthMiddlewareOptions, "message">): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Create auth middleware from verifier function
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const { auth, optionalAuth } = createAuthMiddleware({
|
|
90
|
+
* verify: async (token) => {
|
|
91
|
+
* return verifyJwt(token, process.env.JWT_SECRET);
|
|
92
|
+
* },
|
|
93
|
+
* cookie: 'session',
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* app.use('/api/*', auth);
|
|
97
|
+
* app.use('/public/*', optionalAuth);
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare function createAuthMiddleware(baseOptions: Omit<AuthMiddlewareOptions, "skip" | "message">): {
|
|
101
|
+
auth: (options?: Partial<AuthMiddlewareOptions>) => (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
102
|
+
optionalAuth: (options?: Partial<Omit<AuthMiddlewareOptions, "message">>) => (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @parsrun/server - CORS Middleware
|
|
107
|
+
* Cross-Origin Resource Sharing configuration
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* CORS middleware
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* app.use('*', cors({
|
|
116
|
+
* origin: ['https://example.com', 'https://app.example.com'],
|
|
117
|
+
* credentials: true,
|
|
118
|
+
* }));
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function cors(config?: Partial<CorsConfig>): (c: HonoContext, next: HonoNext) => Promise<Response | void>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @parsrun/server - CSRF Middleware
|
|
125
|
+
* Cross-Site Request Forgery protection
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* CSRF options
|
|
130
|
+
*/
|
|
131
|
+
interface CsrfOptions {
|
|
132
|
+
/** Cookie name for CSRF token */
|
|
133
|
+
cookieName?: string;
|
|
134
|
+
/** Header name for CSRF token */
|
|
135
|
+
headerName?: string;
|
|
136
|
+
/** Methods that require CSRF validation */
|
|
137
|
+
methods?: string[];
|
|
138
|
+
/** Paths to exclude from CSRF protection */
|
|
139
|
+
excludePaths?: string[];
|
|
140
|
+
/** Skip CSRF for certain requests */
|
|
141
|
+
skip?: (c: HonoContext) => boolean;
|
|
142
|
+
/** Token generator */
|
|
143
|
+
generateToken?: () => string;
|
|
144
|
+
/** Cookie options */
|
|
145
|
+
cookie?: {
|
|
146
|
+
secure?: boolean;
|
|
147
|
+
httpOnly?: boolean;
|
|
148
|
+
sameSite?: "strict" | "lax" | "none";
|
|
149
|
+
path?: string;
|
|
150
|
+
maxAge?: number;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* CSRF protection middleware
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* app.use('*', csrf({
|
|
159
|
+
* cookieName: '_csrf',
|
|
160
|
+
* headerName: 'X-CSRF-Token',
|
|
161
|
+
* methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
|
|
162
|
+
* cookie: {
|
|
163
|
+
* secure: true,
|
|
164
|
+
* sameSite: 'strict',
|
|
165
|
+
* },
|
|
166
|
+
* }));
|
|
167
|
+
*
|
|
168
|
+
* // Get token in handler
|
|
169
|
+
* app.get('/csrf-token', (c) => {
|
|
170
|
+
* return c.json({ token: c.get('csrfToken') });
|
|
171
|
+
* });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare function csrf(options?: CsrfOptions): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Double Submit Cookie pattern
|
|
177
|
+
* Generates a token and validates it matches between cookie and header
|
|
178
|
+
*/
|
|
179
|
+
declare function doubleSubmitCookie(options?: CsrfOptions): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Base API error class
|
|
183
|
+
*/
|
|
184
|
+
declare class ApiError extends Error {
|
|
185
|
+
readonly statusCode: number;
|
|
186
|
+
readonly code: string;
|
|
187
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
188
|
+
constructor(statusCode: number, code: string, message: string, details?: Record<string, unknown> | undefined);
|
|
189
|
+
toResponse(): ApiResponse<never>;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 400 Bad Request
|
|
193
|
+
*/
|
|
194
|
+
declare class BadRequestError extends ApiError {
|
|
195
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 401 Unauthorized
|
|
199
|
+
*/
|
|
200
|
+
declare class UnauthorizedError extends ApiError {
|
|
201
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 403 Forbidden
|
|
205
|
+
*/
|
|
206
|
+
declare class ForbiddenError extends ApiError {
|
|
207
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 404 Not Found
|
|
211
|
+
*/
|
|
212
|
+
declare class NotFoundError extends ApiError {
|
|
213
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 409 Conflict
|
|
217
|
+
*/
|
|
218
|
+
declare class ConflictError extends ApiError {
|
|
219
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 422 Unprocessable Entity (Validation Error)
|
|
223
|
+
*/
|
|
224
|
+
declare class ValidationError extends ApiError {
|
|
225
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 429 Too Many Requests
|
|
229
|
+
*/
|
|
230
|
+
declare class RateLimitError extends ApiError {
|
|
231
|
+
readonly retryAfter?: number | undefined;
|
|
232
|
+
constructor(message?: string, retryAfter?: number | undefined);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 500 Internal Server Error
|
|
236
|
+
*/
|
|
237
|
+
declare class InternalError extends ApiError {
|
|
238
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 503 Service Unavailable
|
|
242
|
+
*/
|
|
243
|
+
declare class ServiceUnavailableError extends ApiError {
|
|
244
|
+
constructor(message?: string, details?: Record<string, unknown>);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Error handler options
|
|
248
|
+
*/
|
|
249
|
+
interface ErrorHandlerOptions {
|
|
250
|
+
/** Include stack trace in development */
|
|
251
|
+
includeStack?: boolean;
|
|
252
|
+
/** Custom error logger */
|
|
253
|
+
onError?: (error: Error, c: HonoContext) => void;
|
|
254
|
+
/**
|
|
255
|
+
* Error transport for external error tracking (e.g., Sentry)
|
|
256
|
+
* Automatically captures exceptions with request context
|
|
257
|
+
*/
|
|
258
|
+
errorTransport?: ErrorTransport;
|
|
259
|
+
/**
|
|
260
|
+
* Capture all errors including 4xx client errors
|
|
261
|
+
* By default, only 5xx server errors are captured
|
|
262
|
+
* @default false
|
|
263
|
+
*/
|
|
264
|
+
captureAllErrors?: boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Custom function to determine if an error should be captured
|
|
267
|
+
* Overrides the default captureAllErrors behavior
|
|
268
|
+
*/
|
|
269
|
+
shouldCapture?: (error: Error, statusCode: number) => boolean;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Global error handler middleware
|
|
273
|
+
*
|
|
274
|
+
* @example Basic usage
|
|
275
|
+
* ```typescript
|
|
276
|
+
* app.use('*', errorHandler({
|
|
277
|
+
* includeStack: process.env.NODE_ENV === 'development',
|
|
278
|
+
* onError: (error, c) => {
|
|
279
|
+
* console.error(`[${c.get('requestId')}]`, error);
|
|
280
|
+
* },
|
|
281
|
+
* }));
|
|
282
|
+
* ```
|
|
283
|
+
*
|
|
284
|
+
* @example With Sentry error tracking
|
|
285
|
+
* ```typescript
|
|
286
|
+
* import { SentryTransport } from '@parsrun/core/transports';
|
|
287
|
+
*
|
|
288
|
+
* const sentry = new SentryTransport({
|
|
289
|
+
* dsn: process.env.SENTRY_DSN!,
|
|
290
|
+
* environment: process.env.NODE_ENV,
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* app.use('*', errorHandler({
|
|
294
|
+
* errorTransport: sentry,
|
|
295
|
+
* captureAllErrors: false, // Only capture 5xx errors
|
|
296
|
+
* }));
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
declare function errorHandler(options?: ErrorHandlerOptions): (c: HonoContext, next: HonoNext) => Promise<(Response & hono.TypedResponse<{
|
|
300
|
+
success: boolean;
|
|
301
|
+
error?: {
|
|
302
|
+
code: string;
|
|
303
|
+
message: string;
|
|
304
|
+
details?: {
|
|
305
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
306
|
+
} | undefined;
|
|
307
|
+
} | undefined;
|
|
308
|
+
meta?: {
|
|
309
|
+
page?: number | undefined | undefined;
|
|
310
|
+
limit?: number | undefined | undefined;
|
|
311
|
+
total?: number | undefined | undefined;
|
|
312
|
+
requestId?: string | undefined | undefined;
|
|
313
|
+
} | undefined;
|
|
314
|
+
}, 400, "json">) | (Response & hono.TypedResponse<{
|
|
315
|
+
success: boolean;
|
|
316
|
+
error?: {
|
|
317
|
+
code: string;
|
|
318
|
+
message: string;
|
|
319
|
+
details?: {
|
|
320
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
321
|
+
} | undefined;
|
|
322
|
+
} | undefined;
|
|
323
|
+
meta?: {
|
|
324
|
+
page?: number | undefined | undefined;
|
|
325
|
+
limit?: number | undefined | undefined;
|
|
326
|
+
total?: number | undefined | undefined;
|
|
327
|
+
requestId?: string | undefined | undefined;
|
|
328
|
+
} | undefined;
|
|
329
|
+
}, 500, "json">) | undefined>;
|
|
330
|
+
/**
|
|
331
|
+
* Not found handler
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```typescript
|
|
335
|
+
* app.notFound(notFoundHandler);
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
declare function notFoundHandler(c: HonoContext): Response & hono.TypedResponse<{
|
|
339
|
+
success: boolean;
|
|
340
|
+
error?: {
|
|
341
|
+
code: string;
|
|
342
|
+
message: string;
|
|
343
|
+
details?: {
|
|
344
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
345
|
+
} | undefined;
|
|
346
|
+
} | undefined;
|
|
347
|
+
meta?: {
|
|
348
|
+
page?: number | undefined | undefined;
|
|
349
|
+
limit?: number | undefined | undefined;
|
|
350
|
+
total?: number | undefined | undefined;
|
|
351
|
+
requestId?: string | undefined | undefined;
|
|
352
|
+
} | undefined;
|
|
353
|
+
}, 404, "json">;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* @parsrun/server - Rate Limit Middleware
|
|
357
|
+
* Request throttling with multiple storage backends
|
|
358
|
+
*/
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Rate limit storage interface
|
|
362
|
+
*/
|
|
363
|
+
interface RateLimitStorage {
|
|
364
|
+
/** Get current count for key */
|
|
365
|
+
get(key: string): Promise<number>;
|
|
366
|
+
/** Increment count and set expiry */
|
|
367
|
+
increment(key: string, windowMs: number): Promise<number>;
|
|
368
|
+
/** Reset count for key */
|
|
369
|
+
reset(key: string): Promise<void>;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* In-memory rate limit storage
|
|
373
|
+
* For single-instance deployments or development
|
|
374
|
+
*/
|
|
375
|
+
declare class MemoryRateLimitStorage implements RateLimitStorage {
|
|
376
|
+
private store;
|
|
377
|
+
get(key: string): Promise<number>;
|
|
378
|
+
increment(key: string, windowMs: number): Promise<number>;
|
|
379
|
+
reset(key: string): Promise<void>;
|
|
380
|
+
/** Clean up expired entries */
|
|
381
|
+
cleanup(): void;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Rate limit options
|
|
385
|
+
*/
|
|
386
|
+
interface RateLimitOptions {
|
|
387
|
+
/** Time window in milliseconds */
|
|
388
|
+
windowMs?: number;
|
|
389
|
+
/** Maximum requests per window */
|
|
390
|
+
max?: number;
|
|
391
|
+
/** Generate key from request (default: IP address) */
|
|
392
|
+
keyGenerator?: (c: HonoContext) => string;
|
|
393
|
+
/** Skip rate limiting for certain requests */
|
|
394
|
+
skip?: (c: HonoContext) => boolean;
|
|
395
|
+
/** Custom storage backend */
|
|
396
|
+
storage?: RateLimitStorage;
|
|
397
|
+
/** Error message */
|
|
398
|
+
message?: string;
|
|
399
|
+
/** Include rate limit headers */
|
|
400
|
+
headers?: boolean;
|
|
401
|
+
/** Handler when limit is exceeded */
|
|
402
|
+
onLimitReached?: (c: HonoContext, key: string) => void;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Rate limit middleware
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* // Basic usage - 100 requests per minute
|
|
410
|
+
* app.use('/api/*', rateLimit({
|
|
411
|
+
* windowMs: 60 * 1000,
|
|
412
|
+
* max: 100,
|
|
413
|
+
* }));
|
|
414
|
+
*
|
|
415
|
+
* // Per-user rate limiting
|
|
416
|
+
* app.use('/api/*', rateLimit({
|
|
417
|
+
* keyGenerator: (c) => c.get('user')?.id ?? getIP(c),
|
|
418
|
+
* max: 1000,
|
|
419
|
+
* }));
|
|
420
|
+
*
|
|
421
|
+
* // Strict limit for auth endpoints
|
|
422
|
+
* app.use('/api/auth/*', rateLimit({
|
|
423
|
+
* windowMs: 15 * 60 * 1000, // 15 minutes
|
|
424
|
+
* max: 5,
|
|
425
|
+
* message: 'Too many login attempts',
|
|
426
|
+
* }));
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
declare function rateLimit(options?: RateLimitOptions): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
430
|
+
/**
|
|
431
|
+
* Create rate limiter for specific routes
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* const apiLimiter = createRateLimiter({
|
|
436
|
+
* windowMs: 60000,
|
|
437
|
+
* max: 100,
|
|
438
|
+
* });
|
|
439
|
+
*
|
|
440
|
+
* app.use('/api/*', apiLimiter.middleware);
|
|
441
|
+
*
|
|
442
|
+
* // Reset limit for a user after successful auth
|
|
443
|
+
* await apiLimiter.reset('user:123');
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
declare function createRateLimiter(options?: RateLimitOptions): {
|
|
447
|
+
middleware: (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
448
|
+
storage: RateLimitStorage;
|
|
449
|
+
reset: (key: string) => Promise<void>;
|
|
450
|
+
get: (key: string) => Promise<number>;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @parsrun/server - Request Logger Middleware
|
|
455
|
+
* HTTP request/response logging
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Request logger options
|
|
460
|
+
*/
|
|
461
|
+
interface RequestLoggerOptions {
|
|
462
|
+
/** Skip logging for certain paths */
|
|
463
|
+
skip?: (c: HonoContext) => boolean;
|
|
464
|
+
/** Custom log format */
|
|
465
|
+
format?: "json" | "combined" | "short";
|
|
466
|
+
/** Include request body in logs */
|
|
467
|
+
includeBody?: boolean;
|
|
468
|
+
/** Include response body in logs (be careful with large responses) */
|
|
469
|
+
includeResponseBody?: boolean;
|
|
470
|
+
/** Maximum body length to log */
|
|
471
|
+
maxBodyLength?: number;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Request logger middleware
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```typescript
|
|
478
|
+
* app.use('*', requestLogger({
|
|
479
|
+
* skip: (c) => c.req.path === '/health',
|
|
480
|
+
* format: 'json',
|
|
481
|
+
* }));
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
484
|
+
declare function requestLogger(options?: RequestLoggerOptions): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* @parsrun/server - Usage Tracking Middleware
|
|
488
|
+
* Automatically track API usage per request
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Usage service interface (from @parsrun/payments)
|
|
493
|
+
* Defined here to avoid circular dependency
|
|
494
|
+
*/
|
|
495
|
+
interface UsageServiceLike {
|
|
496
|
+
trackUsage(options: {
|
|
497
|
+
tenantId: string;
|
|
498
|
+
customerId: string;
|
|
499
|
+
subscriptionId?: string;
|
|
500
|
+
featureKey: string;
|
|
501
|
+
quantity?: number;
|
|
502
|
+
metadata?: Record<string, unknown>;
|
|
503
|
+
idempotencyKey?: string;
|
|
504
|
+
}): Promise<unknown>;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Usage tracking middleware options
|
|
508
|
+
*/
|
|
509
|
+
interface UsageTrackingOptions {
|
|
510
|
+
/**
|
|
511
|
+
* Usage service instance
|
|
512
|
+
*/
|
|
513
|
+
usageService: UsageServiceLike;
|
|
514
|
+
/**
|
|
515
|
+
* Feature key to track
|
|
516
|
+
* Can be a static string or a function that extracts it from context
|
|
517
|
+
* @default "api_calls"
|
|
518
|
+
*/
|
|
519
|
+
featureKey?: string | ((c: HonoContext) => string);
|
|
520
|
+
/**
|
|
521
|
+
* Quantity to track
|
|
522
|
+
* Can be a static number or a function that calculates it from context
|
|
523
|
+
* @default 1
|
|
524
|
+
*/
|
|
525
|
+
quantity?: number | ((c: HonoContext) => number);
|
|
526
|
+
/**
|
|
527
|
+
* Skip tracking for certain requests
|
|
528
|
+
*/
|
|
529
|
+
skip?: (c: HonoContext) => boolean;
|
|
530
|
+
/**
|
|
531
|
+
* When to track: before or after the request
|
|
532
|
+
* @default "response"
|
|
533
|
+
*/
|
|
534
|
+
trackOn?: "request" | "response";
|
|
535
|
+
/**
|
|
536
|
+
* Only track successful responses (2xx)
|
|
537
|
+
* @default true
|
|
538
|
+
*/
|
|
539
|
+
successOnly?: boolean;
|
|
540
|
+
/**
|
|
541
|
+
* Custom customer ID extractor
|
|
542
|
+
* @default Uses c.get("user")?.id
|
|
543
|
+
*/
|
|
544
|
+
getCustomerId?: (c: HonoContext) => string | undefined;
|
|
545
|
+
/**
|
|
546
|
+
* Custom tenant ID extractor
|
|
547
|
+
* @default Uses c.get("tenant")?.id or c.get("user")?.tenantId
|
|
548
|
+
*/
|
|
549
|
+
getTenantId?: (c: HonoContext) => string | undefined;
|
|
550
|
+
/**
|
|
551
|
+
* Custom subscription ID extractor
|
|
552
|
+
*/
|
|
553
|
+
getSubscriptionId?: (c: HonoContext) => string | undefined;
|
|
554
|
+
/**
|
|
555
|
+
* Include request metadata
|
|
556
|
+
* @default true
|
|
557
|
+
*/
|
|
558
|
+
includeMetadata?: boolean;
|
|
559
|
+
/**
|
|
560
|
+
* Generate idempotency key to prevent duplicates
|
|
561
|
+
*/
|
|
562
|
+
getIdempotencyKey?: (c: HonoContext) => string | undefined;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Usage tracking middleware
|
|
566
|
+
*
|
|
567
|
+
* Automatically tracks API usage for authenticated requests.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```typescript
|
|
571
|
+
* import { usageTracking } from "@parsrun/server";
|
|
572
|
+
* import { createUsageService, createMemoryUsageStorage } from "@parsrun/payments";
|
|
573
|
+
*
|
|
574
|
+
* const usageService = createUsageService({
|
|
575
|
+
* storage: createMemoryUsageStorage(),
|
|
576
|
+
* });
|
|
577
|
+
*
|
|
578
|
+
* // Track all API calls
|
|
579
|
+
* app.use("/api/*", usageTracking({
|
|
580
|
+
* usageService,
|
|
581
|
+
* featureKey: "api_calls",
|
|
582
|
+
* }));
|
|
583
|
+
*
|
|
584
|
+
* // Track with custom feature key based on route
|
|
585
|
+
* app.use("/api/ai/*", usageTracking({
|
|
586
|
+
* usageService,
|
|
587
|
+
* featureKey: "ai_requests",
|
|
588
|
+
* quantity: (c) => {
|
|
589
|
+
* // Track tokens used from response
|
|
590
|
+
* return c.get("tokensUsed") ?? 1;
|
|
591
|
+
* },
|
|
592
|
+
* }));
|
|
593
|
+
*
|
|
594
|
+
* // Skip certain routes
|
|
595
|
+
* app.use("/api/*", usageTracking({
|
|
596
|
+
* usageService,
|
|
597
|
+
* skip: (c) => c.req.path.startsWith("/api/health"),
|
|
598
|
+
* }));
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
declare function usageTracking(options: UsageTrackingOptions): (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
602
|
+
/**
|
|
603
|
+
* Create usage tracking middleware with pre-configured options
|
|
604
|
+
*/
|
|
605
|
+
declare function createUsageTracking(baseOptions: UsageTrackingOptions): (overrides?: Partial<UsageTrackingOptions>) => (c: HonoContext, next: HonoNext) => Promise<void>;
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* @parsrun/server - Quota Enforcement Middleware
|
|
609
|
+
* Enforce usage quotas before processing requests
|
|
610
|
+
*/
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Quota check result interface (from @parsrun/payments)
|
|
614
|
+
*/
|
|
615
|
+
interface QuotaCheckResult {
|
|
616
|
+
allowed: boolean;
|
|
617
|
+
currentUsage: number;
|
|
618
|
+
limit: number | null;
|
|
619
|
+
remaining: number | null;
|
|
620
|
+
wouldExceed: boolean;
|
|
621
|
+
percentAfter: number | null;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Quota manager interface (from @parsrun/payments)
|
|
625
|
+
* Defined here to avoid circular dependency
|
|
626
|
+
*/
|
|
627
|
+
interface QuotaManagerLike {
|
|
628
|
+
checkQuota(customerId: string, featureKey: string, quantity?: number): Promise<QuotaCheckResult>;
|
|
629
|
+
enforceQuota(customerId: string, featureKey: string, quantity?: number): Promise<void>;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Quota exceeded error class
|
|
633
|
+
*/
|
|
634
|
+
declare class QuotaExceededError extends Error {
|
|
635
|
+
readonly featureKey: string;
|
|
636
|
+
readonly limit: number | null;
|
|
637
|
+
readonly currentUsage: number;
|
|
638
|
+
readonly requestedQuantity: number;
|
|
639
|
+
readonly statusCode = 429;
|
|
640
|
+
readonly code = "QUOTA_EXCEEDED";
|
|
641
|
+
constructor(featureKey: string, limit: number | null, currentUsage: number, requestedQuantity?: number);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Quota enforcement middleware options
|
|
645
|
+
*/
|
|
646
|
+
interface QuotaEnforcementOptions {
|
|
647
|
+
/**
|
|
648
|
+
* Quota manager instance
|
|
649
|
+
*/
|
|
650
|
+
quotaManager: QuotaManagerLike;
|
|
651
|
+
/**
|
|
652
|
+
* Feature key to check
|
|
653
|
+
* Can be a static string or a function that extracts it from context
|
|
654
|
+
*/
|
|
655
|
+
featureKey: string | ((c: HonoContext) => string);
|
|
656
|
+
/**
|
|
657
|
+
* Quantity to check (default: 1)
|
|
658
|
+
*/
|
|
659
|
+
quantity?: number | ((c: HonoContext) => number);
|
|
660
|
+
/**
|
|
661
|
+
* Skip quota check for certain requests
|
|
662
|
+
*/
|
|
663
|
+
skip?: (c: HonoContext) => boolean;
|
|
664
|
+
/**
|
|
665
|
+
* Custom customer ID extractor
|
|
666
|
+
* @default Uses c.get("user")?.id
|
|
667
|
+
*/
|
|
668
|
+
getCustomerId?: (c: HonoContext) => string | undefined;
|
|
669
|
+
/**
|
|
670
|
+
* Include quota headers in response
|
|
671
|
+
* @default true
|
|
672
|
+
*/
|
|
673
|
+
includeHeaders?: boolean;
|
|
674
|
+
/**
|
|
675
|
+
* Custom error handler
|
|
676
|
+
*/
|
|
677
|
+
onQuotaExceeded?: (c: HonoContext, result: QuotaCheckResult, featureKey: string) => Response | void;
|
|
678
|
+
/**
|
|
679
|
+
* Soft limit mode - warn but don't block
|
|
680
|
+
* @default false
|
|
681
|
+
*/
|
|
682
|
+
softLimit?: boolean;
|
|
683
|
+
/**
|
|
684
|
+
* Callback when quota is close to limit (>80%)
|
|
685
|
+
*/
|
|
686
|
+
onQuotaWarning?: (c: HonoContext, result: QuotaCheckResult, featureKey: string) => void;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Quota enforcement middleware
|
|
690
|
+
*
|
|
691
|
+
* Checks and enforces usage quotas before processing requests.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```typescript
|
|
695
|
+
* import { quotaEnforcement } from "@parsrun/server";
|
|
696
|
+
* import { createQuotaManager, createMemoryUsageStorage } from "@parsrun/payments";
|
|
697
|
+
*
|
|
698
|
+
* const quotaManager = createQuotaManager({
|
|
699
|
+
* storage: createMemoryUsageStorage(),
|
|
700
|
+
* });
|
|
701
|
+
*
|
|
702
|
+
* // Enforce API call quota
|
|
703
|
+
* app.use("/api/*", quotaEnforcement({
|
|
704
|
+
* quotaManager,
|
|
705
|
+
* featureKey: "api_calls",
|
|
706
|
+
* }));
|
|
707
|
+
*
|
|
708
|
+
* // Enforce with dynamic feature key
|
|
709
|
+
* app.use("/api/*", quotaEnforcement({
|
|
710
|
+
* quotaManager,
|
|
711
|
+
* featureKey: (c) => {
|
|
712
|
+
* if (c.req.path.startsWith("/api/ai")) return "ai_requests";
|
|
713
|
+
* return "api_calls";
|
|
714
|
+
* },
|
|
715
|
+
* }));
|
|
716
|
+
*
|
|
717
|
+
* // Soft limit mode (warn but allow)
|
|
718
|
+
* app.use("/api/*", quotaEnforcement({
|
|
719
|
+
* quotaManager,
|
|
720
|
+
* featureKey: "api_calls",
|
|
721
|
+
* softLimit: true,
|
|
722
|
+
* onQuotaWarning: (c, result) => {
|
|
723
|
+
* console.warn("Quota warning:", result);
|
|
724
|
+
* },
|
|
725
|
+
* }));
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
declare function quotaEnforcement(options: QuotaEnforcementOptions): (c: HonoContext, next: HonoNext) => Promise<void | Response>;
|
|
729
|
+
/**
|
|
730
|
+
* Create quota enforcement middleware with pre-configured options
|
|
731
|
+
*/
|
|
732
|
+
declare function createQuotaEnforcement(baseOptions: Omit<QuotaEnforcementOptions, "featureKey">): (featureKey: string | ((c: HonoContext) => string)) => (c: HonoContext, next: HonoNext) => Promise<void | Response>;
|
|
733
|
+
/**
|
|
734
|
+
* Multiple quota enforcement
|
|
735
|
+
* Check multiple features at once
|
|
736
|
+
*/
|
|
737
|
+
declare function multiQuotaEnforcement(options: Omit<QuotaEnforcementOptions, "featureKey"> & {
|
|
738
|
+
features: Array<{
|
|
739
|
+
featureKey: string;
|
|
740
|
+
quantity?: number | ((c: HonoContext) => number);
|
|
741
|
+
}>;
|
|
742
|
+
}): (c: HonoContext, next: HonoNext) => Promise<void | Response>;
|
|
743
|
+
|
|
744
|
+
export { type AuthMiddlewareOptions as A, BadRequestError as B, type CsrfOptions as C, type QuotaCheckResult as D, type ErrorHandlerOptions as E, ForbiddenError as F, InternalError as I, type JwtPayload as J, MemoryRateLimitStorage as M, NotFoundError as N, QuotaExceededError as Q, RateLimitError as R, ServiceUnavailableError as S, UnauthorizedError as U, ValidationError as V, auth as a, type JwtVerifier as b, createAuthMiddleware as c, cors as d, csrf as e, doubleSubmitCookie as f, errorHandler as g, ApiError as h, ConflictError as i, createRateLimiter as j, type RateLimitOptions as k, type RateLimitStorage as l, requestLogger as m, notFoundHandler as n, optionalAuth as o, type RequestLoggerOptions as p, createUsageTracking as q, rateLimit as r, type UsageTrackingOptions as s, type UsageServiceLike as t, usageTracking as u, quotaEnforcement as v, createQuotaEnforcement as w, multiQuotaEnforcement as x, type QuotaEnforcementOptions as y, type QuotaManagerLike as z };
|