princejs 2.1.6 → 2.2.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
@@ -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 **5.1kB gzipped** — loads in ~101ms on a slow 3G connection.
31
+ > PrinceJS is **2.3× faster than Express**, matches Hono head-to-head, and sits at just **6.3kB gzipped** — loads in ~122ms on a slow 3G connection.
32
32
 
33
33
  ---
34
34
 
@@ -62,8 +62,10 @@ app.listen(3000);
62
62
  | Feature | Import |
63
63
  |---------|--------|
64
64
  | Routing, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | `princejs` |
65
+ | **Route Grouping** | `princejs` |
65
66
  | CORS, Logger, JWT, Auth, Rate Limit, Validate, Compress, Session, API Key | `princejs/middleware` |
66
- | File Uploads, SSE, In-memory Cache | `princejs/helpers` |
67
+ | **Secure Headers, Timeout, Request ID, IP Restriction, Static Files, JWKS** | `princejs/middleware` |
68
+ | File Uploads, SSE, In-memory Cache, **Streaming** | `princejs/helpers` |
67
69
  | Cron Scheduler | `princejs/scheduler` |
68
70
  | JSX / SSR | `princejs/jsx` |
69
71
  | SQLite Database | `princejs/db` |
@@ -155,6 +157,167 @@ app.post("/admin", (req) => {
155
157
 
156
158
  ---
157
159
 
160
+ ---
161
+
162
+ ## 📁 Route Grouping
163
+
164
+ Namespace routes under a shared prefix with optional shared middleware. Zero overhead at request time — just registers prefixed routes in the trie.
165
+
166
+ ```ts
167
+ // Basic grouping
168
+ app.group("/api", (r) => {
169
+ r.get("/users", () => ({ users: [] })); // → GET /api/users
170
+ r.post("/users", (req) => req.parsedBody); // → POST /api/users
171
+ r.get("/users/:id", (req) => ({ id: req.params?.id })); // → GET /api/users/:id
172
+ });
173
+
174
+ // With shared middleware — applies to every route in the group
175
+ import { auth } from "princejs/middleware";
176
+
177
+ app.group("/admin", auth(), (r) => {
178
+ r.get("/stats", () => ({ ok: true })); // → GET /admin/stats
179
+ r.delete("/user", () => ({ deleted: true })); // → DELETE /admin/user
180
+ });
181
+
182
+ // Chainable
183
+ app
184
+ .group("/v1", (r) => { r.get("/ping", () => ({ v: 1 })); })
185
+ .group("/v2", (r) => { r.get("/ping", () => ({ v: 2 })); });
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 🔐 Security Middleware
191
+
192
+ ### Secure Headers
193
+
194
+ One call sets X-Frame-Options, HSTS, X-Content-Type-Options, X-XSS-Protection, and Referrer-Policy:
195
+
196
+ ```ts
197
+ import { secureHeaders } from "princejs/middleware";
198
+
199
+ app.use(secureHeaders());
200
+
201
+ // Custom overrides
202
+ app.use(secureHeaders({
203
+ xFrameOptions: "DENY",
204
+ contentSecurityPolicy: "default-src 'self'",
205
+ permissionsPolicy: "camera=(), microphone=()",
206
+ strictTransportSecurity: "max-age=63072000; includeSubDomains; preload",
207
+ }));
208
+ ```
209
+
210
+ ### Request Timeout
211
+
212
+ Kill hanging requests before they pile up:
213
+
214
+ ```ts
215
+ import { timeout } from "princejs/middleware";
216
+
217
+ app.use(timeout(5000)); // 408 after 5s
218
+ app.use(timeout(3000, "Gateway Timeout")); // custom message
219
+ ```
220
+
221
+ ### Request ID
222
+
223
+ Attach a unique ID to every request for distributed tracing and log correlation:
224
+
225
+ ```ts
226
+ import { requestId } from "princejs/middleware";
227
+
228
+ app.use(requestId());
229
+ // → sets req.id and X-Request-ID response header
230
+
231
+ // Custom header or generator
232
+ app.use(requestId({
233
+ header: "X-Trace-ID",
234
+ generator: () => `req-${Date.now()}`,
235
+ }));
236
+
237
+ app.get("/", (req) => ({ requestId: req.id }));
238
+ ```
239
+
240
+ ### IP Restriction
241
+
242
+ Allow or deny specific IPs:
243
+
244
+ ```ts
245
+ import { ipRestriction } from "princejs/middleware";
246
+
247
+ // Only allow these IPs
248
+ app.use(ipRestriction({ allowList: ["192.168.1.1", "10.0.0.1"] }));
249
+
250
+ // Block specific IPs
251
+ app.use(ipRestriction({ denyList: ["1.2.3.4"] }));
252
+ ```
253
+
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
+ ---
272
+
273
+ ## 📂 Static Files
274
+
275
+ Serve a directory of static files. Falls through to your routes if the file doesn't exist:
276
+
277
+ ```ts
278
+ import { serveStatic } from "princejs/middleware";
279
+
280
+ app.use(serveStatic("./public"));
281
+
282
+ // Your API routes still work normally
283
+ app.get("/api/users", () => ({ users: [] }));
284
+ ```
285
+
286
+ ---
287
+
288
+ ## 🌊 Streaming
289
+
290
+ Stream responses chunk by chunk — perfect for AI/LLM token output:
291
+
292
+ ```ts
293
+ import { stream } from "princejs/helpers";
294
+
295
+ // Async generator (cleanest for AI output)
296
+ app.get("/ai", stream(async function*(req) {
297
+ yield "Hello ";
298
+ yield "from ";
299
+ yield "PrinceJS!";
300
+ }));
301
+
302
+ // Callback style
303
+ app.get("/stream", stream((req) => {
304
+ req.streamSend("chunk 1");
305
+ req.streamSend("chunk 2");
306
+ }));
307
+
308
+ // Async callback
309
+ app.get("/slow-stream", stream(async (req) => {
310
+ req.streamSend("Starting...");
311
+ await fetch("https://api.openai.com/v1/chat/completions", { /* ... */ })
312
+ .then(res => res.body?.pipeTo(new WritableStream({
313
+ write(chunk) { req.streamSend(chunk); }
314
+ })));
315
+ }));
316
+
317
+ // Custom content type
318
+ app.get("/binary", stream(() => { /* ... */ }, { contentType: "application/octet-stream" }));
319
+ ```
320
+
158
321
  ## 📖 OpenAPI + Scalar Docs ✨
159
322
 
160
323
  Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI — all from a single `app.openapi()` call.
@@ -352,8 +515,11 @@ import {
352
515
  session,
353
516
  compress,
354
517
  validate,
518
+ secureHeaders,
519
+ timeout,
520
+ requestId,
355
521
  } from "princejs/middleware";
356
- import { cache, upload, sse } from "princejs/helpers";
522
+ import { cache, upload, sse, stream } from "princejs/helpers";
357
523
  import { cron } from "princejs/scheduler";
358
524
  import { Html, Head, Body, H1, P, render } from "princejs/jsx";
359
525
  import { db } from "princejs/db";
@@ -372,6 +538,9 @@ app.onError((err, req, path, method) => {
372
538
  });
373
539
 
374
540
  // ── Global middleware ─────────────────────────────────────
541
+ app.use(secureHeaders());
542
+ app.use(requestId());
543
+ app.use(timeout(10000));
375
544
  app.use(cors());
376
545
  app.use(logger());
377
546
  app.use(rateLimit(100, 60));
@@ -431,6 +600,17 @@ app.post(
431
600
  (req) => ({ created: req.parsedBody })
432
601
  );
433
602
 
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
+
434
614
  // ── Cron ──────────────────────────────────────────────────
435
615
  cron("* * * * *", () => console.log("💓 heartbeat"));
436
616
 
@@ -492,7 +672,7 @@ bun test
492
672
 
493
673
  <div align="center">
494
674
 
495
- **PrinceJS: 5.1kB. Hono-speed. Everything included. 👑**
675
+ **PrinceJS: 6.3kB. Hono-speed. Everything included. 👑**
496
676
 
497
677
  *Built with ❤️ in Nigeria*
498
678
 
package/dist/helpers.d.ts CHANGED
@@ -3,4 +3,7 @@ export declare const cache: (ttl: number) => (handler: any) => (req: PrinceReque
3
3
  export declare const email: (to: string, subject: string, html: string) => Promise<void>;
4
4
  export declare const upload: () => (req: PrinceRequest) => Promise<Response>;
5
5
  export declare const sse: () => (req: PrinceRequest) => Response;
6
+ export declare const stream: (handler: (req: PrinceRequest) => AsyncGenerator<string | Uint8Array, void, unknown> | void | Promise<void>, options?: {
7
+ contentType?: string;
8
+ }) => (req: PrinceRequest) => Response;
6
9
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,MAEvB,SAAS,GAAG,MAAY,KAAK,aAAa,iBASnD,CAAC;AAGF,eAAO,MAAM,KAAK,GAAU,IAAI,MAAM,EAAE,SAAS,MAAM,EAAE,MAAM,MAAM,kBAMpE,CAAC;AAGF,eAAO,MAAM,MAAM,SACH,KAAK,aAAa,sBA4CjC,CAAC;AAGF,eAAO,MAAM,GAAG,SACN,KAAK,aAAa,aA0B3B,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,MAEvB,SAAS,GAAG,MAAY,KAAK,aAAa,iBASnD,CAAC;AAGF,eAAO,MAAM,KAAK,GAAU,IAAI,MAAM,EAAE,SAAS,MAAM,EAAE,MAAM,MAAM,kBAMpE,CAAC;AAGF,eAAO,MAAM,MAAM,SACH,KAAK,aAAa,sBA4CjC,CAAC;AAGF,eAAO,MAAM,GAAG,SACN,KAAK,aAAa,aA0B3B,CAAC;AAgBF,eAAO,MAAM,MAAM,GACjB,SAAS,CAAC,GAAG,EAAE,aAAa,KAAK,cAAc,CAAC,MAAM,GAAG,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC1G,UAAU;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,MAK1B,KAAK,aAAa,aAmD3B,CAAC"}
package/dist/helpers.js CHANGED
@@ -81,8 +81,64 @@ var sse = () => {
81
81
  });
82
82
  };
83
83
  };
84
+ var stream = (handler, options) => {
85
+ const contentType = options?.contentType ?? "text/plain; charset=utf-8";
86
+ const enc = new TextEncoder;
87
+ return (req) => {
88
+ let resolveController;
89
+ const controllerReady = new Promise((res) => {
90
+ resolveController = res;
91
+ });
92
+ const readable = new ReadableStream({
93
+ start(c) {
94
+ resolveController(c);
95
+ }
96
+ });
97
+ controllerReady.then((controller) => {
98
+ const enqueue = (chunk) => controller.enqueue(typeof chunk === "string" ? enc.encode(chunk) : chunk);
99
+ req.streamSend = enqueue;
100
+ req.streamClose = () => controller.close();
101
+ req.streamError = (e) => controller.error(e);
102
+ const result = handler(req);
103
+ if (result && typeof result[Symbol.asyncIterator] === "function") {
104
+ (async () => {
105
+ try {
106
+ for await (const chunk of result) {
107
+ enqueue(chunk);
108
+ }
109
+ controller.close();
110
+ } catch (e) {
111
+ controller.error(e);
112
+ }
113
+ })();
114
+ } else if (result instanceof Promise) {
115
+ result.then(() => {
116
+ try {
117
+ controller.close();
118
+ } catch {}
119
+ }).catch((e) => {
120
+ try {
121
+ controller.error(e);
122
+ } catch {}
123
+ });
124
+ } else {
125
+ try {
126
+ controller.close();
127
+ } catch {}
128
+ }
129
+ });
130
+ return new Response(readable, {
131
+ headers: {
132
+ "Content-Type": contentType,
133
+ "Transfer-Encoding": "chunked",
134
+ "X-Content-Type-Options": "nosniff"
135
+ }
136
+ });
137
+ };
138
+ };
84
139
  export {
85
140
  upload,
141
+ stream,
86
142
  sse,
87
143
  email,
88
144
  cache
@@ -35,5 +35,27 @@ export declare const session: (options: {
35
35
  maxAge?: number;
36
36
  name?: string;
37
37
  }) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
38
+ export declare const secureHeaders: (options?: {
39
+ xFrameOptions?: string;
40
+ xContentTypeOptions?: boolean;
41
+ xXssProtection?: string;
42
+ strictTransportSecurity?: string;
43
+ referrerPolicy?: string;
44
+ permissionsPolicy?: string;
45
+ contentSecurityPolicy?: string;
46
+ }) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
47
+ export declare const timeout: (ms: number, message?: string) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
48
+ export declare const requestId: (options?: {
49
+ header?: string;
50
+ generator?: () => string;
51
+ }) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
52
+ export declare const ipRestriction: (options: {
53
+ allowList?: string[];
54
+ denyList?: string[];
55
+ }) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
56
+ export declare const serveStatic: (root: string) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
57
+ export declare const jwks: (jwksUrl: string, options?: {
58
+ algorithms?: string[];
59
+ }) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
38
60
  export {};
39
61
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;AAGhD,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,GAAG,EAAE,aAAa,CAAC;QACnB,GAAG,CAAC,EAAE,QAAQ,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,GAAG,CAAC;KACb,KAAK,IAAI,CAAC;CACZ;AAED,eAAO,MAAM,MAAM,GAAI,UAAS,aAAkB,MASlC,KAAK,aAAa,EAAE,MAAM,IAAI,kCAqD7C,CAAC;AAGF,eAAO,MAAM,IAAI,GAAI,SAAQ,MAAY,MACzB,KAAK,GAAG,EAAE,MAAM,QAAQ,iBA+BvC,CAAC;AAGF,eAAO,MAAM,OAAO,GAAU,SAAS,GAAG,EAAE,QAAQ,UAAU,EAAE,WAAW,MAAM,oBAQhF,CAAC;AAEF,eAAO,MAAM,GAAG,GAAI,KAAK,UAAU,MACnB,KAAK,aAAa,EAAE,MAAM,IAAI,kCAuB7C,CAAC;AAGF,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,eAAW,MAGlC,KAAK,aAAa,EAAE,MAAM,IAAI,kCAqC7C,CAAC;AAGF,eAAO,MAAM,QAAQ,GAAI,QAAQ,CAAC,CAAC,SAAS,MAC5B,KAAK,GAAG,EAAE,MAAM,QAAQ,iBAyEvC,CAAC;AAGF,eAAO,MAAM,IAAI,GAAI,UAAU;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,MACnC,KAAK,aAAa,EAAE,MAAM,IAAI,kCAwB7C,CAAC;AAGF,eAAO,MAAM,MAAM,GAAI,SAAS;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,MAInD,KAAK,aAAa,EAAE,MAAM,IAAI,kCAa7C,CAAC;AAGF,eAAO,MAAM,QAAQ,GAAI,UAAU;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;CAC1C,MAIe,KAAK,aAAa,EAAE,MAAM,IAAI,kCAqC7C,CAAC;AAGF,eAAO,MAAM,OAAO,GAAI,SAAS;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,MAce,KAAK,aAAa,EAAE,MAAM,IAAI,kCA+C7C,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}