bxo 0.0.2 → 0.0.4

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,16 +30,6 @@ 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
- }
40
-
41
-
42
-
43
33
  // Route definition
44
34
  interface Route {
45
35
  method: string;
@@ -63,14 +53,14 @@ interface LifecycleHooks {
63
53
 
64
54
  export default class BXO {
65
55
  private routes: Route[] = [];
66
- private plugins: Plugin[] = [];
56
+ private plugins: BXO[] = [];
67
57
  private hooks: LifecycleHooks = {};
68
58
  private server?: any;
69
59
  private isRunning: boolean = false;
70
60
  private hotReloadEnabled: boolean = false;
71
61
  private watchedFiles: Set<string> = new Set();
72
62
 
73
- constructor() {}
63
+ constructor() { }
74
64
 
75
65
  // Lifecycle hook methods
76
66
  onBeforeStart(handler: () => Promise<void> | void): this {
@@ -118,9 +108,9 @@ export default class BXO {
118
108
  return this;
119
109
  }
120
110
 
121
- // Plugin system
122
- use(plugin: Plugin): this {
123
- this.plugins.push(plugin);
111
+ // Plugin system - now accepts other BXO instances
112
+ use(bxoInstance: BXO): this {
113
+ this.plugins.push(bxoInstance);
124
114
  return this;
125
115
  }
126
116
 
@@ -326,10 +316,10 @@ export default class BXO {
326
316
  await this.hooks.onRequest(ctx);
327
317
  }
328
318
 
329
- // Run plugin onRequest hooks
330
- for (const plugin of this.plugins) {
331
- if (plugin.onRequest) {
332
- await plugin.onRequest(ctx);
319
+ // Run BXO instance onRequest hooks
320
+ for (const bxoInstance of this.plugins) {
321
+ if (bxoInstance.hooks.onRequest) {
322
+ await bxoInstance.hooks.onRequest(ctx);
333
323
  }
334
324
  }
335
325
 
@@ -341,10 +331,10 @@ export default class BXO {
341
331
  response = await this.hooks.onResponse(ctx, response) || response;
342
332
  }
343
333
 
344
- // Run plugin onResponse hooks
345
- for (const plugin of this.plugins) {
346
- if (plugin.onResponse) {
347
- response = await plugin.onResponse(ctx, response) || response;
334
+ // Run BXO instance onResponse hooks
335
+ for (const bxoInstance of this.plugins) {
336
+ if (bxoInstance.hooks.onResponse) {
337
+ response = await bxoInstance.hooks.onResponse(ctx, response) || response;
348
338
  }
349
339
  }
350
340
 
@@ -378,9 +368,9 @@ export default class BXO {
378
368
  errorResponse = await this.hooks.onError(ctx, error as Error);
379
369
  }
380
370
 
381
- for (const plugin of this.plugins) {
382
- if (plugin.onError) {
383
- errorResponse = await plugin.onError(ctx, error as Error) || errorResponse;
371
+ for (const bxoInstance of this.plugins) {
372
+ if (bxoInstance.hooks.onError) {
373
+ errorResponse = await bxoInstance.hooks.onError(ctx, error as Error) || errorResponse;
384
374
  }
385
375
  }
386
376
 
@@ -414,7 +404,7 @@ export default class BXO {
414
404
  if (!this.hotReloadEnabled) return;
415
405
 
416
406
  const fs = require('fs');
417
-
407
+
418
408
  for (const watchPath of this.watchedFiles) {
419
409
  try {
420
410
  fs.watch(watchPath, { recursive: true }, async (eventType: string, filename: string) => {
@@ -516,19 +506,19 @@ export default class BXO {
516
506
  }
517
507
 
518
508
  console.log('🔄 Restarting BXO server...');
519
-
509
+
520
510
  await this.stop();
521
-
511
+
522
512
  // Small delay to ensure cleanup
523
513
  await new Promise(resolve => setTimeout(resolve, 100));
524
-
514
+
525
515
  await this.start(port, hostname);
526
516
 
527
517
  // After restart hook
528
518
  if (this.hooks.onAfterRestart) {
529
519
  await this.hooks.onAfterRestart();
530
520
  }
531
-
521
+
532
522
  } catch (error) {
533
523
  console.error('❌ Error restarting server:', error);
534
524
  throw error;
@@ -554,10 +544,12 @@ export default class BXO {
554
544
  }
555
545
  }
556
546
 
557
- // Export Zod for convenience
558
- export { z };
547
+ const error = (error: Error, status: number = 500) => {
548
+ return new Response(JSON.stringify({ error: error.message }), { status });
549
+ }
559
550
 
560
- export type { Plugin } from './plugins';
551
+ // Export Zod for convenience
552
+ export { z, error };
561
553
 
562
554
  // Export types for external use
563
555
  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.4",
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
  }