bxo 0.0.2 → 0.0.3

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/index.ts CHANGED
@@ -30,13 +30,7 @@ export type Context<TConfig extends RouteConfig = {}> = {
30
30
  // Handler function type
31
31
  type Handler<TConfig extends RouteConfig = {}> = (ctx: Context<TConfig>) => Promise<any> | any;
32
32
 
33
- // Plugin interface (also exported from plugins/index.ts)
34
- interface Plugin {
35
- name?: string;
36
- onRequest?: (ctx: Context) => Promise<void> | void;
37
- onResponse?: (ctx: Context, response: any) => Promise<any> | any;
38
- onError?: (ctx: Context, error: Error) => Promise<any> | any;
39
- }
33
+ // Note: Plugin interface moved to plugins/index.ts as PluginFactory type
40
34
 
41
35
 
42
36
 
@@ -63,14 +57,14 @@ interface LifecycleHooks {
63
57
 
64
58
  export default class BXO {
65
59
  private routes: Route[] = [];
66
- private plugins: Plugin[] = [];
60
+ private plugins: BXO[] = [];
67
61
  private hooks: LifecycleHooks = {};
68
62
  private server?: any;
69
63
  private isRunning: boolean = false;
70
64
  private hotReloadEnabled: boolean = false;
71
65
  private watchedFiles: Set<string> = new Set();
72
66
 
73
- constructor() {}
67
+ constructor() { }
74
68
 
75
69
  // Lifecycle hook methods
76
70
  onBeforeStart(handler: () => Promise<void> | void): this {
@@ -118,9 +112,9 @@ export default class BXO {
118
112
  return this;
119
113
  }
120
114
 
121
- // Plugin system
122
- use(plugin: Plugin): this {
123
- this.plugins.push(plugin);
115
+ // Plugin system - now accepts other BXO instances
116
+ use(bxoInstance: BXO): this {
117
+ this.plugins.push(bxoInstance);
124
118
  return this;
125
119
  }
126
120
 
@@ -326,10 +320,10 @@ export default class BXO {
326
320
  await this.hooks.onRequest(ctx);
327
321
  }
328
322
 
329
- // Run plugin onRequest hooks
330
- for (const plugin of this.plugins) {
331
- if (plugin.onRequest) {
332
- await plugin.onRequest(ctx);
323
+ // Run BXO instance onRequest hooks
324
+ for (const bxoInstance of this.plugins) {
325
+ if (bxoInstance.hooks.onRequest) {
326
+ await bxoInstance.hooks.onRequest(ctx);
333
327
  }
334
328
  }
335
329
 
@@ -341,10 +335,10 @@ export default class BXO {
341
335
  response = await this.hooks.onResponse(ctx, response) || response;
342
336
  }
343
337
 
344
- // Run plugin onResponse hooks
345
- for (const plugin of this.plugins) {
346
- if (plugin.onResponse) {
347
- response = await plugin.onResponse(ctx, response) || response;
338
+ // Run BXO instance onResponse hooks
339
+ for (const bxoInstance of this.plugins) {
340
+ if (bxoInstance.hooks.onResponse) {
341
+ response = await bxoInstance.hooks.onResponse(ctx, response) || response;
348
342
  }
349
343
  }
350
344
 
@@ -378,9 +372,9 @@ export default class BXO {
378
372
  errorResponse = await this.hooks.onError(ctx, error as Error);
379
373
  }
380
374
 
381
- for (const plugin of this.plugins) {
382
- if (plugin.onError) {
383
- errorResponse = await plugin.onError(ctx, error as Error) || errorResponse;
375
+ for (const bxoInstance of this.plugins) {
376
+ if (bxoInstance.hooks.onError) {
377
+ errorResponse = await bxoInstance.hooks.onError(ctx, error as Error) || errorResponse;
384
378
  }
385
379
  }
386
380
 
@@ -414,7 +408,7 @@ export default class BXO {
414
408
  if (!this.hotReloadEnabled) return;
415
409
 
416
410
  const fs = require('fs');
417
-
411
+
418
412
  for (const watchPath of this.watchedFiles) {
419
413
  try {
420
414
  fs.watch(watchPath, { recursive: true }, async (eventType: string, filename: string) => {
@@ -516,19 +510,19 @@ export default class BXO {
516
510
  }
517
511
 
518
512
  console.log('🔄 Restarting BXO server...');
519
-
513
+
520
514
  await this.stop();
521
-
515
+
522
516
  // Small delay to ensure cleanup
523
517
  await new Promise(resolve => setTimeout(resolve, 100));
524
-
518
+
525
519
  await this.start(port, hostname);
526
520
 
527
521
  // After restart hook
528
522
  if (this.hooks.onAfterRestart) {
529
523
  await this.hooks.onAfterRestart();
530
524
  }
531
-
525
+
532
526
  } catch (error) {
533
527
  console.error('❌ Error restarting server:', error);
534
528
  throw error;
@@ -554,10 +548,14 @@ export default class BXO {
554
548
  }
555
549
  }
556
550
 
551
+ const error = (error: Error, status: number = 500) => {
552
+ return new Response(JSON.stringify({ error: error.message }), { status });
553
+ }
554
+
557
555
  // Export Zod for convenience
558
- export { z };
556
+ export { z, error };
559
557
 
560
- export type { Plugin } from './plugins';
558
+ export type { PluginFactory } from './plugins';
561
559
 
562
560
  // Export types for external use
563
561
  export type { RouteConfig };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bxo",
3
3
  "module": "index.ts",
4
- "version": "0.0.2",
4
+ "version": "0.0.3",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {
package/plugins/auth.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import BXO from '../index';
2
+
1
3
  interface AuthOptions {
2
4
  type: 'jwt' | 'bearer' | 'apikey';
3
5
  secret?: string;
@@ -6,7 +8,7 @@ interface AuthOptions {
6
8
  exclude?: string[];
7
9
  }
8
10
 
9
- export function auth(options: AuthOptions) {
11
+ export function auth(options: AuthOptions): BXO {
10
12
  const {
11
13
  type,
12
14
  secret,
@@ -15,84 +17,85 @@ export function auth(options: AuthOptions) {
15
17
  exclude = []
16
18
  } = options;
17
19
 
18
- return {
19
- name: 'auth',
20
- onRequest: async (ctx: any) => {
21
- const url = new URL(ctx.request.url);
22
- const pathname = url.pathname;
23
-
24
- // Skip auth for excluded paths
25
- if (exclude.some(path => {
26
- if (path.includes('*')) {
27
- const regex = new RegExp(path.replace(/\*/g, '.*'));
28
- return regex.test(pathname);
29
- }
30
- return pathname === path || pathname.startsWith(path);
31
- })) {
32
- return;
20
+ const authInstance = new BXO();
21
+
22
+ authInstance.onRequest(async (ctx: any) => {
23
+ const url = new URL(ctx.request.url);
24
+ const pathname = url.pathname;
25
+
26
+ // Skip auth for excluded paths
27
+ if (exclude.some(path => {
28
+ if (path.includes('*')) {
29
+ const regex = new RegExp(path.replace(/\*/g, '.*'));
30
+ return regex.test(pathname);
33
31
  }
32
+ return pathname === path || pathname.startsWith(path);
33
+ })) {
34
+ return;
35
+ }
34
36
 
35
- const authHeader = ctx.request.headers.get(header.toLowerCase());
36
-
37
- if (!authHeader) {
38
- throw new Response(JSON.stringify({ error: 'Authorization header required' }), {
37
+ const authHeader = ctx.request.headers.get(header.toLowerCase());
38
+
39
+ if (!authHeader) {
40
+ throw new Response(JSON.stringify({ error: 'Authorization header required' }), {
41
+ status: 401,
42
+ headers: { 'Content-Type': 'application/json' }
43
+ });
44
+ }
45
+
46
+ let token: string;
47
+
48
+ if (type === 'jwt' || type === 'bearer') {
49
+ if (!authHeader.startsWith('Bearer ')) {
50
+ throw new Response(JSON.stringify({ error: 'Invalid authorization format. Use Bearer <token>' }), {
39
51
  status: 401,
40
52
  headers: { 'Content-Type': 'application/json' }
41
53
  });
42
54
  }
55
+ token = authHeader.slice(7);
56
+ } else if (type === 'apikey') {
57
+ token = authHeader;
58
+ } else {
59
+ token = authHeader;
60
+ }
43
61
 
44
- let token: string;
62
+ try {
63
+ let user: any;
45
64
 
46
- if (type === 'jwt' || type === 'bearer') {
47
- if (!authHeader.startsWith('Bearer ')) {
48
- throw new Response(JSON.stringify({ error: 'Invalid authorization format. Use Bearer <token>' }), {
49
- status: 401,
50
- headers: { 'Content-Type': 'application/json' }
51
- });
65
+ if (verify) {
66
+ user = await verify(token, ctx);
67
+ } else if (type === 'jwt' && secret) {
68
+ // Simple JWT verification (in production, use a proper JWT library)
69
+ const [headerB64, payloadB64, signature] = token.split('.');
70
+ if (!headerB64 || !payloadB64 || !signature) {
71
+ throw new Error('Invalid JWT format');
52
72
  }
53
- token = authHeader.slice(7);
54
- } else if (type === 'apikey') {
55
- token = authHeader;
56
- } else {
57
- token = authHeader;
58
- }
59
-
60
- try {
61
- let user: any;
62
73
 
63
- if (verify) {
64
- user = await verify(token, ctx);
65
- } else if (type === 'jwt' && secret) {
66
- // Simple JWT verification (in production, use a proper JWT library)
67
- const [headerB64, payloadB64, signature] = token.split('.');
68
- if (!headerB64 || !payloadB64 || !signature) {
69
- throw new Error('Invalid JWT format');
70
- }
71
-
72
- const payload = JSON.parse(atob(payloadB64));
73
-
74
- // Check expiration
75
- if (payload.exp && Date.now() >= payload.exp * 1000) {
76
- throw new Error('Token expired');
77
- }
78
-
79
- user = payload;
80
- } else {
81
- user = { token };
74
+ const payload = JSON.parse(atob(payloadB64));
75
+
76
+ // Check expiration
77
+ if (payload.exp && Date.now() >= payload.exp * 1000) {
78
+ throw new Error('Token expired');
82
79
  }
83
-
84
- // Attach user to context
85
- ctx.user = user;
86
80
 
87
- } catch (error) {
88
- const message = error instanceof Error ? error.message : 'Invalid token';
89
- throw new Response(JSON.stringify({ error: message }), {
90
- status: 401,
91
- headers: { 'Content-Type': 'application/json' }
92
- });
81
+ user = payload;
82
+ } else {
83
+ user = { token };
93
84
  }
85
+
86
+ // Attach user to context
87
+ ctx.user = user;
88
+
89
+ } catch (error) {
90
+ const message = error instanceof Error ? error.message : 'Invalid token';
91
+ throw new Response(JSON.stringify({ error: message }), {
92
+ status: 401,
93
+ headers: { 'Content-Type': 'application/json' }
94
+ });
94
95
  }
95
- };
96
+ });
97
+
98
+ return authInstance;
96
99
  }
97
100
 
98
101
  // Helper function for creating JWT tokens (simple implementation)
package/plugins/cors.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import BXO from '../index';
2
+
1
3
  interface CORSOptions {
2
4
  origin?: string | string[] | boolean;
3
5
  methods?: string[];
@@ -6,7 +8,7 @@ interface CORSOptions {
6
8
  maxAge?: number;
7
9
  }
8
10
 
9
- export function cors(options: CORSOptions = {}) {
11
+ export function cors(options: CORSOptions = {}): BXO {
10
12
  const {
11
13
  origin = '*',
12
14
  methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
@@ -15,46 +17,14 @@ export function cors(options: CORSOptions = {}) {
15
17
  maxAge = 86400
16
18
  } = options;
17
19
 
18
- return {
19
- name: 'cors',
20
- onRequest: async (ctx: any) => {
21
- // Handle preflight OPTIONS request
22
- if (ctx.request.method === 'OPTIONS') {
23
- const headers: Record<string, string> = {};
24
-
25
- // Handle origin
26
- if (typeof origin === 'boolean') {
27
- if (origin) {
28
- headers['Access-Control-Allow-Origin'] = ctx.request.headers.get('origin') || '*';
29
- }
30
- } else if (typeof origin === 'string') {
31
- headers['Access-Control-Allow-Origin'] = origin;
32
- } else if (Array.isArray(origin)) {
33
- const requestOrigin = ctx.request.headers.get('origin');
34
- if (requestOrigin && origin.includes(requestOrigin)) {
35
- headers['Access-Control-Allow-Origin'] = requestOrigin;
36
- }
37
- }
38
-
39
- headers['Access-Control-Allow-Methods'] = methods.join(', ');
40
- headers['Access-Control-Allow-Headers'] = allowedHeaders.join(', ');
41
-
42
- if (credentials) {
43
- headers['Access-Control-Allow-Credentials'] = 'true';
44
- }
45
-
46
- headers['Access-Control-Max-Age'] = maxAge.toString();
20
+ const corsInstance = new BXO();
47
21
 
48
- ctx.set.status = 204;
49
- ctx.set.headers = { ...ctx.set.headers, ...headers };
50
-
51
- throw new Response(null, { status: 204, headers });
52
- }
53
- },
54
- onResponse: async (ctx: any, response: any) => {
22
+ corsInstance.onRequest(async (ctx: any) => {
23
+ // Handle preflight OPTIONS request
24
+ if (ctx.request.method === 'OPTIONS') {
55
25
  const headers: Record<string, string> = {};
56
26
 
57
- // Handle origin for actual requests
27
+ // Handle origin
58
28
  if (typeof origin === 'boolean') {
59
29
  if (origin) {
60
30
  headers['Access-Control-Allow-Origin'] = ctx.request.headers.get('origin') || '*';
@@ -68,12 +38,46 @@ export function cors(options: CORSOptions = {}) {
68
38
  }
69
39
  }
70
40
 
41
+ headers['Access-Control-Allow-Methods'] = methods.join(', ');
42
+ headers['Access-Control-Allow-Headers'] = allowedHeaders.join(', ');
43
+
71
44
  if (credentials) {
72
45
  headers['Access-Control-Allow-Credentials'] = 'true';
73
46
  }
47
+
48
+ headers['Access-Control-Max-Age'] = maxAge.toString();
74
49
 
50
+ ctx.set.status = 204;
75
51
  ctx.set.headers = { ...ctx.set.headers, ...headers };
76
- return response;
52
+
53
+ throw new Response(null, { status: 204, headers });
77
54
  }
78
- };
55
+ });
56
+
57
+ corsInstance.onResponse(async (ctx: any, response: any) => {
58
+ const headers: Record<string, string> = {};
59
+
60
+ // Handle origin for actual requests
61
+ if (typeof origin === 'boolean') {
62
+ if (origin) {
63
+ headers['Access-Control-Allow-Origin'] = ctx.request.headers.get('origin') || '*';
64
+ }
65
+ } else if (typeof origin === 'string') {
66
+ headers['Access-Control-Allow-Origin'] = origin;
67
+ } else if (Array.isArray(origin)) {
68
+ const requestOrigin = ctx.request.headers.get('origin');
69
+ if (requestOrigin && origin.includes(requestOrigin)) {
70
+ headers['Access-Control-Allow-Origin'] = requestOrigin;
71
+ }
72
+ }
73
+
74
+ if (credentials) {
75
+ headers['Access-Control-Allow-Credentials'] = 'true';
76
+ }
77
+
78
+ ctx.set.headers = { ...ctx.set.headers, ...headers };
79
+ return response;
80
+ });
81
+
82
+ return corsInstance;
79
83
  }
package/plugins/index.ts CHANGED
@@ -4,10 +4,8 @@ export { logger } from './logger';
4
4
  export { auth, createJWT } from './auth';
5
5
  export { rateLimit } from './ratelimit';
6
6
 
7
- // Plugin types for convenience
8
- export interface Plugin {
9
- name?: string;
10
- onRequest?: (ctx: any) => Promise<void> | void;
11
- onResponse?: (ctx: any, response: any) => Promise<any> | any;
12
- onError?: (ctx: any, error: Error) => Promise<any> | any;
13
- }
7
+ // Import BXO for plugin typing
8
+ import BXO from '../index';
9
+
10
+ // Plugin functions now return BXO instances
11
+ export type PluginFactory<T = any> = (options?: T) => BXO;
package/plugins/logger.ts CHANGED
@@ -1,104 +1,109 @@
1
+ import BXO from '../index';
2
+
1
3
  interface LoggerOptions {
2
4
  format?: 'simple' | 'detailed' | 'json';
3
5
  includeBody?: boolean;
4
6
  includeHeaders?: boolean;
5
7
  }
6
8
 
7
- export function logger(options: LoggerOptions = {}) {
9
+ export function logger(options: LoggerOptions = {}): BXO {
8
10
  const {
9
11
  format = 'simple',
10
12
  includeBody = false,
11
13
  includeHeaders = false
12
14
  } = options;
13
15
 
14
- return {
15
- name: 'logger',
16
- onRequest: async (ctx: any) => {
17
- ctx._startTime = Date.now();
16
+ const loggerInstance = new BXO();
17
+
18
+ loggerInstance.onRequest(async (ctx: any) => {
19
+ ctx._startTime = Date.now();
20
+
21
+ if (format === 'json') {
22
+ const logData: any = {
23
+ timestamp: new Date().toISOString(),
24
+ method: ctx.request.method,
25
+ url: ctx.request.url,
26
+ type: 'request'
27
+ };
28
+
29
+ if (includeHeaders) {
30
+ logData.headers = Object.fromEntries(ctx.request.headers.entries());
31
+ }
18
32
 
19
- if (format === 'json') {
20
- const logData: any = {
21
- timestamp: new Date().toISOString(),
22
- method: ctx.request.method,
23
- url: ctx.request.url,
24
- type: 'request'
25
- };
26
-
27
- if (includeHeaders) {
28
- logData.headers = Object.fromEntries(ctx.request.headers.entries());
29
- }
30
-
31
- if (includeBody && ctx.body) {
32
- logData.body = ctx.body;
33
- }
34
-
35
- console.log(JSON.stringify(logData));
36
- } else if (format === 'detailed') {
37
- console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
38
- if (includeHeaders) {
39
- console.log(' Headers:', Object.fromEntries(ctx.request.headers.entries()));
40
- }
41
- if (includeBody && ctx.body) {
42
- console.log(' Body:', ctx.body);
43
- }
44
- } else {
45
- console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
33
+ if (includeBody && ctx.body) {
34
+ logData.body = ctx.body;
35
+ }
36
+
37
+ console.log(JSON.stringify(logData));
38
+ } else if (format === 'detailed') {
39
+ console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
40
+ if (includeHeaders) {
41
+ console.log(' Headers:', Object.fromEntries(ctx.request.headers.entries()));
42
+ }
43
+ if (includeBody && ctx.body) {
44
+ console.log(' Body:', ctx.body);
46
45
  }
47
- },
48
- onResponse: async (ctx: any, response: any) => {
49
- const duration = Date.now() - (ctx._startTime || 0);
50
- const status = ctx.set.status || 200;
46
+ } else {
47
+ console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
48
+ }
49
+ });
50
+
51
+ loggerInstance.onResponse(async (ctx: any, response: any) => {
52
+ const duration = Date.now() - (ctx._startTime || 0);
53
+ const status = ctx.set.status || 200;
54
+
55
+ if (format === 'json') {
56
+ const logData: any = {
57
+ timestamp: new Date().toISOString(),
58
+ method: ctx.request.method,
59
+ url: ctx.request.url,
60
+ status,
61
+ duration: `${duration}ms`,
62
+ type: 'response'
63
+ };
51
64
 
52
- if (format === 'json') {
53
- const logData: any = {
54
- timestamp: new Date().toISOString(),
55
- method: ctx.request.method,
56
- url: ctx.request.url,
57
- status,
58
- duration: `${duration}ms`,
59
- type: 'response'
60
- };
61
-
62
- if (includeHeaders && ctx.set.headers) {
63
- logData.responseHeaders = ctx.set.headers;
64
- }
65
-
66
- if (includeBody && response) {
67
- logData.response = response;
68
- }
69
-
70
- console.log(JSON.stringify(logData));
71
- } else if (format === 'detailed') {
72
- console.log(`← ${ctx.request.method} ${ctx.request.url} ${status} ${duration}ms`);
73
- if (includeHeaders && ctx.set.headers) {
74
- console.log(' Response Headers:', ctx.set.headers);
75
- }
76
- if (includeBody && response) {
77
- console.log(' Response:', response);
78
- }
79
- } else {
80
- const statusColor = status >= 400 ? '\x1b[31m' : status >= 300 ? '\x1b[33m' : '\x1b[32m';
81
- const resetColor = '\x1b[0m';
82
- console.log(`← ${ctx.request.method} ${ctx.request.url} ${statusColor}${status}${resetColor} ${duration}ms`);
65
+ if (includeHeaders && ctx.set.headers) {
66
+ logData.responseHeaders = ctx.set.headers;
83
67
  }
84
68
 
85
- return response;
86
- },
87
- onError: async (ctx: any, error: Error) => {
88
- const duration = Date.now() - (ctx._startTime || 0);
69
+ if (includeBody && response) {
70
+ logData.response = response;
71
+ }
89
72
 
90
- if (format === 'json') {
91
- console.log(JSON.stringify({
92
- timestamp: new Date().toISOString(),
93
- method: ctx.request.method,
94
- url: ctx.request.url,
95
- error: error.message,
96
- duration: `${duration}ms`,
97
- type: 'error'
98
- }));
99
- } else {
100
- console.log(`✗ ${ctx.request.method} ${ctx.request.url} \x1b[31mERROR\x1b[0m ${duration}ms: ${error.message}`);
73
+ console.log(JSON.stringify(logData));
74
+ } else if (format === 'detailed') {
75
+ console.log(`← ${ctx.request.method} ${ctx.request.url} ${status} ${duration}ms`);
76
+ if (includeHeaders && ctx.set.headers) {
77
+ console.log(' Response Headers:', ctx.set.headers);
78
+ }
79
+ if (includeBody && response) {
80
+ console.log(' Response:', response);
101
81
  }
82
+ } else {
83
+ const statusColor = status >= 400 ? '\x1b[31m' : status >= 300 ? '\x1b[33m' : '\x1b[32m';
84
+ const resetColor = '\x1b[0m';
85
+ console.log(`← ${ctx.request.method} ${ctx.request.url} ${statusColor}${status}${resetColor} ${duration}ms`);
102
86
  }
103
- };
87
+
88
+ return response;
89
+ });
90
+
91
+ loggerInstance.onError(async (ctx: any, error: Error) => {
92
+ const duration = Date.now() - (ctx._startTime || 0);
93
+
94
+ if (format === 'json') {
95
+ console.log(JSON.stringify({
96
+ timestamp: new Date().toISOString(),
97
+ method: ctx.request.method,
98
+ url: ctx.request.url,
99
+ error: error.message,
100
+ duration: `${duration}ms`,
101
+ type: 'error'
102
+ }));
103
+ } else {
104
+ console.log(`✗ ${ctx.request.method} ${ctx.request.url} \x1b[31mERROR\x1b[0m ${duration}ms: ${error.message}`);
105
+ }
106
+ });
107
+
108
+ return loggerInstance;
104
109
  }
@@ -1,3 +1,5 @@
1
+ import BXO from '../index';
2
+
1
3
  interface RateLimitOptions {
2
4
  max: number;
3
5
  window: number; // in seconds
@@ -50,7 +52,7 @@ class RateLimitStore {
50
52
  }
51
53
  }
52
54
 
53
- export function rateLimit(options: RateLimitOptions) {
55
+ export function rateLimit(options: RateLimitOptions): BXO {
54
56
  const {
55
57
  max,
56
58
  window,
@@ -72,65 +74,67 @@ export function rateLimit(options: RateLimitOptions) {
72
74
  // Cleanup expired entries every 5 minutes
73
75
  setInterval(() => store.cleanup(), 5 * 60 * 1000);
74
76
 
75
- return {
76
- name: 'rateLimit',
77
- onRequest: async (ctx: any) => {
78
- const url = new URL(ctx.request.url);
79
- const pathname = url.pathname;
80
-
81
- // Skip rate limiting for excluded paths
82
- if (exclude.some(path => {
83
- if (path.includes('*')) {
84
- const regex = new RegExp(path.replace(/\*/g, '.*'));
85
- return regex.test(pathname);
86
- }
87
- return pathname === path || pathname.startsWith(path);
88
- })) {
89
- return;
90
- }
77
+ const rateLimitInstance = new BXO();
91
78
 
92
- const key = keyGenerator(ctx);
93
- const entry = store.increment(key, window);
94
-
95
- if (entry.count > max) {
96
- const resetTime = Math.ceil(entry.resetTime / 1000);
97
- throw new Response(JSON.stringify({
98
- error: message,
99
- retryAfter: resetTime - Math.floor(Date.now() / 1000)
100
- }), {
101
- status: statusCode,
102
- headers: {
103
- 'Content-Type': 'application/json',
104
- 'X-RateLimit-Limit': max.toString(),
105
- 'X-RateLimit-Remaining': '0',
106
- 'X-RateLimit-Reset': resetTime.toString(),
107
- 'Retry-After': (resetTime - Math.floor(Date.now() / 1000)).toString()
108
- }
109
- });
79
+ rateLimitInstance.onRequest(async (ctx: any) => {
80
+ const url = new URL(ctx.request.url);
81
+ const pathname = url.pathname;
82
+
83
+ // Skip rate limiting for excluded paths
84
+ if (exclude.some(path => {
85
+ if (path.includes('*')) {
86
+ const regex = new RegExp(path.replace(/\*/g, '.*'));
87
+ return regex.test(pathname);
110
88
  }
111
-
112
- // Add rate limit headers
113
- ctx.set.headers = {
114
- ...ctx.set.headers,
115
- 'X-RateLimit-Limit': max.toString(),
116
- 'X-RateLimit-Remaining': Math.max(0, max - entry.count).toString(),
117
- 'X-RateLimit-Reset': Math.ceil(entry.resetTime / 1000).toString()
118
- };
119
- },
120
- onResponse: async (ctx: any, response: any) => {
121
- const status = ctx.set.status || 200;
122
- const key = keyGenerator(ctx);
123
-
124
- // Optionally skip counting successful or failed requests
125
- if ((skipSuccessful && status < 400) || (skipFailed && status >= 400)) {
126
- // Decrement the counter since we don't want to count this request
127
- const entry = store.get(key);
128
- if (entry && entry.count > 0) {
129
- store.set(key, entry.count - 1, entry.resetTime);
89
+ return pathname === path || pathname.startsWith(path);
90
+ })) {
91
+ return;
92
+ }
93
+
94
+ const key = keyGenerator(ctx);
95
+ const entry = store.increment(key, window);
96
+
97
+ if (entry.count > max) {
98
+ const resetTime = Math.ceil(entry.resetTime / 1000);
99
+ throw new Response(JSON.stringify({
100
+ error: message,
101
+ retryAfter: resetTime - Math.floor(Date.now() / 1000)
102
+ }), {
103
+ status: statusCode,
104
+ headers: {
105
+ 'Content-Type': 'application/json',
106
+ 'X-RateLimit-Limit': max.toString(),
107
+ 'X-RateLimit-Remaining': '0',
108
+ 'X-RateLimit-Reset': resetTime.toString(),
109
+ 'Retry-After': (resetTime - Math.floor(Date.now() / 1000)).toString()
130
110
  }
111
+ });
112
+ }
113
+
114
+ // Add rate limit headers
115
+ ctx.set.headers = {
116
+ ...ctx.set.headers,
117
+ 'X-RateLimit-Limit': max.toString(),
118
+ 'X-RateLimit-Remaining': Math.max(0, max - entry.count).toString(),
119
+ 'X-RateLimit-Reset': Math.ceil(entry.resetTime / 1000).toString()
120
+ };
121
+ });
122
+
123
+ rateLimitInstance.onResponse(async (ctx: any, response: any) => {
124
+ const status = ctx.set.status || 200;
125
+ const key = keyGenerator(ctx);
126
+
127
+ // Optionally skip counting successful or failed requests
128
+ if ((skipSuccessful && status < 400) || (skipFailed && status >= 400)) {
129
+ // Decrement the counter since we don't want to count this request
130
+ const entry = store.get(key);
131
+ if (entry && entry.count > 0) {
132
+ store.set(key, entry.count - 1, entry.resetTime);
131
133
  }
132
-
133
- return response;
134
134
  }
135
- };
135
+
136
+ return response;
137
+ });
138
+
139
+ return rateLimitInstance;
136
140
  }