princejs 1.5.4 → 1.6.0

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 CHANGED
@@ -9,13 +9,8 @@
9
9
  ## 🏆 World Record: Fastest Framework Under 3 kB
10
10
 
11
11
  ```bash
12
- # Create a new PrinceJS app
13
12
  bun create princejs my-app
14
-
15
- # Move into the project
16
13
  cd my-app
17
-
18
- # Run in development mode
19
14
  bun dev
20
15
  ```
21
16
 
@@ -38,52 +33,55 @@ app.listen(3000);
38
33
 
39
34
  ## ⚔️ Size War (Gzipped — Real World)
40
35
 
41
- | Framework | Gzipped | Minified | vs PrinceJS |
42
- | ------------ | ---------- | ---------- | ----------- |
43
- | **PrinceJS** | **2.8 kB** | **7.8 kB** | — |
44
- | **Hono** | 7.3 kB | 18.7 kB | 2.6× bigger |
45
- | **Elysia** | 62.5 kB | 245 kB | 22× bigger |
36
+ | Framework | Gzipped | Minified | vs PrinceJS |
37
+ | ------------ | ---------- | -------- | --------------- |
38
+ | **PrinceJS** | **2.8 kB** | 7.8 kB | — |
39
+ | Hono | 7.3 kB | 18.7 kB | **2.6× bigger** |
40
+ | Elysia | 62.5 kB | 245 kB | **22× bigger** |
46
41
 
47
- > PrinceJS fits in a tweet. Elysia needs a ZIP file.
42
+ PrinceJS fits in a tweet. Elysia needs a ZIP file.
48
43
 
49
44
  ---
50
45
 
51
46
  ## ⚡ Benchmarks (autocannon -c 100 -d 30)
52
47
 
53
48
  **Windows 11 • November 15, 2025 • 100 connections • 30 seconds**
49
+ **Route:** `GET /users/:id`
54
50
 
55
- ### Route: `GET /users/:id`
56
-
57
- | Rank | Framework | Req/s | Requests (30s) | Throughput |
58
- | ---- | ------------ | ---------- | -------------- | ----------- |
59
- | 🥇 | **PrinceJS** | **19,200** | **576k** | **2.34 MB/s** |
60
- | 🥈 | Hono | 16,212 | 486k | 1.98 MB/s |
61
- | 🥉 | Elysia | 15,862 | 476k | 1.94 MB/s |
62
- | 4️⃣ | Express | 9,325 | 280k | 1.84 MB/s |
51
+ | Rank | Framework | Req/s | Requests (30s) | Throughput |
52
+ | --------------- | ---------- | ----- | -------------- | ---------- |
53
+ | 🥇 **PrinceJS** | **19,200** | 576k | 2.34 MB/s | |
54
+ | 🥈 Hono | 16,212 | 486k | 1.98 MB/s | |
55
+ | 🥉 Elysia | 15,862 | 476k | 1.94 MB/s | |
56
+ | 4️⃣ Express | 9,325 | 280k | 1.84 MB/s | |
63
57
 
64
58
  ### Summary
65
59
 
66
- - **PrinceJS beats Elysia by 21%** (3,338 more req/s)
67
- - **PrinceJS beats Hono by 18%** (2,988 more req/s)
68
- - **PrinceJS beats Express by 106%** (2× faster)
60
+ * PrinceJS beats **Elysia by 21%** (3,338 more req/s)
61
+ * PrinceJS beats **Hono by 18%** (2,988 more req/s)
62
+ * PrinceJS beats **Express by 106%** (over 2× faster)
69
63
 
70
- > **PrinceJS is the FASTEST framework under 10 kB. Period.**
64
+ > PrinceJS is the FASTEST framework under 10 kB. Period.
71
65
 
72
66
  ---
73
67
 
74
68
  ## 🔥 Why PrinceJS Wins
75
69
 
76
- ### 1. **Trie-Based Router** (Cached)
70
+ ### 1. **Trie-Based Router (Cached)**
71
+
77
72
  Most frameworks rebuild routes on every request. PrinceJS builds once and caches.
78
73
 
79
74
  ### 2. **Zero Overhead Middleware**
75
+
80
76
  Middleware tracking prevents duplicate execution. No wasted cycles.
81
77
 
82
78
  ### 3. **Optimized for Bun**
83
- Native Bun.serve() with WebSocket support. No abstraction layers.
79
+
80
+ Native `Bun.serve()` with WebSocket support. No abstraction layers.
84
81
 
85
82
  ### 4. **Smart Body Parsing**
86
- Only parses body when needed (POST/PUT/PATCH). GET requests skip parsing entirely.
83
+
84
+ Only parses body when needed. GET requests skip parsing entirely.
87
85
 
88
86
  ---
89
87
 
@@ -105,12 +103,42 @@ app
105
103
  })));
106
104
  ```
107
105
 
108
- **Middleware:** CORS, Logger, Rate Limiting, Static Files
109
- ✅ **Validation:** Zod schema validation
110
- **WebSocket:** Full WebSocket support
111
- **File Upload:** Multipart form data handling
112
- **Response Builder:** Fluent API for responses
113
- **OpenAPI:** Auto-generate API docs
106
+ ### Middleware
107
+
108
+ * CORS
109
+ * Logger
110
+ * Rate Limiting
111
+ * Static Files
112
+
113
+ ### ✓ Validation
114
+
115
+ * Zod schema validation
116
+
117
+ ### ✓ WebSocket Support
118
+
119
+ ### ✓ File Uploads
120
+
121
+ ### ✓ Response Builder
122
+
123
+ ### ✓ OpenAPI
124
+
125
+ ---
126
+
127
+ ## 🆕 v3.3.1 — New Tree-Shakable Features
128
+
129
+ ```ts
130
+ import { cache, email, ai, upload } from "princejs/helpers";
131
+ import { cron, openapi } from "princejs/scheduler";
132
+ ```
133
+
134
+ * `cache(60)(handler)` — In-memory cache
135
+ * `email(to, subject, html)` — Resend.com
136
+ * `ai(prompt)` — Grok-3 via xAI API
137
+ * `upload()` — 1-line file upload
138
+ * `cron("*/2 * * * *", task)` — Cron support
139
+ * `openapi({ title, version })` — Auto docs
140
+
141
+ **Tree-shakable = only what you import gets bundled**
114
142
 
115
143
  ---
116
144
 
@@ -132,6 +160,8 @@ yarn add princejs
132
160
  import { prince } from "princejs";
133
161
  import { cors, logger, rateLimit } from "princejs/middleware";
134
162
  import { validate } from "princejs/validation";
163
+ import { cache, ai, upload } from "princejs/helpers";
164
+ import { cron } from "princejs/scheduler";
135
165
  import { z } from "zod";
136
166
 
137
167
  const app = prince(true); // dev mode
@@ -141,10 +171,13 @@ app.use(cors());
141
171
  app.use(logger());
142
172
  app.use(rateLimit({ max: 100, window: 60 }));
143
173
 
174
+ // Validation
175
+ app.use(validate(z.object({ name: z.string() })));
176
+
144
177
  // Routes
145
178
  app.get("/", () => ({
146
179
  message: "Welcome to PrinceJS",
147
- version: "1.5.2"
180
+ version: "3.3.1"
148
181
  }));
149
182
 
150
183
  app.get("/users/:id", (req) => ({
@@ -152,17 +185,26 @@ app.get("/users/:id", (req) => ({
152
185
  name: "John Doe"
153
186
  }));
154
187
 
155
- // File upload
156
- app.post("/upload", (req) => ({
188
+ // New: Cache
189
+ app.get("/data", cache(60)(() => ({ time: Date.now() })));
190
+
191
+ // New: AI
192
+ app.post("/ai", async (req) => ({ reply: await ai(req.body.q) }));
193
+
194
+ // New: Upload
195
+ app.post("/upload", upload(), (req) => ({
157
196
  files: Object.keys(req.files || {}),
158
197
  body: req.body
159
198
  }));
160
199
 
200
+ // New: Cron
201
+ cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
202
+
161
203
  // WebSocket
162
204
  app.ws("/chat", {
163
205
  open: (ws) => ws.send("Welcome!"),
164
206
  message: (ws, msg) => ws.send(`Echo: ${msg}`),
165
- close: (ws) => console.log("Disconnected")
207
+ close: () => console.log("Disconnected")
166
208
  });
167
209
 
168
210
  app.listen(3000);
@@ -172,13 +214,13 @@ app.listen(3000);
172
214
 
173
215
  ## 📚 Documentation
174
216
 
175
- Check: [princejs](https://princejs.vercel.app)
217
+ Visit: **princejs.vercel.app**
176
218
 
177
219
  ---
178
220
 
179
221
  ## 🤝 Contributing
180
222
 
181
- Issues and PRs welcome! This is a learning project but we take quality seriously.
223
+ Issues and PRs welcome!
182
224
 
183
225
  ```bash
184
226
  git clone https://github.com/MatthewTheCoder1218/princejs
@@ -191,9 +233,8 @@ bun test
191
233
 
192
234
  ## 🇳🇬 Built in Nigeria
193
235
 
194
- **Made by @Lil_Prince_1218 — Age 13**
195
-
196
- > *"2.8 kB. 19,200 req/s. The fastest framework under 10 kB."*
236
+ Made by **@Lil_Prince_1218 — Age 13**
237
+ *"2.8 kB. 19,200 req/s. The fastest framework under 10 kB."*
197
238
 
198
239
  Inspired by the greats (Express, Hono, Elysia) but built to win.
199
240
 
@@ -201,15 +242,15 @@ Inspired by the greats (Express, Hono, Elysia) but built to win.
201
242
 
202
243
  ## 📄 License
203
244
 
204
- MIT © 2025 Matthew Michael
245
+ MIT © 2025 **Matthew Michael**
205
246
 
206
247
  ---
207
248
 
208
249
  ## ⭐ Star This Repo
209
250
 
210
- If PrinceJS helped you, give it a star! It helps other developers discover it.
251
+ If PrinceJS helped you, star the repo!
211
252
 
212
- **GitHub:** [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
253
+ GitHub: [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
213
254
 
214
255
  ---
215
256
 
@@ -221,4 +262,4 @@ If PrinceJS helped you, give it a star! It helps other developers discover it.
221
262
 
222
263
  ---
223
264
 
224
- **PrinceJS: Small in size. Giant in speed. 🚀**
265
+ **PrinceJS: Small in size. Giant in speed. 🚀**
@@ -0,0 +1,44 @@
1
+ // @bun
2
+ // src/helpers.ts
3
+ var cache = (ttl) => {
4
+ const store = {};
5
+ return (handler) => async (req) => {
6
+ const key = req.url;
7
+ const now = Date.now();
8
+ if (store[key]?.exp > now)
9
+ return store[key].data;
10
+ const data = await handler(req);
11
+ store[key] = { data, exp: now + ttl * 1000 };
12
+ setTimeout(() => delete store[key], ttl * 1000 + 1000);
13
+ return data;
14
+ };
15
+ };
16
+ var email = async (to, subject, html) => {
17
+ await fetch("https://api.resend.com/emails", {
18
+ method: "POST",
19
+ headers: { Authorization: `Bearer ${process.env.RESEND_KEY}` },
20
+ body: JSON.stringify({ from: "no-reply@princejs.dev", to, subject, html })
21
+ });
22
+ };
23
+ var ai = async (prompt) => {
24
+ const res = await fetch("https://api.x.ai/v1/chat/completions", {
25
+ method: "POST",
26
+ headers: { Authorization: `Bearer ${process.env.XAI_KEY}` },
27
+ body: JSON.stringify({ model: "grok-beta", messages: [{ role: "user", content: prompt }] })
28
+ });
29
+ const json = await res.json();
30
+ return json.choices?.[0]?.message?.content || "";
31
+ };
32
+ var upload = () => {
33
+ return async (req) => {
34
+ const form = await req.formData();
35
+ const file = form.get("file");
36
+ return { name: file.name, size: file.size };
37
+ };
38
+ };
39
+ export {
40
+ upload,
41
+ email,
42
+ cache,
43
+ ai
44
+ };
@@ -1,159 +1,66 @@
1
1
  // @bun
2
2
  // src/middleware.ts
3
- var MIDDLEWARE_EXECUTED = Symbol("middlewareExecuted");
4
- var cors = (options) => {
5
- const origin = options?.origin || "*";
6
- const methods = options?.methods || "GET,POST,PUT,DELETE,PATCH,OPTIONS";
7
- const headers = options?.headers || "Content-Type,Authorization";
8
- const credentials = options?.credentials || false;
3
+ var cors = (origin = "*") => {
9
4
  return async (req, next) => {
10
- if (req[MIDDLEWARE_EXECUTED]?.cors) {
11
- return await next();
12
- }
13
- if (!req[MIDDLEWARE_EXECUTED]) {
14
- req[MIDDLEWARE_EXECUTED] = {};
15
- }
16
- req[MIDDLEWARE_EXECUTED].cors = true;
17
5
  if (req.method === "OPTIONS") {
18
6
  return new Response(null, {
19
7
  status: 204,
20
8
  headers: {
21
9
  "Access-Control-Allow-Origin": origin,
22
- "Access-Control-Allow-Methods": methods,
23
- "Access-Control-Allow-Headers": headers,
24
- ...credentials ? { "Access-Control-Allow-Credentials": "true" } : {}
10
+ "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,PATCH,OPTIONS",
11
+ "Access-Control-Allow-Headers": "Content-Type,Authorization"
25
12
  }
26
13
  });
27
14
  }
28
15
  const res = await next();
29
- if (!res)
30
- return res;
31
- const newHeaders = new Headers(res.headers);
32
- newHeaders.set("Access-Control-Allow-Origin", origin);
33
- if (credentials)
34
- newHeaders.set("Access-Control-Allow-Credentials", "true");
35
- return new Response(res.body, {
36
- status: res.status,
37
- statusText: res.statusText,
38
- headers: newHeaders
39
- });
16
+ res?.headers.set("Access-Control-Allow-Origin", origin);
17
+ return res;
40
18
  };
41
19
  };
42
- var logger = (options) => {
43
- const format = options?.format || "dev";
44
- const colors = options?.colors !== false;
45
- const colorize = (code, text) => {
46
- if (!colors)
47
- return text;
48
- if (code >= 500)
49
- return `\x1B[31m${text}\x1B[0m`;
50
- if (code >= 400)
51
- return `\x1B[33m${text}\x1B[0m`;
52
- if (code >= 300)
53
- return `\x1B[36m${text}\x1B[0m`;
54
- if (code >= 200)
55
- return `\x1B[32m${text}\x1B[0m`;
56
- return text;
57
- };
20
+ var logger = () => {
58
21
  return async (req, next) => {
59
- if (req[MIDDLEWARE_EXECUTED]?.logger) {
60
- return await next();
61
- }
62
- if (!req[MIDDLEWARE_EXECUTED]) {
63
- req[MIDDLEWARE_EXECUTED] = {};
64
- }
65
- req[MIDDLEWARE_EXECUTED].logger = true;
66
22
  const start = Date.now();
67
- const pathname = new URL(req.url).pathname;
68
23
  const res = await next();
69
- if (!res)
70
- return res;
71
- const duration = Date.now() - start;
72
- const status = res.status;
73
- if (format === "dev") {
74
- console.log(`${colorize(status, req.method)} ${pathname} ${colorize(status, String(status))} ${duration}ms`);
75
- } else if (format === "tiny") {
76
- console.log(`${req.method} ${pathname} ${status} - ${duration}ms`);
77
- } else {
78
- const date = new Date().toISOString();
79
- console.log(`[${date}] ${req.method} ${pathname} ${status} ${duration}ms`);
80
- }
24
+ console.log(`${req.method} ${new URL(req.url).pathname} ${res?.status} ${Date.now() - start}ms`);
81
25
  return res;
82
26
  };
83
27
  };
84
- var rateLimit = (options) => {
85
- const store = new Map;
86
- setInterval(() => {
87
- const now = Date.now();
88
- for (const [key, record] of store.entries()) {
89
- if (now > record.resetAt) {
90
- store.delete(key);
91
- }
92
- }
93
- }, options.window * 1000);
28
+ var jwt = (secret) => {
94
29
  return async (req, next) => {
95
- if (req[MIDDLEWARE_EXECUTED]?.rateLimit) {
96
- return await next();
97
- }
98
- if (!req[MIDDLEWARE_EXECUTED]) {
99
- req[MIDDLEWARE_EXECUTED] = {};
100
- }
101
- req[MIDDLEWARE_EXECUTED].rateLimit = true;
102
- const key = options.keyGenerator ? options.keyGenerator(req) : req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "unknown";
103
- const now = Date.now();
104
- const windowMs = options.window * 1000;
105
- let record = store.get(key);
106
- if (!record || now > record.resetAt) {
107
- record = { count: 1, resetAt: now + windowMs };
108
- store.set(key, record);
109
- return await next();
30
+ const auth = req.headers.get("authorization");
31
+ if (auth?.startsWith("Bearer ")) {
32
+ try {
33
+ req.user = JSON.parse(atob(auth.slice(7).split(".")[1]));
34
+ } catch {}
110
35
  }
111
- if (record.count >= options.max) {
112
- const retryAfter = Math.ceil((record.resetAt - now) / 1000);
113
- return new Response(JSON.stringify({
114
- error: options.message || "Too many requests",
115
- retryAfter
116
- }), {
117
- status: 429,
118
- headers: {
119
- "Content-Type": "application/json",
120
- "Retry-After": String(retryAfter)
121
- }
122
- });
123
- }
124
- record.count++;
125
- return await next();
36
+ return next();
126
37
  };
127
38
  };
128
- var serve = (options) => {
129
- const root = options.root || "./public";
130
- const index = options.index || "index.html";
131
- const dotfiles = options.dotfiles || "deny";
39
+ var rateLimit = (max, window = 60) => {
40
+ const store = {};
41
+ return async (req, next) => {
42
+ const ip = req.headers.get("x-forwarded-for") || "unknown";
43
+ const key = `${ip}:${Math.floor(Date.now() / (window * 1000))}`;
44
+ store[key] = (store[key] || 0) + 1;
45
+ if (store[key] > max)
46
+ return new Response("Too many requests", { status: 429 });
47
+ return next();
48
+ };
49
+ };
50
+ var validate = (schema) => {
132
51
  return async (req, next) => {
133
- const url = new URL(req.url);
134
- let filepath = url.pathname;
135
- if (filepath.includes("..")) {
136
- return new Response("Forbidden", { status: 403 });
137
- }
138
- if (dotfiles === "deny" && filepath.split("/").some((part) => part.startsWith("."))) {
139
- return new Response("Forbidden", { status: 403 });
140
- }
141
52
  try {
142
- const file = Bun.file(`${root}${filepath}`);
143
- if (await file.exists()) {
144
- return new Response(file);
145
- }
146
- const indexFile = Bun.file(`${root}${filepath}/${index}`);
147
- if (await indexFile.exists()) {
148
- return new Response(indexFile);
149
- }
150
- } catch (err) {}
151
- return await next();
53
+ req.body = schema.parse(req.body);
54
+ } catch (e) {
55
+ return new Response(JSON.stringify({ error: "Invalid", details: e.errors }), { status: 400 });
56
+ }
57
+ return next();
152
58
  };
153
59
  };
154
60
  export {
155
- serve,
61
+ validate,
156
62
  rateLimit,
157
63
  logger,
64
+ jwt,
158
65
  cors
159
66
  };
@@ -0,0 +1,31 @@
1
+ // @bun
2
+ // src/scheduler.ts
3
+ var cron = (pattern, task) => {
4
+ const parts = pattern.trim().split(/\s+/);
5
+ const [minute, hour, day, month, dow] = parts;
6
+ console.log(`CRON REGISTERED: ${pattern} \u2192 ${task.toString().slice(0, 50)}...`);
7
+ const check = () => {
8
+ const now = new Date;
9
+ const m = now.getMinutes();
10
+ const h = now.getHours();
11
+ const matchMinute = minute === "*" ? true : minute.includes("/") ? m % parseInt(minute.split("/")[1]) === 0 : minute.includes(",") ? minute.split(",").map(Number).includes(m) : m === parseInt(minute);
12
+ const matchHour = hour === "*" ? true : hour.includes("/") ? h % parseInt(hour.split("/")[1]) === 0 : h === parseInt(hour);
13
+ if (matchMinute && matchHour) {
14
+ console.log(`CRON TRIGGERED: ${pattern} @ ${now.toLocaleTimeString()}`);
15
+ try {
16
+ task();
17
+ } catch (e) {
18
+ console.error("CRON ERROR:", e);
19
+ }
20
+ }
21
+ };
22
+ check();
23
+ setInterval(check, 60000);
24
+ };
25
+ var openapi = (info) => {
26
+ return { openapi: "3.0.0", info, paths: {} };
27
+ };
28
+ export {
29
+ openapi,
30
+ cron
31
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "princejs",
3
- "version": "1.5.4",
3
+ "version": "1.6.0",
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",
@@ -17,9 +17,13 @@
17
17
  "import": "./dist/middleware.js",
18
18
  "types": "./dist/middleware.d.ts"
19
19
  },
20
- "./validation": {
21
- "import": "./dist/validation.js",
22
- "types": "./dist/validation.d.ts"
20
+ "./helpers": {
21
+ "import": "./dist/helpers.js",
22
+ "types": "./dist/helpers.d.ts"
23
+ },
24
+ "./scheduler": {
25
+ "import": "./dist/scheduler.js",
26
+ "types": "./dist/scheduler.d.ts"
23
27
  }
24
28
  },
25
29
  "files": [
@@ -38,7 +42,9 @@
38
42
  "server",
39
43
  "typescript",
40
44
  "websocket",
41
- "middleware"
45
+ "middleware",
46
+ "scheduler",
47
+ "fast"
42
48
  ],
43
49
  "author": "Matthew Michael (MatthewTheCoder1218)",
44
50
  "license": "MIT",
@@ -56,10 +62,12 @@
56
62
  "devDependencies": {
57
63
  "@types/bun": "^1.3.2",
58
64
  "bun-types": "latest",
59
- "typescript": "^5.9.3"
65
+ "typescript": "^5.9.3",
66
+ "fast-jwt": "^5.0.0",
67
+ "zod": "^4.1.12"
60
68
  },
61
69
  "peerDependencies": {
62
- "zod": "^4.0.0"
70
+ "zod": "^4.1.12"
63
71
  },
64
72
  "peerDependenciesMeta": {
65
73
  "zod": {
@@ -67,6 +75,6 @@
67
75
  }
68
76
  },
69
77
  "scripts": {
70
- "build": "bun build src/prince.ts --outdir dist --target bun && bun build src/middleware.ts --outdir dist --target bun && bun build src/validation.ts --outdir dist --target bun && bun build bin/create.ts --outdir dist --target bun --format esm"
78
+ "build": "bun build src/prince.ts --outdir dist --target bun && bun build src/middleware.ts --outdir dist --target bun && bun build src/helpers.ts --outdir dist --target bun && bun build bin/create.ts --outdir dist --target bun --format esm && bun build src/scheduler.ts --outdir dist --target bun && bun build src/helpers --outdir dist --target bun"
71
79
  }
72
80
  }