barejs 0.1.6 → 0.1.7
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/README.md +2 -2
- package/package.json +2 -2
- package/src/bare.ts +113 -85
- package/src/context.ts +54 -0
- package/src/index.ts +15 -2
- package/src/{middleware/validators.ts → validators.ts} +7 -17
- package/src/types.ts +0 -24
package/README.md
CHANGED
|
@@ -158,8 +158,8 @@ app.post('/native', native({ properties: { id: { type: 'number' } } }), (ctx) =>
|
|
|
158
158
|
* [x] **JIT Static Routing**: lookup via compiled static maps.
|
|
159
159
|
* [x] **Validation Integration**: Support for TypeBox, Zod, and Native JSON.
|
|
160
160
|
* [x] **Full Plugin System**: Modular extensibility with zero overhead.
|
|
161
|
-
* [
|
|
162
|
-
* [
|
|
161
|
+
* [x] **Dynamic Path JIT**: Compiled Regex for parameterized routes (e.g., `/user/:id`).
|
|
162
|
+
* [x] **Native WebSocket Support**: High-speed binary streaming.
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
package/package.json
CHANGED
package/src/bare.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
params: Record<string, string>;
|
|
4
|
-
json: (data: any) => Response;
|
|
5
|
-
body?: any;
|
|
6
|
-
[key: string]: any;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type Next = () => Promise<any> | any;
|
|
10
|
-
export type Middleware = (ctx: Context, next: Next) => any;
|
|
11
|
-
export type Handler = (ctx: Context) => any;
|
|
1
|
+
import { BareContext } from './context';
|
|
2
|
+
import type { Context, Middleware, Handler, WSHandlers } from './context';
|
|
12
3
|
|
|
13
4
|
export interface BarePlugin {
|
|
14
5
|
name: string;
|
|
@@ -19,17 +10,35 @@ export interface BarePlugin {
|
|
|
19
10
|
export class BareJS {
|
|
20
11
|
private routes: { method: string; path: string; handlers: (Middleware | Handler)[] }[] = [];
|
|
21
12
|
private globalMiddlewares: Middleware[] = [];
|
|
22
|
-
private compiledFetch?: (req: Request) => Promise<Response> | Response;
|
|
23
|
-
|
|
13
|
+
private compiledFetch?: (req: Request, server: any) => Promise<Response> | Response;
|
|
14
|
+
|
|
15
|
+
private staticMap: Record<string, (ctx: BareContext) => Promise<Response>> = {};
|
|
16
|
+
|
|
17
|
+
private dynamicRoutes: {
|
|
18
|
+
method: string;
|
|
19
|
+
regex: RegExp;
|
|
20
|
+
paramNames: string[];
|
|
21
|
+
chain: (ctx: BareContext) => Promise<Response>
|
|
22
|
+
}[] = [];
|
|
23
|
+
|
|
24
|
+
private wsHandler: { path: string; handlers: WSHandlers } | null = null;
|
|
25
|
+
private ContextClass = BareContext;
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
private errorHandler: (err: any, ctx: Context) => Response = (err) =>
|
|
28
|
+
new Response(JSON.stringify({ error: err.message }), { status: 500 });
|
|
29
|
+
|
|
30
|
+
// --- HTTP Methods ---
|
|
26
31
|
public get = (path: string, ...h: (Middleware | Handler)[]) => { this.routes.push({ method: "GET", path, handlers: h }); return this; };
|
|
27
32
|
public post = (path: string, ...h: (Middleware | Handler)[]) => { this.routes.push({ method: "POST", path, handlers: h }); return this; };
|
|
28
33
|
public put = (path: string, ...h: (Middleware | Handler)[]) => { this.routes.push({ method: "PUT", path, handlers: h }); return this; };
|
|
29
34
|
public patch = (path: string, ...h: (Middleware | Handler)[]) => { this.routes.push({ method: "PATCH", path, handlers: h }); return this; };
|
|
30
35
|
public delete = (path: string, ...h: (Middleware | Handler)[]) => { this.routes.push({ method: "DELETE", path, handlers: h }); return this; };
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
public ws = (path: string, handlers: WSHandlers) => {
|
|
38
|
+
this.wsHandler = { path, handlers };
|
|
39
|
+
return this;
|
|
40
|
+
};
|
|
41
|
+
|
|
33
42
|
public use = (arg: Middleware | BarePlugin) => {
|
|
34
43
|
if (typeof arg === 'object' && 'install' in arg) {
|
|
35
44
|
arg.install(this);
|
|
@@ -39,90 +48,109 @@ export class BareJS {
|
|
|
39
48
|
return this;
|
|
40
49
|
};
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
|
|
52
|
+
private async handleDynamic(req: Request, method: string, path: string): Promise<Response> {
|
|
53
|
+
const dr = this.dynamicRoutes;
|
|
54
|
+
const len = dr.length;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < len; i++) {
|
|
57
|
+
const route = dr[i];
|
|
58
|
+
if (route && route.method === method) {
|
|
59
|
+
const match = path.match(route.regex);
|
|
60
|
+
if (match) {
|
|
61
|
+
const ctx = new this.ContextClass(req);
|
|
62
|
+
const pNames = route.paramNames;
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
for (let j = 0; j < pNames.length; j++) {
|
|
66
|
+
const name = pNames[j];
|
|
67
|
+
const value = match[j + 1];
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if (name && value) {
|
|
71
|
+
ctx.params[name] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return await route.chain(ctx);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return new Response('404 Not Found', { status: 404 });
|
|
79
|
+
}
|
|
47
80
|
|
|
48
81
|
private compile() {
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
const chain = async (ctx: Context) => {
|
|
52
|
-
let idx = 0;
|
|
53
|
-
const middlewares = [...this.globalMiddlewares, ...route.handlers];
|
|
54
|
-
const next = async (): Promise<any> => {
|
|
55
|
-
const handler = middlewares[idx++];
|
|
56
|
-
if (!handler) return;
|
|
57
|
-
// Support both async and sync handlers
|
|
58
|
-
return await (handler.length > 1
|
|
59
|
-
? (handler as Middleware)(ctx, next)
|
|
60
|
-
: (handler as Handler)(ctx));
|
|
61
|
-
};
|
|
62
|
-
return await next();
|
|
63
|
-
};
|
|
64
|
-
this.staticMap[`${route.method}:${route.path}`] = chain;
|
|
65
|
-
});
|
|
82
|
+
this.staticMap = {};
|
|
83
|
+
this.dynamicRoutes = [];
|
|
66
84
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const staticMap = this.staticMap;
|
|
70
|
-
const EMPTY_PARAMS = Object.freeze({});
|
|
71
|
-
const jsonHeader = { "content-type": "application/json" };
|
|
85
|
+
for (const route of this.routes) {
|
|
86
|
+
const pipeline = [...this.globalMiddlewares, ...route.handlers];
|
|
72
87
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const path = pathStart === -1 ? '/' : url.substring(pathStart);
|
|
77
|
-
const key = req.method + ":" + path;
|
|
78
|
-
|
|
79
|
-
const runner = staticMap[key];
|
|
80
|
-
if (runner) {
|
|
81
|
-
const ctx = {
|
|
82
|
-
req,
|
|
83
|
-
params: EMPTY_PARAMS,
|
|
84
|
-
json: (d) => new Response(JSON.stringify(d), { headers: jsonHeader })
|
|
85
|
-
};
|
|
86
|
-
return await runner(ctx);
|
|
87
|
-
}
|
|
88
|
-
return new Response('404 Not Found', { status: 404 });
|
|
89
|
-
};`;
|
|
88
|
+
// เทคนิค Compose: มัด Middleware จากหลังมาหน้า
|
|
89
|
+
// เพื่อให้ตัวแรกสุดกลายเป็นฟังก์ชันเดียวที่เรียกตัวถัดไปได้ทันที
|
|
90
|
+
let composed = async (ctx: BareContext): Promise<Response> => ctx._finalize();
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
for (let i = pipeline.length - 1; i >= 0; i--) {
|
|
93
|
+
const current = pipeline[i];
|
|
94
|
+
const nextFn = composed;
|
|
95
|
+
composed = async (ctx: BareContext) => {
|
|
96
|
+
const res = await (current as any)(ctx, () => nextFn(ctx));
|
|
97
|
+
if (res instanceof Response) ctx.res = res;
|
|
98
|
+
return ctx.res || ctx._finalize();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
if (route.path.includes(':')) {
|
|
103
|
+
const paramNames: string[] = [];
|
|
104
|
+
const regexPath = route.path.replace(/:([^\/]+)/g, (_, name) => {
|
|
105
|
+
paramNames.push(name);
|
|
106
|
+
return "([^/]+)";
|
|
107
|
+
});
|
|
108
|
+
this.dynamicRoutes.push({
|
|
109
|
+
method: route.method,
|
|
110
|
+
regex: new RegExp(`^${regexPath}$`),
|
|
111
|
+
paramNames,
|
|
112
|
+
chain: composed
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
this.staticMap[`${route.method}:${route.path}`] = composed;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
96
118
|
|
|
119
|
+
// JIT: ใช้พารามิเตอร์แบบกระจายเพื่อลด Overhead ของ Scope
|
|
120
|
+
this.compiledFetch = new Function(
|
|
121
|
+
'staticMap', 'hd', 'Ctx',
|
|
122
|
+
`return async (req, server) => {
|
|
123
|
+
const url = req.url;
|
|
124
|
+
const start = url.indexOf('/', 8);
|
|
125
|
+
const path = start === -1 ? '/' : url.substring(start);
|
|
126
|
+
const method = req.method;
|
|
97
127
|
|
|
128
|
+
const runner = staticMap[method + ":" + path];
|
|
129
|
+
if (runner) return await runner(new Ctx(req));
|
|
98
130
|
|
|
131
|
+
return await hd(req, method, path);
|
|
132
|
+
}`
|
|
133
|
+
)(this.staticMap, this.handleDynamic.bind(this), this.ContextClass);
|
|
134
|
+
}
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const bold = "\x1b[1m";
|
|
105
|
-
if (process.env.NODE_ENV !== 'production' && process.env.BARE_SILENT !== 'true') {
|
|
106
|
-
console.log(`
|
|
107
|
-
${cyan}${bold} ____ _ ____
|
|
108
|
-
| __ ) __ _ _ __ ___ | / ___|
|
|
109
|
-
| _ \\ / _\` | '__/ _ \\ _ | \\___ \\
|
|
110
|
-
| |_) | (_| | | | __/| |_| |___) |
|
|
111
|
-
|____/ \\__,_|_| \\___| \\___/|____/
|
|
112
|
-
${reset}
|
|
113
|
-
${yellow}BareJS${reset} ${gray}${reset}
|
|
114
|
-
${gray}-----------------------------------${reset}
|
|
115
|
-
🚀 Running at: ${cyan}http://${ip}:${port}${reset}
|
|
116
|
-
${gray}Ready to build everything awesome!${reset}
|
|
117
|
-
`);
|
|
118
|
-
} else {
|
|
136
|
+
public fetch = (req: Request, server: any = {} as any): Promise<Response> | Response => {
|
|
137
|
+
if (!this.compiledFetch) this.compile();
|
|
138
|
+
return this.compiledFetch!(req, server);
|
|
139
|
+
};
|
|
119
140
|
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
public listen(ip: string = '0.0.0.0', port: number = 3000) {
|
|
142
|
+
this.compile();
|
|
143
|
+
console.log(`\x1b[32m⚡ BareJS Extreme (2x Optimized) running at http://${ip}:${port}\x1b[0m`);
|
|
122
144
|
return Bun.serve({
|
|
123
145
|
hostname: ip,
|
|
124
146
|
port,
|
|
125
|
-
|
|
147
|
+
reusePort: true,
|
|
148
|
+
fetch: (req, server) => this.fetch(req, server),
|
|
149
|
+
websocket: {
|
|
150
|
+
open: (ws) => this.wsHandler?.handlers.open?.(ws),
|
|
151
|
+
message: (ws, msg) => this.wsHandler?.handlers.message?.(ws, msg),
|
|
152
|
+
close: (ws, code, res) => this.wsHandler?.handlers.close?.(ws, code, res),
|
|
153
|
+
}
|
|
126
154
|
});
|
|
127
155
|
}
|
|
128
156
|
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface WSHandlers {
|
|
2
|
+
open?: (ws: any) => void;
|
|
3
|
+
message?: (ws: any, message: string | Buffer) => void;
|
|
4
|
+
close?: (ws: any, code: number, reason: string) => void;
|
|
5
|
+
drain?: (ws: any) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Next = () => Promise<any> | any;
|
|
9
|
+
export type Middleware = (ctx: Context, next: Next) => any;
|
|
10
|
+
export type Handler = (ctx: Context) => any;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export interface Context {
|
|
14
|
+
req: Request;
|
|
15
|
+
res: Response;
|
|
16
|
+
params: Record<string, string>;
|
|
17
|
+
body?: any;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
json: (data: any) => void;
|
|
20
|
+
status: (code: number) => this;
|
|
21
|
+
setResHeader: (key: string, value: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class BareContext {
|
|
25
|
+
public res: Response | undefined;
|
|
26
|
+
public params: Record<string, string> = {};
|
|
27
|
+
public body: any = undefined;
|
|
28
|
+
public _status = 200;
|
|
29
|
+
public _headers: Record<string, string> = {};
|
|
30
|
+
|
|
31
|
+
constructor(public req: Request) {}
|
|
32
|
+
|
|
33
|
+
json(data: any) {
|
|
34
|
+
this._headers["Content-Type"] = "application/json";
|
|
35
|
+
this.res = new Response(JSON.stringify(data), {
|
|
36
|
+
status: this._status,
|
|
37
|
+
headers: this._headers,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
status(code: number): this {
|
|
42
|
+
this._status = code;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setResHeader(k: string, v: string) {
|
|
47
|
+
this._headers[k] = v;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public _finalize(): Response {
|
|
51
|
+
if (this.res) return this.res;
|
|
52
|
+
return new Response(null, { status: this._status, headers: this._headers });
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { BareJS
|
|
2
|
-
import {
|
|
1
|
+
import { BareJS} from './bare';
|
|
2
|
+
import type { Context } from './context';
|
|
3
|
+
import { typebox, native } from './validators';
|
|
3
4
|
import * as TB from '@sinclair/typebox';
|
|
4
5
|
|
|
5
6
|
const app = new BareJS();
|
|
@@ -15,6 +16,7 @@ app.post('/users-tb', typebox(UserSchema), (ctx: Context) => {
|
|
|
15
16
|
return ctx.json({ message: "Saved via TypeBox", user: ctx.body });
|
|
16
17
|
});
|
|
17
18
|
|
|
19
|
+
|
|
18
20
|
// ✅ Route 2: Using Native Validator (Safe alternative if TypeBox has issues)
|
|
19
21
|
app.post('/users-native', native(UserSchema), (ctx: Context) => {
|
|
20
22
|
return ctx.json({ message: "Saved via Native", user: ctx.body });
|
|
@@ -22,5 +24,16 @@ app.post('/users-native', native(UserSchema), (ctx: Context) => {
|
|
|
22
24
|
|
|
23
25
|
// ✅ Route 3: No Validator (Pure speed, 0 ns overhead)
|
|
24
26
|
app.get('/ping', (ctx: Context) => ctx.json({ message: "pong" }));
|
|
27
|
+
// Dynamic Path
|
|
28
|
+
app.get('/user/:id', (ctx: Context) => {
|
|
29
|
+
const userId = ctx.params.id;
|
|
30
|
+
return ctx.json({ user: userId, status: 'active' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Multiple Params
|
|
34
|
+
app.get('/post/:category/:id', (ctx: Context) => {
|
|
35
|
+
return ctx.json(ctx.params); // { category: 'tech', id: '1' }
|
|
36
|
+
});
|
|
37
|
+
|
|
25
38
|
|
|
26
39
|
app.listen('0.0.0.0', 3000);
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import * as Compiler from '@sinclair/typebox/compiler';
|
|
2
|
+
// ใช้ import type สำหรับ Types เพื่อความปลอดภัยของ TS
|
|
3
|
+
import type { Context, Next } from './context';
|
|
2
4
|
|
|
3
|
-
/**
|
|
4
|
-
* 1. TypeBox Validator: Highest Performance (JIT Optimized)
|
|
5
|
-
* Best for: Production and Benchmarking
|
|
6
|
-
*/
|
|
7
5
|
export const typebox = (schema: any) => {
|
|
8
6
|
const check = Compiler.TypeCompiler.Compile(schema);
|
|
9
|
-
return async (ctx:
|
|
7
|
+
return async (ctx: Context, next: Next) => {
|
|
10
8
|
try {
|
|
11
9
|
const body = await ctx.req.json();
|
|
12
10
|
if (!check.Check(body)) return new Response("TypeBox Validation Failed", { status: 400 });
|
|
@@ -16,19 +14,15 @@ export const typebox = (schema: any) => {
|
|
|
16
14
|
};
|
|
17
15
|
};
|
|
18
16
|
|
|
19
|
-
/**
|
|
20
|
-
* 2. Native Validator: Zero Dependency
|
|
21
|
-
* Best for: Avoiding Runtime bugs or keeping the bundle lightweight.
|
|
22
|
-
*/
|
|
23
17
|
export const native = (schema: any) => {
|
|
24
18
|
const properties = schema.properties || {};
|
|
25
19
|
const keys = Object.keys(properties);
|
|
26
|
-
return async (ctx:
|
|
20
|
+
return async (ctx: Context, next: Next) => {
|
|
27
21
|
try {
|
|
28
|
-
const body = await ctx.req.json();
|
|
22
|
+
const body = await ctx.req.json() as any; // Cast เป็น any เพื่อแก้ปัญหา 'unknown'
|
|
29
23
|
for (const key of keys) {
|
|
30
24
|
if (typeof body[key] !== properties[key].type)
|
|
31
|
-
return new Response(`Native Validation Failed: ${key}
|
|
25
|
+
return new Response(`Native Validation Failed: ${key}`, { status: 400 });
|
|
32
26
|
}
|
|
33
27
|
ctx.body = body;
|
|
34
28
|
return next();
|
|
@@ -36,12 +30,8 @@ export const native = (schema: any) => {
|
|
|
36
30
|
};
|
|
37
31
|
};
|
|
38
32
|
|
|
39
|
-
/**
|
|
40
|
-
* 3. Zod Validator: Best Developer Experience
|
|
41
|
-
* Note: Requires 'npm install zod'
|
|
42
|
-
*/
|
|
43
33
|
export const zod = (schema: any) => {
|
|
44
|
-
return async (ctx:
|
|
34
|
+
return async (ctx: Context, next: Next) => {
|
|
45
35
|
try {
|
|
46
36
|
const body = await ctx.req.json();
|
|
47
37
|
const result = schema.safeParse(body);
|
package/src/types.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// src/context.ts
|
|
2
|
-
export class Context {
|
|
3
|
-
public res: Response;
|
|
4
|
-
|
|
5
|
-
constructor(public req: Request) {
|
|
6
|
-
this.res = new Response("Not Found", { status: 404 });
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
json(data: any) {
|
|
10
|
-
this.res = new Response(JSON.stringify(data), {
|
|
11
|
-
headers: { "Content-Type": "application/json" },
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
setResHeader(key: string, value: string) {
|
|
17
|
-
const newHeaders = new Headers(this.res.headers);
|
|
18
|
-
newHeaders.set(key, value);
|
|
19
|
-
this.res = new Response(this.res.body, {
|
|
20
|
-
status: this.res.status,
|
|
21
|
-
headers: newHeaders,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|