barejs 0.1.7 → 0.1.9
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/package.json +2 -2
- package/src/bare.ts +146 -105
- package/src/context.ts +16 -16
- package/src/index.ts +4 -39
- package/src/validators.ts +24 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "barejs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"author": "xarhang",
|
|
5
5
|
"description": "High-performance JIT-specialized web framework for Bun",
|
|
6
6
|
"main": "./src/bare.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./src/bare.ts",
|
|
11
|
-
"./middleware": "./src/
|
|
11
|
+
"./middleware": "./src/validators.ts"
|
|
12
12
|
},
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"repository": {
|
package/src/bare.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/bare.ts
|
|
2
|
+
export * from './context';
|
|
3
|
+
export * from './validators';
|
|
4
|
+
|
|
1
5
|
import { BareContext } from './context';
|
|
2
6
|
import type { Context, Middleware, Handler, WSHandlers } from './context';
|
|
3
7
|
|
|
@@ -8,37 +12,50 @@ export interface BarePlugin {
|
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
export class BareJS {
|
|
11
|
-
private routes: { method: string; path: string; handlers:
|
|
12
|
-
private globalMiddlewares: Middleware
|
|
13
|
-
private compiledFetch?:
|
|
14
|
-
|
|
15
|
-
private staticMap:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}[] = [];
|
|
15
|
+
private routes: Array<{ method: string; path: string; handlers: Array<Middleware | Handler> }> = [];
|
|
16
|
+
private globalMiddlewares: Array<Middleware> = [];
|
|
17
|
+
private compiledFetch?: Function;
|
|
18
|
+
|
|
19
|
+
private staticMap: Map<string, Function> = new Map();
|
|
20
|
+
private dynamicRoutes: Array<{
|
|
21
|
+
m: string;
|
|
22
|
+
r: RegExp;
|
|
23
|
+
p: string[];
|
|
24
|
+
c: Function;
|
|
25
|
+
}> = [];
|
|
23
26
|
|
|
24
27
|
private wsHandler: { path: string; handlers: WSHandlers } | null = null;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
public
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
|
|
29
|
+
public get = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
30
|
+
this.routes.push({ method: "GET", path, handlers: h });
|
|
31
|
+
return this;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
public post = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
35
|
+
this.routes.push({ method: "POST", path, handlers: h });
|
|
36
|
+
return this;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
public put = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
40
|
+
this.routes.push({ method: "PUT", path, handlers: h });
|
|
41
|
+
return this;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
public patch = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
45
|
+
this.routes.push({ method: "PATCH", path, handlers: h });
|
|
46
|
+
return this;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
public delete = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
50
|
+
this.routes.push({ method: "DELETE", path, handlers: h });
|
|
51
|
+
return this;
|
|
52
|
+
};
|
|
53
|
+
|
|
37
54
|
public ws = (path: string, handlers: WSHandlers) => {
|
|
38
55
|
this.wsHandler = { path, handlers };
|
|
39
56
|
return this;
|
|
40
57
|
};
|
|
41
|
-
|
|
58
|
+
|
|
42
59
|
public use = (arg: Middleware | BarePlugin) => {
|
|
43
60
|
if (typeof arg === 'object' && 'install' in arg) {
|
|
44
61
|
arg.install(this);
|
|
@@ -47,105 +64,129 @@ export class BareJS {
|
|
|
47
64
|
}
|
|
48
65
|
return this;
|
|
49
66
|
};
|
|
50
|
-
|
|
51
67
|
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
private compile() {
|
|
69
|
+
this.staticMap.clear();
|
|
70
|
+
this.dynamicRoutes.length = 0;
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return await route.chain(ctx);
|
|
75
|
-
}
|
|
72
|
+
const gLen = this.globalMiddlewares.length;
|
|
73
|
+
const hasGlobal = gLen > 0;
|
|
74
|
+
const rLen = this.routes.length;
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < rLen; i++) {
|
|
77
|
+
const route = this.routes[i]!;
|
|
78
|
+
const handlers = route.handlers;
|
|
79
|
+
const hLen = handlers.length;
|
|
80
|
+
const hasDynamic = route.path.indexOf(':') !== -1;
|
|
81
|
+
|
|
82
|
+
// ULTRA FAST PATH: No middleware, single handler, no params
|
|
83
|
+
if (!hasGlobal && hLen === 1 && !hasDynamic) {
|
|
84
|
+
const h = handlers[0]!;
|
|
85
|
+
this.staticMap.set(route.method + route.path, (ctx: BareContext) => {
|
|
86
|
+
const r = h(ctx, () => ctx._finalize());
|
|
87
|
+
return r instanceof Response ? r : ctx._finalize();
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
76
90
|
}
|
|
77
|
-
}
|
|
78
|
-
return new Response('404 Not Found', { status: 404 });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private compile() {
|
|
82
|
-
this.staticMap = {};
|
|
83
|
-
this.dynamicRoutes = [];
|
|
84
|
-
|
|
85
|
-
for (const route of this.routes) {
|
|
86
|
-
const pipeline = [...this.globalMiddlewares, ...route.handlers];
|
|
87
91
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
let composed
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
// FAST PATH: Single handler with params OR multiple handlers
|
|
93
|
+
const total = gLen + hLen;
|
|
94
|
+
let composed: Function;
|
|
95
|
+
|
|
96
|
+
if (total === 1) {
|
|
97
|
+
const h = handlers[0] || this.globalMiddlewares[0];
|
|
98
|
+
composed = (ctx: BareContext) => {
|
|
99
|
+
const r = h!(ctx, () => ctx._finalize());
|
|
100
|
+
return r instanceof Response ? r : ctx._finalize();
|
|
101
|
+
};
|
|
102
|
+
} else {
|
|
103
|
+
const pipeline = Array(total);
|
|
104
|
+
for (let j = 0; j < gLen; j++) pipeline[j] = this.globalMiddlewares[j];
|
|
105
|
+
for (let j = 0; j < hLen; j++) pipeline[gLen + j] = handlers[j];
|
|
106
|
+
|
|
107
|
+
composed = (ctx: BareContext) => {
|
|
108
|
+
let idx = 0;
|
|
109
|
+
const exec = (): any => {
|
|
110
|
+
if (idx >= total) return ctx._finalize();
|
|
111
|
+
const fn = pipeline[idx++]!;
|
|
112
|
+
const r = fn(ctx, exec);
|
|
113
|
+
if (r instanceof Response) ctx.res = r;
|
|
114
|
+
return ctx.res || (idx >= total ? ctx._finalize() : exec());
|
|
115
|
+
};
|
|
116
|
+
return exec();
|
|
99
117
|
};
|
|
100
118
|
}
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
const
|
|
104
|
-
const regexPath = route.path.replace(/:([
|
|
105
|
-
|
|
119
|
+
|
|
120
|
+
if (hasDynamic) {
|
|
121
|
+
const pNames: string[] = [];
|
|
122
|
+
const regexPath = route.path.replace(/:([^/]+)/g, (_, n) => {
|
|
123
|
+
pNames.push(n);
|
|
106
124
|
return "([^/]+)";
|
|
107
125
|
});
|
|
108
126
|
this.dynamicRoutes.push({
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
m: route.method,
|
|
128
|
+
r: new RegExp(`^${regexPath}$`),
|
|
129
|
+
p: pNames,
|
|
130
|
+
c: composed
|
|
113
131
|
});
|
|
114
132
|
} else {
|
|
115
|
-
this.staticMap
|
|
133
|
+
this.staticMap.set(route.method + route.path, composed);
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
|
-
|
|
119
|
-
// JIT:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
|
|
137
|
+
// MAXIMUM JIT: Inline everything, minimal allocations
|
|
138
|
+
const sMap = this.staticMap;
|
|
139
|
+
const dRoutes = this.dynamicRoutes;
|
|
140
|
+
const dLen = dRoutes.length;
|
|
141
|
+
|
|
142
|
+
this.compiledFetch = (req: Request) => {
|
|
143
|
+
const url = req.url;
|
|
144
|
+
let i = url.indexOf('/', 8);
|
|
145
|
+
if (i === -1) i = url.length;
|
|
146
|
+
|
|
147
|
+
const path = i === url.length ? '/' : url.substring(i);
|
|
148
|
+
const key = req.method + path;
|
|
149
|
+
|
|
150
|
+
const runner = sMap.get(key);
|
|
151
|
+
if (runner) {
|
|
152
|
+
const ctx = new BareContext(req);
|
|
153
|
+
return runner(ctx);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const method = req.method;
|
|
157
|
+
for (let j = 0; j < dLen; j++) {
|
|
158
|
+
const d = dRoutes[j]!;
|
|
159
|
+
if (d.m === method) {
|
|
160
|
+
const m = d.r.exec(path);
|
|
161
|
+
if (m) {
|
|
162
|
+
const ctx = new BareContext(req);
|
|
163
|
+
const pLen = d.p.length;
|
|
164
|
+
for (let k = 0; k < pLen; k++) {
|
|
165
|
+
ctx.params[d.p[k]!] = m[k + 1]!;
|
|
166
|
+
}
|
|
167
|
+
return d.c(ctx);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return new Response('404', { status: 404 });
|
|
173
|
+
};
|
|
134
174
|
}
|
|
135
|
-
|
|
136
|
-
public fetch = (req: Request
|
|
175
|
+
|
|
176
|
+
public fetch = (req: Request) => {
|
|
137
177
|
if (!this.compiledFetch) this.compile();
|
|
138
|
-
return this.compiledFetch!(req
|
|
178
|
+
return this.compiledFetch!(req);
|
|
139
179
|
};
|
|
140
|
-
|
|
141
|
-
public listen(ip
|
|
180
|
+
|
|
181
|
+
public listen(ip = '0.0.0.0', port = 3000) {
|
|
142
182
|
this.compile();
|
|
143
|
-
console.log(`\x1b[32m⚡ BareJS
|
|
183
|
+
console.log(`\x1b[32m⚡ BareJS MAX at http://${ip}:${port}\x1b[0m`);
|
|
184
|
+
|
|
144
185
|
return Bun.serve({
|
|
145
186
|
hostname: ip,
|
|
146
187
|
port,
|
|
147
|
-
reusePort: true,
|
|
148
|
-
fetch:
|
|
188
|
+
reusePort: true,
|
|
189
|
+
fetch: this.fetch.bind(this),
|
|
149
190
|
websocket: {
|
|
150
191
|
open: (ws) => this.wsHandler?.handlers.open?.(ws),
|
|
151
192
|
message: (ws, msg) => this.wsHandler?.handlers.message?.(ws, msg),
|
|
@@ -153,4 +194,4 @@ export class BareJS {
|
|
|
153
194
|
}
|
|
154
195
|
});
|
|
155
196
|
}
|
|
156
|
-
}
|
|
197
|
+
}
|
package/src/context.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
// src/context.ts
|
|
1
3
|
export interface WSHandlers {
|
|
2
4
|
open?: (ws: any) => void;
|
|
3
5
|
message?: (ws: any, message: string | Buffer) => void;
|
|
@@ -5,14 +7,13 @@ export interface WSHandlers {
|
|
|
5
7
|
drain?: (ws: any) => void;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
export type Next = () =>
|
|
10
|
+
export type Next = () => any;
|
|
9
11
|
export type Middleware = (ctx: Context, next: Next) => any;
|
|
10
|
-
export type Handler = (ctx: Context) => any;
|
|
11
|
-
|
|
12
|
+
export type Handler = (ctx: Context, next: Next) => any;
|
|
12
13
|
|
|
13
14
|
export interface Context {
|
|
14
15
|
req: Request;
|
|
15
|
-
res
|
|
16
|
+
res?: Response;
|
|
16
17
|
params: Record<string, string>;
|
|
17
18
|
body?: any;
|
|
18
19
|
[key: string]: any;
|
|
@@ -22,14 +23,14 @@ export interface Context {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export class BareContext {
|
|
25
|
-
public res
|
|
26
|
+
public res?: Response;
|
|
26
27
|
public params: Record<string, string> = {};
|
|
27
|
-
public body: any
|
|
28
|
+
public body: any;
|
|
28
29
|
public _status = 200;
|
|
29
30
|
public _headers: Record<string, string> = {};
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
constructor(public req: Request) {}
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
json(data: any) {
|
|
34
35
|
this._headers["Content-Type"] = "application/json";
|
|
35
36
|
this.res = new Response(JSON.stringify(data), {
|
|
@@ -37,18 +38,17 @@ export class BareContext {
|
|
|
37
38
|
headers: this._headers,
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
status(code: number)
|
|
41
|
+
|
|
42
|
+
status(code: number) {
|
|
42
43
|
this._status = code;
|
|
43
44
|
return this;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
setResHeader(k: string, v: string) {
|
|
47
48
|
this._headers[k] = v;
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return new Response(null, { status: this._status, headers: this._headers });
|
|
50
|
+
|
|
51
|
+
_finalize() {
|
|
52
|
+
return this.res || new Response(null, { status: this._status, headers: this._headers });
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,39 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const app = new BareJS();
|
|
7
|
-
|
|
8
|
-
// Create Schema with TypeBox
|
|
9
|
-
const UserSchema = TB.Type.Object({
|
|
10
|
-
name: TB.Type.String(),
|
|
11
|
-
age: TB.Type.Number()
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// ✅ Route 1: Using TypeBox Validator (Fastest for Bun)
|
|
15
|
-
app.post('/users-tb', typebox(UserSchema), (ctx: Context) => {
|
|
16
|
-
return ctx.json({ message: "Saved via TypeBox", user: ctx.body });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// ✅ Route 2: Using Native Validator (Safe alternative if TypeBox has issues)
|
|
21
|
-
app.post('/users-native', native(UserSchema), (ctx: Context) => {
|
|
22
|
-
return ctx.json({ message: "Saved via Native", user: ctx.body });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// ✅ Route 3: No Validator (Pure speed, 0 ns overhead)
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
app.listen('0.0.0.0', 3000);
|
|
1
|
+
export { BareJS } from './bare';
|
|
2
|
+
export { BareContext } from './context';
|
|
3
|
+
export type { Context, Middleware, Handler, Next } from './context';
|
|
4
|
+
export { typebox, native, zod } from './validators';
|
package/src/validators.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
// src/validators.ts
|
|
1
2
|
import * as Compiler from '@sinclair/typebox/compiler';
|
|
2
|
-
// ใช้ import type สำหรับ Types เพื่อความปลอดภัยของ TS
|
|
3
3
|
import type { Context, Next } from './context';
|
|
4
4
|
|
|
5
5
|
export const typebox = (schema: any) => {
|
|
@@ -7,26 +7,36 @@ export const typebox = (schema: any) => {
|
|
|
7
7
|
return async (ctx: Context, next: Next) => {
|
|
8
8
|
try {
|
|
9
9
|
const body = await ctx.req.json();
|
|
10
|
-
if (!check.Check(body)) return new Response("
|
|
10
|
+
if (!check.Check(body)) return new Response("Validation Failed", { status: 400 });
|
|
11
11
|
ctx.body = body;
|
|
12
12
|
return next();
|
|
13
|
-
} catch {
|
|
13
|
+
} catch {
|
|
14
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
15
|
+
}
|
|
14
16
|
};
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export const native = (schema: any) => {
|
|
18
|
-
const
|
|
19
|
-
const keys = Object.keys(
|
|
20
|
+
const props = schema.properties || {};
|
|
21
|
+
const keys = Object.keys(props);
|
|
22
|
+
const kLen = keys.length;
|
|
23
|
+
|
|
20
24
|
return async (ctx: Context, next: Next) => {
|
|
21
25
|
try {
|
|
22
|
-
const body = await ctx.req.json() as any;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const body = await ctx.req.json() as any;
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < kLen; i++) {
|
|
29
|
+
const k = keys[i]!;
|
|
30
|
+
if (typeof body[k] !== props[k]?.type) {
|
|
31
|
+
return new Response(`Validation Failed: ${k}`, { status: 400 });
|
|
32
|
+
}
|
|
26
33
|
}
|
|
34
|
+
|
|
27
35
|
ctx.body = body;
|
|
28
36
|
return next();
|
|
29
|
-
} catch {
|
|
37
|
+
} catch {
|
|
38
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
39
|
+
}
|
|
30
40
|
};
|
|
31
41
|
};
|
|
32
42
|
|
|
@@ -38,6 +48,8 @@ export const zod = (schema: any) => {
|
|
|
38
48
|
if (!result.success) return new Response(JSON.stringify(result.error), { status: 400 });
|
|
39
49
|
ctx.body = result.data;
|
|
40
50
|
return next();
|
|
41
|
-
} catch {
|
|
51
|
+
} catch {
|
|
52
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
53
|
+
}
|
|
42
54
|
};
|
|
43
|
-
};
|
|
55
|
+
};
|