princejs 2.1.6 → 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 CHANGED
@@ -61,9 +61,9 @@ app.listen(3000);
61
61
 
62
62
  | Feature | Import |
63
63
  |---------|--------|
64
- | Routing, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | `princejs` |
65
- | CORS, Logger, JWT, Auth, Rate Limit, Validate, Compress, Session, API Key | `princejs/middleware` |
66
- | File Uploads, SSE, In-memory Cache | `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` |
67
67
  | Cron Scheduler | `princejs/scheduler` |
68
68
  | JSX / SSR | `princejs/jsx` |
69
69
  | SQLite Database | `princejs/db` |
@@ -155,6 +155,188 @@ app.post("/admin", (req) => {
155
155
 
156
156
  ---
157
157
 
158
+
159
+ ## 🗂️ Route Grouping
160
+
161
+ Group routes under a shared prefix with optional shared middleware. Zero overhead at request time — purely a registration convenience.
162
+
163
+ ```ts
164
+ import { prince } from "princejs";
165
+
166
+ const app = prince();
167
+
168
+ // Basic grouping
169
+ app.group("/api", (r) => {
170
+ r.get("/users", () => ({ users: [] }));
171
+ r.post("/users", (req) => ({ created: req.parsedBody }));
172
+ r.get("/users/:id", (req) => ({ id: req.params?.id }));
173
+ });
174
+ // → GET /api/users
175
+ // → POST /api/users
176
+ // → GET /api/users/:id
177
+
178
+ // With shared middleware — applies to every route in the group
179
+ import { auth } from "princejs/middleware";
180
+
181
+ app.group("/admin", auth(), (r) => {
182
+ r.get("/stats", () => ({ stats: {} }));
183
+ r.delete("/users/:id", (req) => ({ deleted: req.params?.id }));
184
+ });
185
+
186
+ // Chainable
187
+ app
188
+ .group("/v1", (r) => { r.get("/ping", () => ({ v: 1 })); })
189
+ .group("/v2", (r) => { r.get("/ping", () => ({ v: 2 })); });
190
+
191
+ app.listen(3000);
192
+ ```
193
+
194
+ ---
195
+
196
+ ## 🛡️ Secure Headers
197
+
198
+ One call sets all the security headers your production app needs:
199
+
200
+ ```ts
201
+ import { secureHeaders } from "princejs/middleware";
202
+
203
+ app.use(secureHeaders());
204
+ // Sets: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection,
205
+ // Strict-Transport-Security, Referrer-Policy
206
+
207
+ // Custom options
208
+ app.use(secureHeaders({
209
+ xFrameOptions: "DENY",
210
+ contentSecurityPolicy: "default-src 'self'",
211
+ permissionsPolicy: "camera=(), microphone=()",
212
+ strictTransportSecurity: "max-age=63072000; includeSubDomains; preload",
213
+ }));
214
+ ```
215
+
216
+ ---
217
+
218
+ ## ⏱️ Request Timeout
219
+
220
+ Kill hanging requests before they pile up:
221
+
222
+ ```ts
223
+ import { timeout } from "princejs/middleware";
224
+
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());
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 🏷️ Request ID
235
+
236
+ Attach a unique ID to every request for distributed tracing and log correlation:
237
+
238
+ ```ts
239
+ import { requestId } from "princejs/middleware";
240
+
241
+ app.use(requestId());
242
+ // → sets req.id and X-Request-ID response header
243
+
244
+ // Custom header name
245
+ app.use(requestId({ header: "X-Trace-ID" }));
246
+
247
+ // Custom generator
248
+ app.use(requestId({ generator: () => `req-${Date.now()}` }));
249
+
250
+ app.get("/", (req) => ({ requestId: req.id }));
251
+ ```
252
+
253
+ ---
254
+
255
+ ## 🚫 IP Restriction
256
+
257
+ Allow or block specific IPs:
258
+
259
+ ```ts
260
+ import { ipRestriction } from "princejs/middleware";
261
+
262
+ // Only allow these IPs
263
+ app.use(ipRestriction({ allowList: ["192.168.1.1", "10.0.0.1"] }));
264
+
265
+ // Block these IPs
266
+ app.use(ipRestriction({ denyList: ["1.2.3.4"] }));
267
+ ```
268
+
269
+ ---
270
+
271
+ ## 📁 Static Files
272
+
273
+ Serve a directory of static files. Falls through to your routes if the file doesn't exist:
274
+
275
+ ```ts
276
+ import { serveStatic } from "princejs/middleware";
277
+
278
+ app.use(serveStatic("./public"));
279
+ // → GET /logo.png serves ./public/logo.png
280
+ // → GET / serves ./public/index.html
281
+ // → GET /api/users falls through to your route handler
282
+ ```
283
+
284
+ ---
285
+
286
+ ## 🌊 Streaming
287
+
288
+ Stream chunked responses for AI/LLM output, large payloads, or anything that generates data over time:
289
+
290
+ ```ts
291
+ import { stream } from "princejs/helpers";
292
+
293
+ // Async generator — cleanest for AI token streaming
294
+ app.get("/ai", stream(async function*(req) {
295
+ yield "Hello ";
296
+ await delay(100);
297
+ yield "from ";
298
+ yield "PrinceJS!";
299
+ }));
300
+
301
+ // Async callback
302
+ app.get("/data", stream(async (req) => {
303
+ req.streamSend("chunk 1");
304
+ await fetchMoreData();
305
+ req.streamSend("chunk 2");
306
+ }));
307
+
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";
324
+
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 }));
336
+ ```
337
+
338
+ ---
339
+
158
340
  ## 📖 OpenAPI + Scalar Docs ✨
159
341
 
160
342
  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 +534,11 @@ import {
352
534
  session,
353
535
  compress,
354
536
  validate,
537
+ secureHeaders,
538
+ timeout,
539
+ requestId,
355
540
  } from "princejs/middleware";
356
- import { cache, upload, sse } from "princejs/helpers";
541
+ import { cache, upload, sse, stream } from "princejs/helpers";
357
542
  import { cron } from "princejs/scheduler";
358
543
  import { Html, Head, Body, H1, P, render } from "princejs/jsx";
359
544
  import { db } from "princejs/db";
@@ -372,6 +557,9 @@ app.onError((err, req, path, method) => {
372
557
  });
373
558
 
374
559
  // ── Global middleware ─────────────────────────────────────
560
+ app.use(secureHeaders());
561
+ app.use(requestId());
562
+ app.use(timeout(10000));
375
563
  app.use(cors());
376
564
  app.use(logger());
377
565
  app.use(rateLimit(100, 60));
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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"}