bxo 0.0.1 → 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/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
  }