princejs 2.2.0 → 2.2.1
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 +86 -78
- package/package.json +1 -1
package/Readme.md
CHANGED
|
@@ -28,7 +28,7 @@ Benchmarked with `oha -c 100 -z 30s` on Windows 10:
|
|
|
28
28
|
| Fastify | 15,519 | 16,434 |
|
|
29
29
|
| Express | 13,138 | 13,458 |
|
|
30
30
|
|
|
31
|
-
> PrinceJS is **2.3× faster than Express**, matches Hono head-to-head, and sits at just **
|
|
31
|
+
> PrinceJS is **2.3× faster than Express**, matches Hono head-to-head, and sits at just **5.1kB gzipped** — loads in ~101ms on a slow 3G connection.
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
@@ -61,11 +61,9 @@ app.listen(3000);
|
|
|
61
61
|
|
|
62
62
|
| Feature | Import |
|
|
63
63
|
|---------|--------|
|
|
64
|
-
| Routing, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | `princejs` |
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
| **Secure Headers, Timeout, Request ID, IP Restriction, Static Files, JWKS** | `princejs/middleware` |
|
|
68
|
-
| File Uploads, SSE, In-memory Cache, **Streaming** | `princejs/helpers` |
|
|
64
|
+
| Routing, Route Grouping, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | `princejs` |
|
|
65
|
+
| CORS, Logger, JWT, JWKS, Auth, Rate Limit, Validate, Compress, Session, API Key, Secure Headers, Timeout, Request ID, IP Restriction, Static Files | `princejs/middleware` |
|
|
66
|
+
| File Uploads, SSE, Streaming, In-memory Cache | `princejs/helpers` |
|
|
69
67
|
| Cron Scheduler | `princejs/scheduler` |
|
|
70
68
|
| JSX / SSR | `princejs/jsx` |
|
|
71
69
|
| SQLite Database | `princejs/db` |
|
|
@@ -157,48 +155,56 @@ app.post("/admin", (req) => {
|
|
|
157
155
|
|
|
158
156
|
---
|
|
159
157
|
|
|
160
|
-
---
|
|
161
158
|
|
|
162
|
-
##
|
|
159
|
+
## 🗂️ Route Grouping
|
|
163
160
|
|
|
164
|
-
|
|
161
|
+
Group routes under a shared prefix with optional shared middleware. Zero overhead at request time — purely a registration convenience.
|
|
165
162
|
|
|
166
163
|
```ts
|
|
164
|
+
import { prince } from "princejs";
|
|
165
|
+
|
|
166
|
+
const app = prince();
|
|
167
|
+
|
|
167
168
|
// Basic grouping
|
|
168
169
|
app.group("/api", (r) => {
|
|
169
|
-
r.get("/users", () => ({ users: [] }));
|
|
170
|
-
r.post("/users", (req) => req.parsedBody);
|
|
171
|
-
r.get("/users/:id", (req) => ({ id: req.params?.id }));
|
|
170
|
+
r.get("/users", () => ({ users: [] }));
|
|
171
|
+
r.post("/users", (req) => ({ created: req.parsedBody }));
|
|
172
|
+
r.get("/users/:id", (req) => ({ id: req.params?.id }));
|
|
172
173
|
});
|
|
174
|
+
// → GET /api/users
|
|
175
|
+
// → POST /api/users
|
|
176
|
+
// → GET /api/users/:id
|
|
173
177
|
|
|
174
178
|
// With shared middleware — applies to every route in the group
|
|
175
179
|
import { auth } from "princejs/middleware";
|
|
176
180
|
|
|
177
181
|
app.group("/admin", auth(), (r) => {
|
|
178
|
-
r.get("/stats", () => ({
|
|
179
|
-
r.delete("/
|
|
182
|
+
r.get("/stats", () => ({ stats: {} }));
|
|
183
|
+
r.delete("/users/:id", (req) => ({ deleted: req.params?.id }));
|
|
180
184
|
});
|
|
181
185
|
|
|
182
186
|
// Chainable
|
|
183
187
|
app
|
|
184
188
|
.group("/v1", (r) => { r.get("/ping", () => ({ v: 1 })); })
|
|
185
189
|
.group("/v2", (r) => { r.get("/ping", () => ({ v: 2 })); });
|
|
190
|
+
|
|
191
|
+
app.listen(3000);
|
|
186
192
|
```
|
|
187
193
|
|
|
188
194
|
---
|
|
189
195
|
|
|
190
|
-
##
|
|
196
|
+
## 🛡️ Secure Headers
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
One call sets X-Frame-Options, HSTS, X-Content-Type-Options, X-XSS-Protection, and Referrer-Policy:
|
|
198
|
+
One call sets all the security headers your production app needs:
|
|
195
199
|
|
|
196
200
|
```ts
|
|
197
201
|
import { secureHeaders } from "princejs/middleware";
|
|
198
202
|
|
|
199
203
|
app.use(secureHeaders());
|
|
204
|
+
// Sets: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection,
|
|
205
|
+
// Strict-Transport-Security, Referrer-Policy
|
|
200
206
|
|
|
201
|
-
// Custom
|
|
207
|
+
// Custom options
|
|
202
208
|
app.use(secureHeaders({
|
|
203
209
|
xFrameOptions: "DENY",
|
|
204
210
|
contentSecurityPolicy: "default-src 'self'",
|
|
@@ -207,18 +213,25 @@ app.use(secureHeaders({
|
|
|
207
213
|
}));
|
|
208
214
|
```
|
|
209
215
|
|
|
210
|
-
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## ⏱️ Request Timeout
|
|
211
219
|
|
|
212
220
|
Kill hanging requests before they pile up:
|
|
213
221
|
|
|
214
222
|
```ts
|
|
215
223
|
import { timeout } from "princejs/middleware";
|
|
216
224
|
|
|
217
|
-
app.use(timeout(5000));
|
|
218
|
-
app.use(timeout(3000, "
|
|
225
|
+
app.use(timeout(5000)); // 5 second global timeout → 408
|
|
226
|
+
app.use(timeout(3000, "Slow!")); // custom message
|
|
227
|
+
|
|
228
|
+
// Per-route timeout
|
|
229
|
+
app.get("/heavy", timeout(10000), (req) => heavyOperation());
|
|
219
230
|
```
|
|
220
231
|
|
|
221
|
-
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 🏷️ Request ID
|
|
222
235
|
|
|
223
236
|
Attach a unique ID to every request for distributed tracing and log correlation:
|
|
224
237
|
|
|
@@ -228,18 +241,20 @@ import { requestId } from "princejs/middleware";
|
|
|
228
241
|
app.use(requestId());
|
|
229
242
|
// → sets req.id and X-Request-ID response header
|
|
230
243
|
|
|
231
|
-
// Custom header
|
|
232
|
-
app.use(requestId({
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}));
|
|
244
|
+
// Custom header name
|
|
245
|
+
app.use(requestId({ header: "X-Trace-ID" }));
|
|
246
|
+
|
|
247
|
+
// Custom generator
|
|
248
|
+
app.use(requestId({ generator: () => `req-${Date.now()}` }));
|
|
236
249
|
|
|
237
250
|
app.get("/", (req) => ({ requestId: req.id }));
|
|
238
251
|
```
|
|
239
252
|
|
|
240
|
-
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 🚫 IP Restriction
|
|
241
256
|
|
|
242
|
-
Allow or
|
|
257
|
+
Allow or block specific IPs:
|
|
243
258
|
|
|
244
259
|
```ts
|
|
245
260
|
import { ipRestriction } from "princejs/middleware";
|
|
@@ -247,30 +262,13 @@ import { ipRestriction } from "princejs/middleware";
|
|
|
247
262
|
// Only allow these IPs
|
|
248
263
|
app.use(ipRestriction({ allowList: ["192.168.1.1", "10.0.0.1"] }));
|
|
249
264
|
|
|
250
|
-
// Block
|
|
265
|
+
// Block these IPs
|
|
251
266
|
app.use(ipRestriction({ denyList: ["1.2.3.4"] }));
|
|
252
267
|
```
|
|
253
268
|
|
|
254
|
-
### JWKS — Auth0, Clerk, Supabase
|
|
255
|
-
|
|
256
|
-
Verify JWTs against a remote JWKS endpoint — no symmetric key needed:
|
|
257
|
-
|
|
258
|
-
```ts
|
|
259
|
-
import { jwks } from "princejs/middleware";
|
|
260
|
-
|
|
261
|
-
// Auth0
|
|
262
|
-
app.use(jwks("https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json"));
|
|
263
|
-
|
|
264
|
-
// Clerk
|
|
265
|
-
app.use(jwks("https://YOUR_CLERK_DOMAIN/.well-known/jwks.json"));
|
|
266
|
-
|
|
267
|
-
// Keys are cached automatically — only fetched on rotation
|
|
268
|
-
app.get("/protected", auth(), (req) => ({ user: req.user }));
|
|
269
|
-
```
|
|
270
|
-
|
|
271
269
|
---
|
|
272
270
|
|
|
273
|
-
##
|
|
271
|
+
## 📁 Static Files
|
|
274
272
|
|
|
275
273
|
Serve a directory of static files. Falls through to your routes if the file doesn't exist:
|
|
276
274
|
|
|
@@ -278,46 +276,67 @@ Serve a directory of static files. Falls through to your routes if the file does
|
|
|
278
276
|
import { serveStatic } from "princejs/middleware";
|
|
279
277
|
|
|
280
278
|
app.use(serveStatic("./public"));
|
|
281
|
-
|
|
282
|
-
//
|
|
283
|
-
|
|
279
|
+
// → GET /logo.png serves ./public/logo.png
|
|
280
|
+
// → GET / serves ./public/index.html
|
|
281
|
+
// → GET /api/users falls through to your route handler
|
|
284
282
|
```
|
|
285
283
|
|
|
286
284
|
---
|
|
287
285
|
|
|
288
286
|
## 🌊 Streaming
|
|
289
287
|
|
|
290
|
-
Stream responses
|
|
288
|
+
Stream chunked responses for AI/LLM output, large payloads, or anything that generates data over time:
|
|
291
289
|
|
|
292
290
|
```ts
|
|
293
291
|
import { stream } from "princejs/helpers";
|
|
294
292
|
|
|
295
|
-
// Async generator
|
|
293
|
+
// Async generator — cleanest for AI token streaming
|
|
296
294
|
app.get("/ai", stream(async function*(req) {
|
|
297
295
|
yield "Hello ";
|
|
296
|
+
await delay(100);
|
|
298
297
|
yield "from ";
|
|
299
298
|
yield "PrinceJS!";
|
|
300
299
|
}));
|
|
301
300
|
|
|
302
|
-
//
|
|
303
|
-
app.get("/
|
|
301
|
+
// Async callback
|
|
302
|
+
app.get("/data", stream(async (req) => {
|
|
304
303
|
req.streamSend("chunk 1");
|
|
304
|
+
await fetchMoreData();
|
|
305
305
|
req.streamSend("chunk 2");
|
|
306
306
|
}));
|
|
307
307
|
|
|
308
|
-
//
|
|
309
|
-
app.get("/
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
308
|
+
// Custom content type for binary or JSON streams
|
|
309
|
+
app.get("/events", stream(async function*(req) {
|
|
310
|
+
for (const item of items) {
|
|
311
|
+
yield JSON.stringify(item) + "\n";
|
|
312
|
+
}
|
|
313
|
+
}, { contentType: "application/x-ndjson" }));
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 🔑 JWKS / Third-Party Auth
|
|
319
|
+
|
|
320
|
+
Verify JWTs from Auth0, Clerk, Supabase, or any JWKS endpoint — no symmetric key needed:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { jwks } from "princejs/middleware";
|
|
316
324
|
|
|
317
|
-
//
|
|
318
|
-
app.
|
|
325
|
+
// Auth0
|
|
326
|
+
app.use(jwks("https://your-domain.auth0.com/.well-known/jwks.json"));
|
|
327
|
+
|
|
328
|
+
// Clerk
|
|
329
|
+
app.use(jwks("https://your-clerk-domain.clerk.accounts.dev/.well-known/jwks.json"));
|
|
330
|
+
|
|
331
|
+
// Supabase
|
|
332
|
+
app.use(jwks("https://your-project.supabase.co/auth/v1/.well-known/jwks.json"));
|
|
333
|
+
|
|
334
|
+
// req.user is set after verification, same as jwt()
|
|
335
|
+
app.get("/protected", auth(), (req) => ({ user: req.user }));
|
|
319
336
|
```
|
|
320
337
|
|
|
338
|
+
---
|
|
339
|
+
|
|
321
340
|
## 📖 OpenAPI + Scalar Docs ✨
|
|
322
341
|
|
|
323
342
|
Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI — all from a single `app.openapi()` call.
|
|
@@ -600,17 +619,6 @@ app.post(
|
|
|
600
619
|
(req) => ({ created: req.parsedBody })
|
|
601
620
|
);
|
|
602
621
|
|
|
603
|
-
// ── Route groups ─────────────────────────────────────────
|
|
604
|
-
app.group("/v1", (r) => {
|
|
605
|
-
r.get("/status", () => ({ version: 1, ok: true }));
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// ── Streaming ─────────────────────────────────────────────
|
|
609
|
-
app.get("/ai", stream(async function*() {
|
|
610
|
-
yield "Hello ";
|
|
611
|
-
yield "World!";
|
|
612
|
-
}));
|
|
613
|
-
|
|
614
622
|
// ── Cron ──────────────────────────────────────────────────
|
|
615
623
|
cron("* * * * *", () => console.log("💓 heartbeat"));
|
|
616
624
|
|
|
@@ -672,7 +680,7 @@ bun test
|
|
|
672
680
|
|
|
673
681
|
<div align="center">
|
|
674
682
|
|
|
675
|
-
**PrinceJS:
|
|
683
|
+
**PrinceJS: 5.1kB. Hono-speed. Everything included. 👑**
|
|
676
684
|
|
|
677
685
|
*Built with ❤️ in Nigeria*
|
|
678
686
|
|
package/package.json
CHANGED