@coderbuzz/ken 0.1.0 → 0.1.2
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 +787 -25
- package/dist/README.md +787 -25
- package/dist/package.json +3 -2
- package/package.json +2 -1
package/dist/README.md
CHANGED
|
@@ -4,38 +4,800 @@
|
|
|
4
4
|
|
|
5
5
|
### The Modern Standard for TypeScript Backends.
|
|
6
6
|
|
|
7
|
-
Ken is a
|
|
8
|
-
Node.js, Deno, and Bun. It focuses on clarity, speed, and a clean
|
|
9
|
-
experience without unnecessary abstraction.
|
|
7
|
+
Ken is a next-generation TypeScript backend framework designed to run seamlessly
|
|
8
|
+
across Node.js, Deno, and Bun. It focuses on clarity, speed, and a clean
|
|
9
|
+
developer experience without unnecessary abstraction.
|
|
10
10
|
|
|
11
|
-
Ken is
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
code.
|
|
11
|
+
Built entirely with TypeScript, Ken is minimalist, high-performance, and fully
|
|
12
|
+
type-safe. Designed for the modern JavaScript ecosystem, it ensures your
|
|
13
|
+
application runs consistently across Node.js, Deno, and Bun. Write once, deploy
|
|
14
|
+
everywhere, and unleash the true potential of your server-side code.
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
Engineered from the ground up to deliver unparalleled developer experience (DX)
|
|
17
|
+
and superior performance, Ken leverages the power of modern TypeScript and a
|
|
18
|
+
fluent, modular architecture to help developers build scalable APIs and services
|
|
19
|
+
with absolute type safety — with full type inference, schema validation, and
|
|
20
|
+
built-in WebSocket support.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
<!-- Ken is a minimalist, high-performance, type-safe backend framework for
|
|
23
|
+
TypeScript. It runs natively on Node.js, Deno, and Bun — write once, deploy
|
|
24
|
+
anywhere — with full type inference, schema validation, and built-in WebSocket
|
|
25
|
+
support. -->
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
without code changes.
|
|
27
|
+
### Key Features
|
|
27
28
|
|
|
28
|
-
-
|
|
29
|
-
|
|
29
|
+
- **Runtime Agnostic**: Seamlessly run your application on Node.js, Deno, and
|
|
30
|
+
Bun without code changes.
|
|
31
|
+
- **TypeScript Native**: Full type-checking and autocompletion support out of
|
|
32
|
+
the box.
|
|
33
|
+
- **Schema Validation**: Validate request params, query, headers, cookies, and
|
|
34
|
+
body with inline schemas.
|
|
35
|
+
- **Built-in Middleware**: JWT, CORS, sessions, compression, rate limiting,
|
|
36
|
+
secure headers, and more.
|
|
37
|
+
- **WebSocket Support**: Real-time connections with pub/sub, ping/pong, and
|
|
38
|
+
typed upgrade data.
|
|
39
|
+
- **Performance-Driven**: Minimal overhead engineered for high throughput and
|
|
40
|
+
low latency.
|
|
41
|
+
- **Modular & Extensible**: Easily integrate with existing libraries and scale
|
|
42
|
+
complexity as needed.
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
latency.
|
|
44
|
+
---
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
# Bun
|
|
50
|
+
bun add @coderbuzz/ken
|
|
51
|
+
|
|
52
|
+
# npm
|
|
53
|
+
npm install @coderbuzz/ken
|
|
54
|
+
|
|
55
|
+
# Deno
|
|
56
|
+
import { AppServer } from "npm:@coderbuzz/ken";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For Node.js, also install a peer for ESM compatibility:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
npm install tsx
|
|
63
|
+
node --import tsx/esm server.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { AppServer } from "@coderbuzz/ken";
|
|
72
|
+
|
|
73
|
+
const app = new AppServer({ port: 3000 });
|
|
74
|
+
|
|
75
|
+
app.get("/", "Hello, Ken!");
|
|
76
|
+
|
|
77
|
+
const { hostname, port } = await app.run();
|
|
78
|
+
console.log(`Listening on ${hostname}:${port}`);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Routing
|
|
84
|
+
|
|
85
|
+
### Static Routes
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
app.get("/", "Hello Ken!");
|
|
89
|
+
app.get("/health", "OK");
|
|
90
|
+
app.get("/version", { version: "1.0.0" }); // plain objects are JSON-serialized
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Dynamic Params
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
app.get("/users/:id", (ctx) => new Response(`User ${ctx.params.id}`));
|
|
97
|
+
|
|
98
|
+
app.get(
|
|
99
|
+
"/posts/:postId/comments/:commentId",
|
|
100
|
+
(ctx) =>
|
|
101
|
+
new Response(`Post ${ctx.params.postId}, Comment ${ctx.params.commentId}`),
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Optional Params & Wildcards
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
app.get(
|
|
109
|
+
"/optional/:id?",
|
|
110
|
+
(ctx) => new Response(`ID: ${ctx.params.id ?? "none"}`),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
app.get("/files/*", (ctx) => new Response(`File: ${ctx.params["*"]}`));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### HTTP Methods
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
app.post("/items", handler);
|
|
120
|
+
app.put("/items/:id", handler);
|
|
121
|
+
app.patch("/items/:id", handler);
|
|
122
|
+
app.delete("/items/:id", handler);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Schema Validation
|
|
128
|
+
|
|
129
|
+
Ken validates request data inline via the schema object (first argument before
|
|
130
|
+
the handler). Use
|
|
131
|
+
[`@coderbuzz/kyo`](https://www.npmjs.com/package/@coderbuzz/kyo) for schema
|
|
132
|
+
builders.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {
|
|
136
|
+
boolean,
|
|
137
|
+
coerce,
|
|
138
|
+
number,
|
|
139
|
+
object,
|
|
140
|
+
optional,
|
|
141
|
+
string,
|
|
142
|
+
} from "@coderbuzz/kyo";
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Params
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
app.get("/products/:id", {
|
|
149
|
+
params: { id: coerce(number()) },
|
|
150
|
+
}, (ctx) => Response.json({ productId: ctx.params.id }));
|
|
151
|
+
// ctx.params.id is typed as number
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Query
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
app.get("/search", {
|
|
158
|
+
query: {
|
|
159
|
+
q: string({ min: 1 }),
|
|
160
|
+
page: coerce(number({ min: 1, max: 100 })),
|
|
161
|
+
limit: optional(coerce(number({ min: 10, max: 100 }))),
|
|
162
|
+
},
|
|
163
|
+
}, (ctx) => Response.json({ search: ctx.query.q, page: ctx.query.page }));
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Headers
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
app.get("/api/resource", {
|
|
170
|
+
headers: { "x-api-key": string({ min: 10 }) },
|
|
171
|
+
}, (ctx) => Response.json({ key: ctx.headers["x-api-key"] }));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Cookies
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
app.get(
|
|
178
|
+
"/api/profile",
|
|
179
|
+
{
|
|
180
|
+
cookies: {
|
|
181
|
+
sessionId: string({ min: 5 }),
|
|
182
|
+
premium: optional(coerce(boolean())),
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
(ctx) =>
|
|
186
|
+
Response.json({
|
|
187
|
+
session: ctx.cookies.sessionId,
|
|
188
|
+
isPremium: ctx.cookies.premium,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### JSON Body
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
app.post("/api/users", {
|
|
197
|
+
json: object({
|
|
198
|
+
name: string({ min: 2 }),
|
|
199
|
+
age: number({ min: 18 }),
|
|
200
|
+
active: boolean(),
|
|
201
|
+
email: optional(string()),
|
|
202
|
+
}),
|
|
203
|
+
}, async (ctx) => {
|
|
204
|
+
const body = await ctx.json;
|
|
205
|
+
return Response.json({ name: body.name, age: body.age });
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Text Body
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
app.post("/api/echo", {
|
|
213
|
+
text: string({ min: 5 }),
|
|
214
|
+
}, async (ctx) => new Response(await ctx.text));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Form Body
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
app.post("/api/submit", {
|
|
221
|
+
form: { field: string({ min: 3 }) },
|
|
222
|
+
}, async (ctx) => {
|
|
223
|
+
const data = await ctx.form;
|
|
224
|
+
return new Response(data.field);
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Middleware & State
|
|
231
|
+
|
|
232
|
+
Middleware runs before the handler and returns typed state accessible via
|
|
233
|
+
`ctx.state`.
|
|
234
|
+
|
|
235
|
+
### Per-Route State
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
app.get("/protected", {
|
|
239
|
+
state: {
|
|
240
|
+
auth: (ctx) => {
|
|
241
|
+
const token = ctx.headers["authorization"];
|
|
242
|
+
if (token !== "Bearer valid-token") {
|
|
243
|
+
throw new Response("Unauthorized", { status: 401 });
|
|
244
|
+
}
|
|
245
|
+
return { userId: "user123", role: "admin" };
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
}, (ctx) => Response.json({ user: ctx.state.auth.userId }));
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Async State
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
app.get("/data", {
|
|
255
|
+
state: {
|
|
256
|
+
data: async () => {
|
|
257
|
+
const result = await fetchFromDb();
|
|
258
|
+
return result;
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
}, (ctx) => Response.json(ctx.state.data));
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Multiple Guards
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
app.post("/admin/action", {
|
|
268
|
+
state: {
|
|
269
|
+
auth: (ctx) => {
|
|
270
|
+
const token = ctx.headers["authorization"];
|
|
271
|
+
if (!token) throw new Response("Unauthorized", { status: 401 });
|
|
272
|
+
return { userId: "admin1", role: "admin" };
|
|
273
|
+
},
|
|
274
|
+
permission: (ctx) => {
|
|
275
|
+
if ((ctx.state as any).auth.role !== "admin") {
|
|
276
|
+
throw new Response("Forbidden", { status: 403 });
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
}, () => Response.json({ message: "Action performed" }));
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### `onFinish` Callback
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
app.get("/with-logging", {
|
|
287
|
+
state: {
|
|
288
|
+
logger: (ctx) => {
|
|
289
|
+
const start = Date.now();
|
|
290
|
+
ctx.onFinish((resp) => {
|
|
291
|
+
console.log(
|
|
292
|
+
`${ctx.method} ${ctx.url} - ${resp?.status} - ${
|
|
293
|
+
Date.now() - start
|
|
294
|
+
}ms`,
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
}, () => new Response("logged"));
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `define()` — Scoped Middleware
|
|
303
|
+
|
|
304
|
+
Apply middleware to a group of routes with full type inference:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
app.define(
|
|
308
|
+
{
|
|
309
|
+
userId: (ctx) => ctx.headers["x-user-id"] || "guest",
|
|
310
|
+
isAdmin: (ctx) => ctx.headers["x-role"] === "admin",
|
|
311
|
+
},
|
|
312
|
+
(app) => {
|
|
313
|
+
app.get(
|
|
314
|
+
"/me",
|
|
315
|
+
(ctx) =>
|
|
316
|
+
Response.json({ userId: ctx.state.userId, isAdmin: ctx.state.isAdmin }),
|
|
317
|
+
);
|
|
318
|
+
app.get("/dashboard", (ctx) => {
|
|
319
|
+
if (!ctx.state.isAdmin) throw new Response("Forbidden", { status: 403 });
|
|
320
|
+
return Response.json({ admin: true });
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `apply()` — Global Middleware
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
// Side-effect middleware (logging, metrics)
|
|
330
|
+
app.apply("/*", (ctx) => {
|
|
331
|
+
console.log(ctx.method, ctx.url);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// State-producing middleware
|
|
335
|
+
app.apply("/*", { auth: (ctx) => verifyAuth(ctx) });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### `use()` — Mount Sub-Apps
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
const api = new App();
|
|
342
|
+
api.get("/users", handler);
|
|
343
|
+
api.get("/posts", handler);
|
|
344
|
+
|
|
345
|
+
app.use("/api/v1", api);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Cookies
|
|
351
|
+
|
|
352
|
+
### Reading Cookies
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
app.get("/profile", (ctx) => {
|
|
356
|
+
return Response.json({ theme: ctx.cookies.theme });
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Setting Cookies
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
app.get("/login", (ctx) => {
|
|
364
|
+
ctx.setCookie("session", "abc123", {
|
|
365
|
+
path: "/",
|
|
366
|
+
httpOnly: true,
|
|
367
|
+
secure: true,
|
|
368
|
+
sameSite: "Strict",
|
|
369
|
+
maxAge: 3600,
|
|
370
|
+
});
|
|
371
|
+
return new Response("Logged in");
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Error Handling
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
// Custom app-level error handler
|
|
381
|
+
app.onError((error, ctx) => {
|
|
382
|
+
console.error(ctx.method, ctx.url, error);
|
|
383
|
+
return Response.json(
|
|
384
|
+
{
|
|
385
|
+
message: error instanceof Error ? error.message : "Internal Server Error",
|
|
386
|
+
},
|
|
387
|
+
{ status: 500 },
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Custom 404 handler
|
|
392
|
+
app.notFound((ctx) => {
|
|
393
|
+
return Response.json({ error: "Not Found", path: ctx.url }, { status: 404 });
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Throw a Response to short-circuit with a specific status
|
|
397
|
+
app.get("/secret", (ctx) => {
|
|
398
|
+
throw new Response("Forbidden", { status: 403 });
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Built-in Middleware
|
|
405
|
+
|
|
406
|
+
### CORS
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import { cors } from "@coderbuzz/ken";
|
|
410
|
+
|
|
411
|
+
const api = cors({ origin: "https://example.com", credentials: true });
|
|
412
|
+
api.get("/", () => Response.json({ data: true }));
|
|
413
|
+
app.use("/api", api);
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### JWT
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import { jwt, signJwt } from "@coderbuzz/ken";
|
|
420
|
+
|
|
421
|
+
// Sign a token
|
|
422
|
+
app.get("/token", async () => {
|
|
423
|
+
const token = await signJwt({
|
|
424
|
+
sub: "user123",
|
|
425
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
426
|
+
}, "secret");
|
|
427
|
+
return Response.json({ token });
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Protect a route
|
|
431
|
+
app.get("/secure", {
|
|
432
|
+
state: { auth: jwt({ secret: "secret" }) },
|
|
433
|
+
}, (ctx) => Response.json({ payload: ctx.state.auth }));
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Session
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
import { session } from "@coderbuzz/ken";
|
|
440
|
+
|
|
441
|
+
const userSession = session({
|
|
442
|
+
cookieName: "_sid",
|
|
443
|
+
validate: (cookieValue) => {
|
|
444
|
+
const user = db.getUser(cookieValue);
|
|
445
|
+
if (!user?.active) throw new Response("Unauthorized", { status: 401 });
|
|
446
|
+
return user;
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
app.get("/dashboard", {
|
|
451
|
+
state: { session: userSession },
|
|
452
|
+
}, (ctx) => Response.json({ user: ctx.state.session }));
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Basic Auth
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
import { basicAuth } from "@coderbuzz/ken";
|
|
459
|
+
|
|
460
|
+
app.get("/admin", {
|
|
461
|
+
state: { auth: basicAuth({ username: "admin", password: "secret" }) },
|
|
462
|
+
}, (ctx) => Response.json({ user: ctx.state.auth.username }));
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Bearer Auth
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { bearerAuth } from "@coderbuzz/ken";
|
|
469
|
+
|
|
470
|
+
app.get("/api/resource", {
|
|
471
|
+
state: { auth: bearerAuth({ token: "my-token" }) },
|
|
472
|
+
}, (ctx) => Response.json({ token: ctx.state.auth.token }));
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Logger
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
import { logger } from "@coderbuzz/ken";
|
|
479
|
+
|
|
480
|
+
app.use(logger());
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Request ID
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
import { requestId } from "@coderbuzz/ken";
|
|
487
|
+
|
|
488
|
+
app.get("/request", {
|
|
489
|
+
state: { reqId: requestId() },
|
|
490
|
+
}, (ctx) => Response.json({ id: ctx.state.reqId }));
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Secure Headers
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
import { secureHeaders } from "@coderbuzz/ken";
|
|
497
|
+
|
|
498
|
+
app.get("/page", {
|
|
499
|
+
state: { sec: secureHeaders() },
|
|
500
|
+
}, () => new Response("secure"));
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Compression
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
import { compress } from "@coderbuzz/ken";
|
|
507
|
+
|
|
508
|
+
app.get("/data", {
|
|
509
|
+
state: { encoding: compress() },
|
|
510
|
+
}, (ctx) => Response.json({ encoding: ctx.state.encoding.encoding }));
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Cache
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
import { cache } from "@coderbuzz/ken";
|
|
517
|
+
|
|
518
|
+
app.get("/static", {
|
|
519
|
+
state: { caching: cache({ maxAge: 3600, public: true }) },
|
|
520
|
+
}, () => new Response("cached content"));
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Rate Limiting / Body Limit
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
import { bodyLimit } from "@coderbuzz/ken";
|
|
527
|
+
|
|
528
|
+
app.post("/upload", {
|
|
529
|
+
state: { limit: bodyLimit({ maxSize: 1_000_000 }) }, // 1 MB
|
|
530
|
+
}, async (ctx) => {
|
|
531
|
+
const body = await ctx.json;
|
|
532
|
+
return Response.json({ received: body });
|
|
533
|
+
});
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Timeout
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
import { timeout } from "@coderbuzz/ken";
|
|
540
|
+
|
|
541
|
+
app.get("/slow", {
|
|
542
|
+
state: { timeoutSig: timeout({ duration: 5000 }) },
|
|
543
|
+
}, () => new Response("fast enough"));
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Timing
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import { timing } from "@coderbuzz/ken";
|
|
550
|
+
|
|
551
|
+
app.get("/timed", {
|
|
552
|
+
state: { perf: timing() },
|
|
553
|
+
}, () => new Response("timed"));
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### IP Restriction
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import { ipRestriction } from "@coderbuzz/ken";
|
|
560
|
+
|
|
561
|
+
app.get("/internal", {
|
|
562
|
+
state: { ipCheck: ipRestriction({ allowList: ["127.0.0.1", "::1"] }) },
|
|
563
|
+
}, () => new Response("allowed"));
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### CSRF Protection
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
import { csrf } from "@coderbuzz/ken";
|
|
570
|
+
|
|
571
|
+
app.post("/form", {
|
|
572
|
+
state: { protection: csrf({ origin: ["https://example.com"] }) },
|
|
573
|
+
}, () => Response.json({ success: true }));
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### ETag
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
import { etag } from "@coderbuzz/ken";
|
|
580
|
+
|
|
581
|
+
app.get("/resource", {
|
|
582
|
+
state: { etagValue: etag() },
|
|
583
|
+
}, (ctx) => {
|
|
584
|
+
const tag = '"v1"';
|
|
585
|
+
if (ctx.state.etagValue === tag) return new Response(null, { status: 304 });
|
|
586
|
+
return new Response("content", { headers: { ETag: tag } });
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Combining Middleware
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
app.get("/combined", {
|
|
594
|
+
state: {
|
|
595
|
+
reqId: requestId(),
|
|
596
|
+
perf: timing(),
|
|
597
|
+
sec: secureHeaders(),
|
|
598
|
+
},
|
|
599
|
+
}, (ctx) => Response.json({ id: ctx.state.reqId }));
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## WebSocket
|
|
605
|
+
|
|
606
|
+
### Basic Echo
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
app.ws("/echo", {
|
|
610
|
+
message(peer, message) {
|
|
611
|
+
peer.send(message);
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Lifecycle Hooks
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
app.ws("/chat", {
|
|
620
|
+
open(peer) {
|
|
621
|
+
peer.send("connected");
|
|
622
|
+
},
|
|
623
|
+
message(peer, message) {
|
|
624
|
+
peer.send(`echo: ${message}`);
|
|
625
|
+
},
|
|
626
|
+
close(peer, code, reason) {
|
|
627
|
+
console.log("disconnected", code, reason);
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Typed Upgrade Data
|
|
633
|
+
|
|
634
|
+
```ts
|
|
635
|
+
app.ws<{ userId: string }>("/auth", {
|
|
636
|
+
upgrade(req) {
|
|
637
|
+
const url = new URL(req.url);
|
|
638
|
+
const userId = url.searchParams.get("userId");
|
|
639
|
+
if (!userId) return new Response("Unauthorized", { status: 401 });
|
|
640
|
+
return { userId };
|
|
641
|
+
},
|
|
642
|
+
open(peer) {
|
|
643
|
+
peer.send(`Hello ${peer.data.userId}`);
|
|
644
|
+
},
|
|
645
|
+
message(peer, message) {
|
|
646
|
+
peer.send(`${peer.data.userId}: ${message}`);
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Native Pub/Sub
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
app.ws("/chat", {
|
|
655
|
+
open(peer) {
|
|
656
|
+
peer.subscribe("chat");
|
|
657
|
+
peer.publish("chat", "someone joined");
|
|
658
|
+
},
|
|
659
|
+
message(peer, message) {
|
|
660
|
+
peer.publish("chat", message);
|
|
661
|
+
peer.send(`you: ${message}`);
|
|
662
|
+
},
|
|
663
|
+
close(peer) {
|
|
664
|
+
peer.publish("chat", "someone left");
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### WsTopicHub (App-Level Pub/Sub)
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
import { WsTopicHub } from "@coderbuzz/ken";
|
|
673
|
+
|
|
674
|
+
const hub = new WsTopicHub();
|
|
675
|
+
|
|
676
|
+
app.ws("/notifications", {
|
|
677
|
+
open(peer) {
|
|
678
|
+
hub.subscribe(peer, "alerts");
|
|
679
|
+
},
|
|
680
|
+
close(peer) {
|
|
681
|
+
hub.unsubscribeAll(peer);
|
|
682
|
+
},
|
|
683
|
+
message(peer, _msg) {
|
|
684
|
+
hub.markAlive(peer);
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Publish from anywhere (e.g., another route)
|
|
689
|
+
app.post("/broadcast", async (ctx) => {
|
|
690
|
+
const { message } = await ctx.json;
|
|
691
|
+
hub.publish("alerts", message);
|
|
692
|
+
return Response.json({ sent: true });
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Ping/Pong (Heartbeat)
|
|
697
|
+
|
|
698
|
+
```ts
|
|
699
|
+
app.ws("/live", {
|
|
700
|
+
pong(peer) {
|
|
701
|
+
console.log(`${peer.remoteAddress} is alive`);
|
|
702
|
+
},
|
|
703
|
+
message(peer, msg) {
|
|
704
|
+
peer.send(msg);
|
|
705
|
+
},
|
|
706
|
+
}, { pingInterval: 30, pongTimeout: 10 });
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Streaming Response
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
app.get("/stream", () => {
|
|
715
|
+
const stream = new ReadableStream({
|
|
716
|
+
start(controller) {
|
|
717
|
+
controller.enqueue(new TextEncoder().encode("chunk 1"));
|
|
718
|
+
controller.enqueue(new TextEncoder().encode("chunk 2"));
|
|
719
|
+
controller.close();
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
return new Response(stream);
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## File Utilities
|
|
729
|
+
|
|
730
|
+
```ts
|
|
731
|
+
import {
|
|
732
|
+
listDirectory,
|
|
733
|
+
receiveFiles,
|
|
734
|
+
saveFile,
|
|
735
|
+
sendFile,
|
|
736
|
+
} from "@coderbuzz/ken";
|
|
737
|
+
|
|
738
|
+
// Serve a file
|
|
739
|
+
app.get("/download", (ctx) => sendFile(ctx, "./assets/file.pdf"));
|
|
740
|
+
|
|
741
|
+
// List directory
|
|
742
|
+
app.get("/files", (ctx) => listDirectory(ctx, "./uploads"));
|
|
743
|
+
|
|
744
|
+
// Receive uploaded files
|
|
745
|
+
app.post("/upload", async (ctx) => {
|
|
746
|
+
const files = await receiveFiles(ctx);
|
|
747
|
+
for (const file of files) {
|
|
748
|
+
await saveFile(file, "./uploads");
|
|
749
|
+
}
|
|
750
|
+
return Response.json({ count: files.length });
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## Utilities
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
import {
|
|
760
|
+
compressString,
|
|
761
|
+
decompressString,
|
|
762
|
+
decryptString,
|
|
763
|
+
encryptString,
|
|
764
|
+
generateSecretKey,
|
|
765
|
+
getPathname,
|
|
766
|
+
memoize,
|
|
767
|
+
} from "@coderbuzz/ken";
|
|
768
|
+
|
|
769
|
+
// Encryption
|
|
770
|
+
const key = await generateSecretKey();
|
|
771
|
+
const encrypted = await encryptString("hello", key);
|
|
772
|
+
const decrypted = await decryptString(encrypted, key);
|
|
773
|
+
|
|
774
|
+
// Compression
|
|
775
|
+
const compressed = await compressString("large text...");
|
|
776
|
+
const original = await decompressString(compressed);
|
|
777
|
+
|
|
778
|
+
// Memoization
|
|
779
|
+
const cached = memoize(expensiveFn, { ttl: 60_000 });
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
## Runtime Detection
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
import { isBun, isDeno, isNode } from "@coderbuzz/ken";
|
|
788
|
+
|
|
789
|
+
if (isBun) console.log("Running on Bun");
|
|
790
|
+
if (isDeno) console.log("Running on Deno");
|
|
791
|
+
if (isNode) console.log("Running on Node.js");
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
---
|
|
36
795
|
|
|
37
|
-
##
|
|
796
|
+
## Remote Info
|
|
38
797
|
|
|
39
|
-
```
|
|
40
|
-
|
|
798
|
+
```ts
|
|
799
|
+
app.get("/ip", (ctx) => {
|
|
800
|
+
const { address, port } = ctx.remoteInfo;
|
|
801
|
+
return Response.json({ address, port });
|
|
802
|
+
});
|
|
41
803
|
```
|