princejs 1.3.2 → 1.3.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.
Files changed (3) hide show
  1. package/Readme.md +1 -1
  2. package/dist/prince.js +150 -21
  3. package/package.json +5 -2
package/Readme.md CHANGED
@@ -66,7 +66,7 @@ Real-world 30-second load test with `autocannon -c 100 -d 30`.
66
66
 
67
67
  | Framework | Avg Req/sec | Total Requests (30s) | Avg Bytes/sec | Avg Latency |
68
68
  | -------------------- | ------------ | -------------------- | ------------- | ------------ |
69
- | **PrinceJS (yours)** | **8,526.34** | **256,000** | **1.14 MB/s** | **11.22 ms** |
69
+ | **PrinceJS** | **8,526.34** | **256,000** | **1.14 MB/s** | **11.22 ms** |
70
70
  | Hono | 8,044.8 | 241,000 | 1.08 MB/s | 11.22 ms |
71
71
  | Elysia | 9,531.21 | 286,000 | 1.28 MB/s | 10 ms |
72
72
 
package/dist/prince.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- // prince.ts
2
+ // src/prince.ts
3
3
  class TrieNode {
4
4
  children = Object.create(null);
5
5
  paramChild;
@@ -8,11 +8,52 @@ class TrieNode {
8
8
  handlers = null;
9
9
  }
10
10
 
11
+ class ResponseBuilder {
12
+ _status = 200;
13
+ _headers = {};
14
+ _body = null;
15
+ status(code) {
16
+ this._status = code;
17
+ return this;
18
+ }
19
+ header(key, value) {
20
+ this._headers[key] = value;
21
+ return this;
22
+ }
23
+ json(data) {
24
+ this._headers["Content-Type"] = "application/json";
25
+ this._body = JSON.stringify(data);
26
+ return this.build();
27
+ }
28
+ text(data) {
29
+ this._headers["Content-Type"] = "text/plain";
30
+ this._body = data;
31
+ return this.build();
32
+ }
33
+ html(data) {
34
+ this._headers["Content-Type"] = "text/html";
35
+ this._body = data;
36
+ return this.build();
37
+ }
38
+ redirect(url, status = 302) {
39
+ this._status = status;
40
+ this._headers["Location"] = url;
41
+ return this.build();
42
+ }
43
+ build() {
44
+ return new Response(this._body, {
45
+ status: this._status,
46
+ headers: this._headers
47
+ });
48
+ }
49
+ }
50
+
11
51
  class Prince {
12
52
  devMode;
13
53
  rawRoutes = [];
14
54
  middlewares = [];
15
55
  errorHandler;
56
+ prefix = "";
16
57
  constructor(devMode = false) {
17
58
  this.devMode = devMode;
18
59
  }
@@ -30,6 +71,36 @@ class Prince {
30
71
  headers: { "Content-Type": "application/json" }
31
72
  });
32
73
  }
74
+ response() {
75
+ return new ResponseBuilder;
76
+ }
77
+ route(path) {
78
+ const group = new Prince(this.devMode);
79
+ group.prefix = path;
80
+ group.middlewares = [...this.middlewares];
81
+ return {
82
+ get: (subpath, handler) => {
83
+ this.get(path + subpath, handler);
84
+ return group;
85
+ },
86
+ post: (subpath, handler) => {
87
+ this.post(path + subpath, handler);
88
+ return group;
89
+ },
90
+ put: (subpath, handler) => {
91
+ this.put(path + subpath, handler);
92
+ return group;
93
+ },
94
+ delete: (subpath, handler) => {
95
+ this.delete(path + subpath, handler);
96
+ return group;
97
+ },
98
+ patch: (subpath, handler) => {
99
+ this.patch(path + subpath, handler);
100
+ return group;
101
+ }
102
+ };
103
+ }
33
104
  get(path, handler) {
34
105
  return this.add("GET", path, handler);
35
106
  }
@@ -71,6 +142,44 @@ class Prince {
71
142
  const end = q !== -1 ? q : h !== -1 ? h : u.length;
72
143
  return u.slice(start, end);
73
144
  }
145
+ parseQuery(url) {
146
+ const q = url.indexOf("?");
147
+ if (q === -1)
148
+ return {};
149
+ const query = {};
150
+ const search = url.slice(q + 1);
151
+ const pairs = search.split("&");
152
+ for (let i = 0;i < pairs.length; i++) {
153
+ const pair = pairs[i];
154
+ const eq = pair.indexOf("=");
155
+ if (eq === -1) {
156
+ query[decodeURIComponent(pair)] = "";
157
+ } else {
158
+ query[decodeURIComponent(pair.slice(0, eq))] = decodeURIComponent(pair.slice(eq + 1));
159
+ }
160
+ }
161
+ return query;
162
+ }
163
+ async parseBody(req) {
164
+ const ct = req.headers.get("content-type") || "";
165
+ if (ct.includes("application/json")) {
166
+ return await req.json();
167
+ }
168
+ if (ct.includes("application/x-www-form-urlencoded")) {
169
+ const text = await req.text();
170
+ const params = {};
171
+ const pairs = text.split("&");
172
+ for (const pair of pairs) {
173
+ const [key, val] = pair.split("=");
174
+ params[decodeURIComponent(key)] = decodeURIComponent(val || "");
175
+ }
176
+ return params;
177
+ }
178
+ if (ct.includes("text/")) {
179
+ return await req.text();
180
+ }
181
+ return null;
182
+ }
74
183
  buildRouter() {
75
184
  const root = new TrieNode;
76
185
  for (const route of this.rawRoutes) {
@@ -85,7 +194,9 @@ class Prince {
85
194
  for (let i = 0;i < parts.length; i++) {
86
195
  const part = parts[i];
87
196
  if (part === "**") {
88
- node.catchAllChild ??= { node: new TrieNode };
197
+ if (!node.catchAllChild) {
198
+ node.catchAllChild = { name: "**", node: new TrieNode };
199
+ }
89
200
  node = node.catchAllChild.node;
90
201
  break;
91
202
  } else if (part === "*") {
@@ -109,14 +220,38 @@ class Prince {
109
220
  }
110
221
  return root;
111
222
  }
112
- compilePipeline(handler, paramsFromMatch) {
223
+ compilePipeline(handler, paramsGetter) {
113
224
  const mws = this.middlewares.slice();
114
- return async (req) => {
225
+ const hasMiddleware = mws.length > 0;
226
+ if (!hasMiddleware) {
227
+ return async (req, params, query) => {
228
+ const princeReq = req;
229
+ princeReq.params = params;
230
+ princeReq.query = query;
231
+ if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
232
+ princeReq.body = await this.parseBody(req);
233
+ }
234
+ const res = await handler(princeReq);
235
+ if (res instanceof Response)
236
+ return res;
237
+ if (typeof res === "string")
238
+ return new Response(res, { status: 200 });
239
+ if (res instanceof Uint8Array || res instanceof ArrayBuffer)
240
+ return new Response(res, { status: 200 });
241
+ return this.json(res);
242
+ };
243
+ }
244
+ return async (req, params, query) => {
245
+ const princeReq = req;
246
+ princeReq.params = params;
247
+ princeReq.query = query;
115
248
  let idx = 0;
116
249
  const runNext = async () => {
117
250
  if (idx >= mws.length) {
118
- const params = paramsFromMatch(req);
119
- const res = await handler(req, params);
251
+ if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
252
+ princeReq.body = await this.parseBody(req);
253
+ }
254
+ const res = await handler(princeReq);
120
255
  if (res instanceof Response)
121
256
  return res;
122
257
  if (typeof res === "string")
@@ -126,8 +261,7 @@ class Prince {
126
261
  return this.json(res);
127
262
  }
128
263
  const mw = mws[idx++];
129
- const maybe = await mw(req, runNext);
130
- return maybe;
264
+ return await mw(req, runNext);
131
265
  };
132
266
  const out = await runNext();
133
267
  if (out instanceof Response)
@@ -150,9 +284,10 @@ class Prince {
150
284
  fetch: async (req) => {
151
285
  try {
152
286
  const pathname = this.fastPathname(req);
287
+ const query = this.parseQuery(req.url);
153
288
  const segments = pathname === "/" ? [] : pathname.slice(1).split("/");
154
289
  let node = root;
155
- const params = Object.create(null);
290
+ const params = {};
156
291
  let matched = true;
157
292
  if (segments.length === 0) {
158
293
  if (!node.handlers)
@@ -162,20 +297,16 @@ class Prince {
162
297
  return this.json({ error: "Method not allowed" }, 405);
163
298
  let methodMap2 = handlerMap.get(node);
164
299
  if (!methodMap2) {
165
- methodMap2 = Object.create(null);
300
+ methodMap2 = {};
166
301
  handlerMap.set(node, methodMap2);
167
302
  }
168
303
  if (!methodMap2[req.method]) {
169
304
  methodMap2[req.method] = this.compilePipeline(handler2, (_) => params);
170
305
  }
171
- return await methodMap2[req.method](req);
306
+ return await methodMap2[req.method](req, params, query);
172
307
  }
173
308
  for (let i = 0;i < segments.length; i++) {
174
309
  const seg = segments[i];
175
- if (!node) {
176
- matched = false;
177
- break;
178
- }
179
310
  if (node.children[seg]) {
180
311
  node = node.children[seg];
181
312
  continue;
@@ -194,7 +325,6 @@ class Prince {
194
325
  if (node.catchAllChild.name)
195
326
  params[node.catchAllChild.name] = remaining;
196
327
  node = node.catchAllChild.node;
197
- i = segments.length;
198
328
  break;
199
329
  }
200
330
  matched = false;
@@ -202,19 +332,18 @@ class Prince {
202
332
  }
203
333
  if (!matched || !node || !node.handlers)
204
334
  return this.json({ error: "Route not found" }, 404);
205
- const byMethod = node.handlers;
206
- const handler = byMethod[req.method] ?? byMethod["GET"];
335
+ const handler = node.handlers[req.method];
207
336
  if (!handler)
208
337
  return this.json({ error: "Method not allowed" }, 405);
209
338
  let methodMap = handlerMap.get(node);
210
339
  if (!methodMap) {
211
- methodMap = Object.create(null);
340
+ methodMap = {};
212
341
  handlerMap.set(node, methodMap);
213
342
  }
214
343
  if (!methodMap[req.method]) {
215
344
  methodMap[req.method] = this.compilePipeline(handler, (_) => params);
216
345
  }
217
- return await methodMap[req.method](req);
346
+ return await methodMap[req.method](req, params, query);
218
347
  } catch (err) {
219
348
  if (this.errorHandler) {
220
349
  try {
@@ -225,7 +354,7 @@ class Prince {
225
354
  }
226
355
  }
227
356
  });
228
- console.log(`PrinceJS v2 running at http://localhost:${port}`);
357
+ console.log(`\uD83D\uDE80 PrinceJS running at http://localhost:${port}`);
229
358
  }
230
359
  }
231
360
  var prince = (dev = false) => new Prince(dev);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "princejs",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "An easy and fast backend framework — by a 13yo developer, for developers.",
5
5
  "main": "dist/prince.js",
6
6
  "types": "dist/prince.d.ts",
@@ -25,7 +25,10 @@
25
25
  "@types/bun": "^1.3.2",
26
26
  "bun-types": "latest"
27
27
  },
28
+ "dependencies": {
29
+ "zod": "^4.1.12"
30
+ },
28
31
  "scripts": {
29
- "build": "bun build prince.ts --outdir dist --target bun"
32
+ "build": "bun build ./src/prince.ts --outdir dist --target bun"
30
33
  }
31
34
  }