barejs 0.1.7 → 0.1.8
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 +1 -1
- package/src/bare.ts +143 -105
- package/src/context.ts +16 -16
- package/src/validators.ts +24 -12
package/package.json
CHANGED
package/src/bare.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// src/bare.ts
|
|
1
2
|
import { BareContext } from './context';
|
|
2
3
|
import type { Context, Middleware, Handler, WSHandlers } from './context';
|
|
3
4
|
|
|
@@ -8,37 +9,50 @@ export interface BarePlugin {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
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
|
-
}[] = [];
|
|
12
|
+
private routes: Array<{ method: string; path: string; handlers: Array<Middleware | Handler> }> = [];
|
|
13
|
+
private globalMiddlewares: Array<Middleware> = [];
|
|
14
|
+
private compiledFetch?: Function;
|
|
15
|
+
|
|
16
|
+
private staticMap: Map<string, Function> = new Map();
|
|
17
|
+
private dynamicRoutes: Array<{
|
|
18
|
+
m: string;
|
|
19
|
+
r: RegExp;
|
|
20
|
+
p: string[];
|
|
21
|
+
c: Function;
|
|
22
|
+
}> = [];
|
|
23
23
|
|
|
24
24
|
private wsHandler: { path: string; handlers: WSHandlers } | null = null;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
public
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
|
|
26
|
+
public get = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
27
|
+
this.routes.push({ method: "GET", path, handlers: h });
|
|
28
|
+
return this;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
public post = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
32
|
+
this.routes.push({ method: "POST", path, handlers: h });
|
|
33
|
+
return this;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
public put = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
37
|
+
this.routes.push({ method: "PUT", path, handlers: h });
|
|
38
|
+
return this;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
public patch = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
42
|
+
this.routes.push({ method: "PATCH", path, handlers: h });
|
|
43
|
+
return this;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
public delete = (path: string, ...h: Array<Middleware | Handler>) => {
|
|
47
|
+
this.routes.push({ method: "DELETE", path, handlers: h });
|
|
48
|
+
return this;
|
|
49
|
+
};
|
|
50
|
+
|
|
37
51
|
public ws = (path: string, handlers: WSHandlers) => {
|
|
38
52
|
this.wsHandler = { path, handlers };
|
|
39
53
|
return this;
|
|
40
54
|
};
|
|
41
|
-
|
|
55
|
+
|
|
42
56
|
public use = (arg: Middleware | BarePlugin) => {
|
|
43
57
|
if (typeof arg === 'object' && 'install' in arg) {
|
|
44
58
|
arg.install(this);
|
|
@@ -47,105 +61,129 @@ export class BareJS {
|
|
|
47
61
|
}
|
|
48
62
|
return this;
|
|
49
63
|
};
|
|
50
|
-
|
|
51
64
|
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
private compile() {
|
|
66
|
+
this.staticMap.clear();
|
|
67
|
+
this.dynamicRoutes.length = 0;
|
|
55
68
|
|
|
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
|
-
}
|
|
69
|
+
const gLen = this.globalMiddlewares.length;
|
|
70
|
+
const hasGlobal = gLen > 0;
|
|
71
|
+
const rLen = this.routes.length;
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < rLen; i++) {
|
|
74
|
+
const route = this.routes[i]!;
|
|
75
|
+
const handlers = route.handlers;
|
|
76
|
+
const hLen = handlers.length;
|
|
77
|
+
const hasDynamic = route.path.indexOf(':') !== -1;
|
|
78
|
+
|
|
79
|
+
// ULTRA FAST PATH: No middleware, single handler, no params
|
|
80
|
+
if (!hasGlobal && hLen === 1 && !hasDynamic) {
|
|
81
|
+
const h = handlers[0]!;
|
|
82
|
+
this.staticMap.set(route.method + route.path, (ctx: BareContext) => {
|
|
83
|
+
const r = h(ctx, () => ctx._finalize());
|
|
84
|
+
return r instanceof Response ? r : ctx._finalize();
|
|
85
|
+
});
|
|
86
|
+
continue;
|
|
76
87
|
}
|
|
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
88
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
let composed
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
// FAST PATH: Single handler with params OR multiple handlers
|
|
90
|
+
const total = gLen + hLen;
|
|
91
|
+
let composed: Function;
|
|
92
|
+
|
|
93
|
+
if (total === 1) {
|
|
94
|
+
const h = handlers[0] || this.globalMiddlewares[0];
|
|
95
|
+
composed = (ctx: BareContext) => {
|
|
96
|
+
const r = h!(ctx, () => ctx._finalize());
|
|
97
|
+
return r instanceof Response ? r : ctx._finalize();
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
const pipeline = Array(total);
|
|
101
|
+
for (let j = 0; j < gLen; j++) pipeline[j] = this.globalMiddlewares[j];
|
|
102
|
+
for (let j = 0; j < hLen; j++) pipeline[gLen + j] = handlers[j];
|
|
103
|
+
|
|
104
|
+
composed = (ctx: BareContext) => {
|
|
105
|
+
let idx = 0;
|
|
106
|
+
const exec = (): any => {
|
|
107
|
+
if (idx >= total) return ctx._finalize();
|
|
108
|
+
const fn = pipeline[idx++]!;
|
|
109
|
+
const r = fn(ctx, exec);
|
|
110
|
+
if (r instanceof Response) ctx.res = r;
|
|
111
|
+
return ctx.res || (idx >= total ? ctx._finalize() : exec());
|
|
112
|
+
};
|
|
113
|
+
return exec();
|
|
99
114
|
};
|
|
100
115
|
}
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
const
|
|
104
|
-
const regexPath = route.path.replace(/:([
|
|
105
|
-
|
|
116
|
+
|
|
117
|
+
if (hasDynamic) {
|
|
118
|
+
const pNames: string[] = [];
|
|
119
|
+
const regexPath = route.path.replace(/:([^/]+)/g, (_, n) => {
|
|
120
|
+
pNames.push(n);
|
|
106
121
|
return "([^/]+)";
|
|
107
122
|
});
|
|
108
123
|
this.dynamicRoutes.push({
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
m: route.method,
|
|
125
|
+
r: new RegExp(`^${regexPath}$`),
|
|
126
|
+
p: pNames,
|
|
127
|
+
c: composed
|
|
113
128
|
});
|
|
114
129
|
} else {
|
|
115
|
-
this.staticMap
|
|
130
|
+
this.staticMap.set(route.method + route.path, composed);
|
|
116
131
|
}
|
|
117
132
|
}
|
|
118
|
-
|
|
119
|
-
// JIT:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
|
|
134
|
+
// MAXIMUM JIT: Inline everything, minimal allocations
|
|
135
|
+
const sMap = this.staticMap;
|
|
136
|
+
const dRoutes = this.dynamicRoutes;
|
|
137
|
+
const dLen = dRoutes.length;
|
|
138
|
+
|
|
139
|
+
this.compiledFetch = (req: Request) => {
|
|
140
|
+
const url = req.url;
|
|
141
|
+
let i = url.indexOf('/', 8);
|
|
142
|
+
if (i === -1) i = url.length;
|
|
143
|
+
|
|
144
|
+
const path = i === url.length ? '/' : url.substring(i);
|
|
145
|
+
const key = req.method + path;
|
|
146
|
+
|
|
147
|
+
const runner = sMap.get(key);
|
|
148
|
+
if (runner) {
|
|
149
|
+
const ctx = new BareContext(req);
|
|
150
|
+
return runner(ctx);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const method = req.method;
|
|
154
|
+
for (let j = 0; j < dLen; j++) {
|
|
155
|
+
const d = dRoutes[j]!;
|
|
156
|
+
if (d.m === method) {
|
|
157
|
+
const m = d.r.exec(path);
|
|
158
|
+
if (m) {
|
|
159
|
+
const ctx = new BareContext(req);
|
|
160
|
+
const pLen = d.p.length;
|
|
161
|
+
for (let k = 0; k < pLen; k++) {
|
|
162
|
+
ctx.params[d.p[k]!] = m[k + 1]!;
|
|
163
|
+
}
|
|
164
|
+
return d.c(ctx);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return new Response('404', { status: 404 });
|
|
170
|
+
};
|
|
134
171
|
}
|
|
135
|
-
|
|
136
|
-
public fetch = (req: Request
|
|
172
|
+
|
|
173
|
+
public fetch = (req: Request) => {
|
|
137
174
|
if (!this.compiledFetch) this.compile();
|
|
138
|
-
return this.compiledFetch!(req
|
|
175
|
+
return this.compiledFetch!(req);
|
|
139
176
|
};
|
|
140
|
-
|
|
141
|
-
public listen(ip
|
|
177
|
+
|
|
178
|
+
public listen(ip = '0.0.0.0', port = 3000) {
|
|
142
179
|
this.compile();
|
|
143
|
-
console.log(`\x1b[32m⚡ BareJS
|
|
180
|
+
console.log(`\x1b[32m⚡ BareJS MAX at http://${ip}:${port}\x1b[0m`);
|
|
181
|
+
|
|
144
182
|
return Bun.serve({
|
|
145
183
|
hostname: ip,
|
|
146
184
|
port,
|
|
147
|
-
reusePort: true,
|
|
148
|
-
fetch:
|
|
185
|
+
reusePort: true,
|
|
186
|
+
fetch: this.fetch.bind(this),
|
|
149
187
|
websocket: {
|
|
150
188
|
open: (ws) => this.wsHandler?.handlers.open?.(ws),
|
|
151
189
|
message: (ws, msg) => this.wsHandler?.handlers.message?.(ws, msg),
|
|
@@ -153,4 +191,4 @@ export class BareJS {
|
|
|
153
191
|
}
|
|
154
192
|
});
|
|
155
193
|
}
|
|
156
|
-
}
|
|
194
|
+
}
|
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/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
|
+
};
|