raffel 1.0.2 → 1.0.3

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
@@ -2,14 +2,14 @@
2
2
 
3
3
  # Raffel
4
4
 
5
- ### Build APIs Like Express. Scale Like Nothing Else.
5
+ ### One server. HTTP, WebSocket, gRPC, TCP, UDP — all at once.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/raffel.svg?style=flat-square&color=8b5cf6)](https://www.npmjs.com/package/raffel)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
9
  [![Node.js](https://img.shields.io/badge/Node.js-18+-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
10
10
  [![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE)
11
11
 
12
- [Quick Start](#quick-start) · [Full Documentation](https://forattini-dev.github.io/raffel) · [Examples](./examples) · [Migration from Express](#migration-from-express)
12
+ [Quick Start](#quick-start) · [Full Documentation](https://forattini-dev.github.io/raffel) · [Examples](./examples) · [Migration from Hono](#migration-from-hono)
13
13
 
14
14
  </div>
15
15
 
@@ -18,32 +18,29 @@
18
18
  ## If You Know Express, You Know Raffel
19
19
 
20
20
  ```typescript
21
- import { createServer } from 'raffel'
21
+ import { HttpApp, serve } from 'raffel'
22
22
 
23
- const app = createServer({ port: 3000 })
23
+ const app = new HttpApp()
24
24
 
25
- app.get('/users', async () => {
26
- return db.users.findMany()
25
+ app.get('/users', async (c) => {
26
+ return c.json(await db.users.findMany())
27
27
  })
28
28
 
29
- app.get('/users/:id', async ({ id }) => {
30
- return db.users.findById(id)
29
+ app.get('/users/:id', async (c) => {
30
+ const user = await db.users.findById(c.req.param('id'))
31
+ if (!user) return c.json({ error: 'Not found' }, 404)
32
+ return c.json(user)
31
33
  })
32
34
 
33
- app.post('/users', async (body) => {
34
- return db.users.create(body)
35
+ app.post('/users', async (c) => {
36
+ const body = await c.req.json()
37
+ return c.json(await db.users.create(body), 201)
35
38
  })
36
39
 
37
- await app.start()
38
- ```
39
-
40
- ```bash
41
- curl http://localhost:3000/users
42
- curl http://localhost:3000/users/123
43
- curl -X POST http://localhost:3000/users -d '{"name":"John"}'
40
+ serve({ fetch: app.fetch, port: 3000 })
44
41
  ```
45
42
 
46
- **That's it. Familiar, right?**
43
+ **Identical to Hono.** Same routes, same context API, same middleware signature. Your muscle memory works.
47
44
 
48
45
  ---
49
46
 
@@ -56,387 +53,633 @@ pnpm add raffel
56
53
  ### Hello World
57
54
 
58
55
  ```typescript
59
- import { createServer } from 'raffel'
56
+ import { HttpApp, serve } from 'raffel'
60
57
 
61
- const app = createServer({ port: 3000 })
58
+ const app = new HttpApp()
62
59
 
63
- app.get('/hello/:name', async ({ name }) => {
64
- return { message: `Hello, ${name}!` }
65
- })
60
+ app.get('/hello/:name', (c) => c.text(`Hello, ${c.req.param('name')}!`))
66
61
 
67
- await app.start()
62
+ serve({ fetch: app.fetch, port: 3000 })
68
63
  ```
69
64
 
70
- ```bash
71
- curl http://localhost:3000/hello/World
72
- # → {"message":"Hello, World!"}
73
- ```
74
-
75
- ### CRUD API in 30 Seconds
65
+ ### CRUD in 30 Seconds
76
66
 
77
67
  ```typescript
78
- import { createServer } from 'raffel'
68
+ import { HttpApp, serve } from 'raffel'
79
69
 
80
- const app = createServer({ port: 3000 })
70
+ const app = new HttpApp()
71
+ const users = new Map<string, unknown>()
81
72
 
82
- const users = new Map()
73
+ app.get('/users', (c) => c.json([...users.values()]))
83
74
 
84
- app.get('/users', async () => [...users.values()])
85
-
86
- app.get('/users/:id', async ({ id }) => {
87
- const user = users.get(id)
88
- if (!user) throw app.errors.notFound('User not found')
89
- return user
75
+ app.get('/users/:id', (c) => {
76
+ const user = users.get(c.req.param('id'))
77
+ return user ? c.json(user) : c.json({ error: 'Not found' }, 404)
90
78
  })
91
79
 
92
- app.post('/users', async (body) => {
93
- const user = { id: crypto.randomUUID(), ...body }
80
+ app.post('/users', async (c) => {
81
+ const user = { id: crypto.randomUUID(), ...(await c.req.json()) }
94
82
  users.set(user.id, user)
95
- return user
83
+ return c.json(user, 201)
96
84
  })
97
85
 
98
- app.put('/users/:id', async ({ id, ...body }) => {
99
- if (!users.has(id)) throw app.errors.notFound('User not found')
100
- const user = { id, ...body }
86
+ app.put('/users/:id', async (c) => {
87
+ const id = c.req.param('id')
88
+ if (!users.has(id)) return c.json({ error: 'Not found' }, 404)
89
+ const user = { id, ...(await c.req.json()) }
101
90
  users.set(id, user)
102
- return user
103
- })
104
-
105
- app.delete('/users/:id', async ({ id }) => {
106
- if (!users.delete(id)) throw app.errors.notFound('User not found')
107
- return { success: true }
91
+ return c.json(user)
108
92
  })
109
93
 
110
- await app.start()
111
- ```
112
-
113
- ### Add Validation (with Zod)
114
-
115
- ```typescript
116
- import { createServer } from 'raffel'
117
- import { z } from 'zod'
118
-
119
- const app = createServer({ port: 3000 })
120
-
121
- app.post('/users', {
122
- body: z.object({
123
- name: z.string().min(2),
124
- email: z.string().email(),
125
- }),
126
- handler: async (body) => {
127
- return db.users.create(body)
128
- }
94
+ app.delete('/users/:id', (c) => {
95
+ const id = c.req.param('id')
96
+ return users.delete(id)
97
+ ? c.json({ success: true })
98
+ : c.json({ error: 'Not found' }, 404)
129
99
  })
130
100
 
131
- await app.start()
132
- ```
133
-
134
- Invalid request? Automatic error response:
135
-
136
- ```json
137
- {
138
- "error": "VALIDATION_ERROR",
139
- "message": "Validation failed",
140
- "details": [
141
- { "path": "email", "message": "Invalid email" }
142
- ]
143
- }
101
+ serve({ fetch: app.fetch, port: 3000 })
144
102
  ```
145
103
 
146
- ### Add Middleware
104
+ ### Production-Ready `serve()`
147
105
 
148
106
  ```typescript
149
- import { createServer } from 'raffel'
150
-
151
- const app = createServer({ port: 3000 })
152
-
153
- // Global middleware
154
- app.use(async (req, next) => {
155
- const start = Date.now()
156
- const result = await next()
157
- console.log(`${req.method} ${req.path} - ${Date.now() - start}ms`)
158
- return result
159
- })
160
-
161
- // Auth middleware
162
- const requireAuth = async (req, next) => {
163
- const token = req.headers.authorization?.replace('Bearer ', '')
164
- if (!token) throw app.errors.unauthorized()
165
- req.user = await verifyToken(token)
166
- return next()
167
- }
168
-
169
- app.get('/profile', requireAuth, async (_, req) => {
170
- return req.user
107
+ serve({
108
+ fetch: app.fetch,
109
+ port: 3000,
110
+ keepAliveTimeout: 65000, // slightly above load balancer idle timeout
111
+ headersTimeout: 66000,
112
+ onListen: ({ port, hostname }) => console.log(`Listening on ${hostname}:${port}`),
171
113
  })
172
-
173
- await app.start()
174
114
  ```
175
115
 
176
116
  ---
177
117
 
178
118
  ## Wait, There's More
179
119
 
180
- Here's where Raffel gets interesting. **That same API you just wrote? It already works over WebSocket, JSON-RPC, and more.**
120
+ Raffel is not just an HTTP framework. It's a **unified multi-protocol runtime**. Every handler you write is protocol-agnostic the same business logic runs over HTTP, WebSocket, gRPC, JSON-RPC, GraphQL, TCP, and UDP.
121
+
122
+ ### The Procedure API
181
123
 
182
124
  ```typescript
183
- const app = createServer({
125
+ import { createServer } from 'raffel'
126
+ import { z } from 'zod'
127
+
128
+ const server = createServer({
184
129
  port: 3000,
185
130
  websocket: { path: '/ws' },
186
131
  jsonrpc: { path: '/rpc' },
187
132
  })
188
133
 
189
- app.get('/users/:id', async ({ id }) => {
190
- return db.users.findById(id)
191
- })
134
+ server
135
+ .procedure('users.create')
136
+ .input(z.object({ name: z.string().min(2), email: z.string().email() }))
137
+ .output(z.object({ id: z.string(), name: z.string(), email: z.string() }))
138
+ .handler(async (input, ctx) => {
139
+ return db.users.create(input)
140
+ })
192
141
 
193
- await app.start()
142
+ await server.start()
194
143
  ```
195
144
 
196
- **Same handler. Three protocols. Zero extra code.**
145
+ **Same handler. Every protocol. Zero extra code.**
197
146
 
198
147
  ```bash
199
- # HTTP (as usual)
200
- curl http://localhost:3000/users/123
148
+ # HTTP
149
+ curl -X POST http://localhost:3000/users \
150
+ -d '{"name":"Alice","email":"alice@example.com"}'
201
151
 
202
152
  # WebSocket
203
153
  wscat -c ws://localhost:3000/ws
204
- > {"method":"users.get","params":{"id":"123"}}
154
+ > {"method":"users.create","params":{"name":"Alice","email":"alice@example.com"}}
205
155
 
206
- # JSON-RPC
156
+ # JSON-RPC 2.0
207
157
  curl -X POST http://localhost:3000/rpc \
208
- -d '{"jsonrpc":"2.0","method":"users.get","params":{"id":"123"},"id":1}'
158
+ -d '{"jsonrpc":"2.0","method":"users.create","params":{...},"id":1}'
159
+ ```
160
+
161
+ ### Streaming
162
+
163
+ ```typescript
164
+ // Server → client stream
165
+ server
166
+ .stream('logs.tail')
167
+ .handler(async function* ({ file }) {
168
+ for await (const line of readLines(file)) {
169
+ yield { line, timestamp: Date.now() }
170
+ }
171
+ })
172
+
173
+ // Bidirectional stream
174
+ server
175
+ .stream('chat.session')
176
+ .bidi()
177
+ .handler(async (stream, ctx) => {
178
+ for await (const msg of stream) {
179
+ await stream.write({ echo: msg, from: ctx.auth?.userId })
180
+ }
181
+ })
209
182
  ```
210
183
 
211
- ### Why Does This Matter?
184
+ ### Events with Delivery Guarantees
212
185
 
213
- - **Write once** - Same validation, auth, and error handling everywhere
214
- - **Client choice** - HTTP for REST, WebSocket for real-time, JSON-RPC for internal services
215
- - **Zero friction** - No adapters, no mappings, no duplicate code
186
+ ```typescript
187
+ server
188
+ .event('emails.send')
189
+ .delivery('at-least-once')
190
+ .handler(async (payload, ctx, ack) => {
191
+ await sendEmail(payload)
192
+ ack()
193
+ })
194
+ ```
216
195
 
217
196
  ---
218
197
 
219
198
  ## The Full Picture
220
199
 
221
- Under the hood, Raffel normalizes all requests into a unified format called an **Envelope**. But you don't need to think about that - it just works.
200
+ | Module | What it does |
201
+ |--------|-------------|
202
+ | **HTTP** | Hono-compatible router + `serve()` with production timeouts |
203
+ | **WebSocket** | Real-time adapter + Pusher-like channels (public/private/presence) |
204
+ | **gRPC** | Full gRPC adapter with TLS and streaming |
205
+ | **JSON-RPC 2.0** | Batch + notification + error codes per spec |
206
+ | **GraphQL** | Schema-first adapter with subscriptions |
207
+ | **TCP / UDP** | Raw socket handlers with connection filters |
208
+ | **Single-Port** | Sniff protocol on one port — HTTP, WS, gRPC, gRPC-Web all on `:3000` |
209
+ | **Interceptors** | Rate limit, circuit breaker, retry, timeout, cache, bulkhead, and more |
210
+ | **Session Store** | Memory + Redis drivers with lazy load + auto-save |
211
+ | **Proxy Suite** | HTTP forward, CONNECT tunnel (MITM), SOCKS5, transparent |
212
+ | **Metrics** | Prometheus-style counters, gauges, histograms with exporters |
213
+ | **Tracing** | OpenTelemetry spans with Jaeger / Zipkin exporters |
214
+ | **OpenAPI** | Generate spec from schemas + serve ReDoc / Swagger UI |
215
+ | **Channels** | Pusher-like pub/sub with presence and authorization |
216
+ | **MCP Server** | Model Context Protocol for AI-assisted development |
217
+ | **Testing** | Full mock suite: HTTP, WS, TCP, UDP, DNS, SSE, Proxy |
218
+ | **Validation** | Plug in Zod, Yup, Joi, Ajv, or fastest-validator |
219
+
220
+ ---
221
+
222
+ ## Interceptors
222
223
 
223
- | What You Write | What Raffel Exposes |
224
- |----------------|---------------------|
225
- | `app.get('/users/:id', handler)` | HTTP GET, WS, JSON-RPC, gRPC, GraphQL |
226
- | `app.post('/users', handler)` | HTTP POST, WS, JSON-RPC, gRPC, GraphQL |
227
- | Validation schema | Same validation, all protocols |
228
- | Auth middleware | Same auth, all protocols |
229
- | Error handling | Protocol-appropriate errors |
224
+ Interceptors are reusable middleware that compose cleanly across any protocol.
230
225
 
231
- ### Supported Protocols
226
+ ```typescript
227
+ import {
228
+ createRateLimitInterceptor,
229
+ createCircuitBreakerInterceptor,
230
+ createRetryInterceptor,
231
+ createTimeoutInterceptor,
232
+ createCacheInterceptor,
233
+ createLoggingInterceptor,
234
+ createTracingInterceptor,
235
+ } from 'raffel'
236
+
237
+ server
238
+ .procedure('users.list')
239
+ .use(createTimeoutInterceptor({ timeout: 5000 }))
240
+ .use(createRateLimitInterceptor({ limit: 100, window: '1m' }))
241
+ .use(createCacheInterceptor({ ttl: 60, store: cacheStore }))
242
+ .use(createLoggingInterceptor())
243
+ .handler(async () => db.users.findMany())
244
+ ```
232
245
 
233
- | Protocol | Status | Use Case |
234
- |----------|--------|----------|
235
- | HTTP | Production | REST APIs, webhooks |
236
- | WebSocket | Production | Real-time, bi-directional |
237
- | JSON-RPC | Production | Internal services, batch |
238
- | gRPC | Production | Microservices, high-perf |
239
- | GraphQL | Production | Flexible queries |
240
- | TCP | Production | IoT, custom protocols |
241
- | UDP | Production | Gaming, streaming |
246
+ Apply globally, per-group, or per-procedure:
247
+
248
+ ```typescript
249
+ // Global
250
+ server.use(createTracingInterceptor({ tracer }))
251
+ server.use(createLoggingInterceptor())
252
+
253
+ // Group / module
254
+ const adminModule = createRouterModule('admin', [requireAdmin])
255
+ adminModule.procedure('users.delete').handler(...)
256
+
257
+ // Per-procedure
258
+ server.procedure('payments.charge')
259
+ .use(createCircuitBreakerInterceptor({ threshold: 5, timeout: 30000 }))
260
+ .use(createRetryInterceptor({ attempts: 3, backoff: 'exponential' }))
261
+ .handler(...)
262
+ ```
263
+
264
+ | Interceptor | Purpose |
265
+ |-------------|---------|
266
+ | `createRateLimitInterceptor` | Token bucket / sliding window (memory, Redis, filesystem) |
267
+ | `createCircuitBreakerInterceptor` | Auto-open after failures, half-open probe |
268
+ | `createBulkheadInterceptor` | Concurrency isolation per procedure |
269
+ | `createRetryInterceptor` | Exponential backoff with jitter |
270
+ | `createTimeoutInterceptor` | Per-phase, cascading, deadline propagation |
271
+ | `createCacheInterceptor` | Read-through / write-through (memory, file, Redis) |
272
+ | `createDedupInterceptor` | In-flight request deduplication |
273
+ | `createSizeLimitInterceptor` | Request / response size guard |
274
+ | `createFallbackInterceptor` | Return default on failure |
275
+ | `createRequestIdInterceptor` | Inject/propagate correlation IDs |
276
+ | `createLoggingInterceptor` | Structured request/response logging |
277
+ | `createMetricsInterceptor` | Auto-instrument with Prometheus metrics |
278
+ | `createTracingInterceptor` | OpenTelemetry span creation |
279
+ | `createSessionInterceptor` | Session load/save via memory or Redis |
280
+ | `createValidationInterceptor` | Schema validation on input/output |
281
+ | `createAuthMiddleware` | Bearer token, API key strategies |
242
282
 
243
283
  ---
244
284
 
245
- ## Going Deeper: The Procedure API
285
+ ## Channels (Real-Time Pub/Sub)
246
286
 
247
- For power users who want full control, Raffel exposes its native API:
287
+ Pusher-compatible channel model over WebSocket.
248
288
 
249
289
  ```typescript
250
- import { createServer, registerValidator, createZodAdapter } from 'raffel'
251
- import { z } from 'zod'
290
+ import { createChannelManager } from 'raffel'
291
+
292
+ const channels = createChannelManager(
293
+ {
294
+ authorize: async (socketId, channel, ctx) => {
295
+ // private-* and presence-* channels require auth
296
+ return { authorized: !!ctx.auth }
297
+ },
298
+ presence: {
299
+ onJoin: (channel, member) => broadcastPresence(channel),
300
+ onLeave: (channel, member) => broadcastPresence(channel),
301
+ },
302
+ },
303
+ (socketId, message) => ws.sendToClient(socketId, message)
304
+ )
305
+
306
+ // Subscribe
307
+ await channels.subscribe(socketId, 'presence-room:42', ctx)
308
+
309
+ // Broadcast to all subscribers
310
+ channels.broadcast('presence-room:42', 'new-message', { text: 'Hello!' })
311
+
312
+ // Get online members
313
+ const members = channels.getMembers('presence-room:42')
314
+ ```
252
315
 
253
- registerValidator(createZodAdapter(z))
316
+ Channel types: `public-*` (anyone), `private-*` (authorized), `presence-*` (auth + member tracking).
254
317
 
255
- const server = createServer({
256
- port: 3000,
257
- websocket: { path: '/ws' },
318
+ ---
319
+
320
+ ## Proxy Suite
321
+
322
+ Full proxy toolkit built into Raffel — no extra dependencies.
323
+
324
+ ### HTTP Forward Proxy
325
+
326
+ ```typescript
327
+ import { createHttpForwardProxy } from 'raffel'
328
+
329
+ const proxy = createHttpForwardProxy(httpServer, {
330
+ auth: { type: 'basic', credentials: { admin: 'secret' } },
331
+ filter: {
332
+ allowHosts: ['*.trusted.com', 'api.internal'],
333
+ denyHosts: ['*.evil.com'],
334
+ },
335
+ onRequest: (req) => { /* log or modify */ return req },
258
336
  })
337
+ ```
259
338
 
260
- // Full procedure definition
261
- server.procedure('users.create')
262
- .description('Create a new user')
263
- .input(z.object({
264
- name: z.string().min(2),
265
- email: z.string().email(),
266
- }))
267
- .output(z.object({
268
- id: z.string().uuid(),
269
- name: z.string(),
270
- email: z.string(),
271
- }))
272
- .handler(async (input, ctx) => {
273
- // ctx has auth, tracing, request metadata
274
- return db.users.create(input)
275
- })
339
+ ### CONNECT Tunnel (with MITM)
276
340
 
277
- // Streaming (server → client)
278
- server.stream('logs.tail')
279
- .handler(async function* ({ file }) {
280
- for await (const line of readLines(file)) {
281
- yield { line, timestamp: Date.now() }
282
- }
283
- })
341
+ ```typescript
342
+ import { createConnectTunnel } from 'raffel'
343
+
344
+ // Transparent tunnel
345
+ const tunnel = createConnectTunnel({ mode: 'pipe' })
346
+
347
+ // MITM: inspect and modify HTTPS traffic
348
+ const mitm = createConnectTunnel({
349
+ mode: 'mitm',
350
+ onRequest: (req) => {
351
+ req.headers['x-intercepted'] = 'true'
352
+ return req
353
+ },
354
+ onResponse: (res) => {
355
+ res.headers['x-inspected'] = 'true'
356
+ return res
357
+ },
358
+ onUpstreamCert: (cert) => trustedCerts.has(cert.fingerprint), // cert pinning
359
+ })
360
+ ```
284
361
 
285
- // Events (fire-and-forget with guarantees)
286
- server.event('emails.send')
287
- .delivery('at-least-once')
288
- .handler(async (payload, ctx, ack) => {
289
- await sendEmail(payload)
290
- ack()
291
- })
362
+ ### SOCKS5 Proxy
292
363
 
293
- await server.start()
364
+ ```typescript
365
+ import { createSocks5Proxy } from 'raffel'
366
+
367
+ const socks5 = createSocks5Proxy({
368
+ port: 1080,
369
+ auth: { type: 'userpass', users: { alice: 'secret' } },
370
+ })
371
+ await socks5.start()
294
372
  ```
295
373
 
296
- ### The Envelope Model
374
+ ### Transparent Proxy (Linux TPROXY)
375
+
376
+ ```typescript
377
+ import { createTransparentProxy } from 'raffel'
297
378
 
298
- Every request becomes an Envelope:
379
+ const proxy = createTransparentProxy({
380
+ mode: 'tproxy',
381
+ port: 8080,
382
+ upstream: { host: 'backend.internal', port: 8080 },
383
+ })
384
+ ```
385
+
386
+ ---
387
+
388
+ ## Session Store
299
389
 
300
390
  ```typescript
301
- interface Envelope {
302
- id: string // Correlation ID
303
- procedure: string // Handler name (e.g., "users.create")
304
- type: 'request' | 'response' | 'stream:data' | 'event'
305
- payload: unknown // Your data
306
- context: Context // Auth, tracing, deadline, metadata
307
- }
391
+ import { createSessionInterceptor, createRedisSessionDriver } from 'raffel'
392
+
393
+ const sessions = createSessionInterceptor({
394
+ driver: createRedisSessionDriver({ client: redis }),
395
+ cookie: { name: 'sid', httpOnly: true, secure: true, sameSite: 'lax' },
396
+ ttl: 86400,
397
+ })
398
+
399
+ server.use(sessions)
400
+
401
+ server.procedure('auth.me').handler(async (_, ctx) => {
402
+ // ctx.session is loaded lazily, saved automatically
403
+ const { userId } = ctx.session.get()
404
+ return db.users.findById(userId)
405
+ })
308
406
  ```
309
407
 
310
- This abstraction is what enables protocol-agnostic handlers. You write business logic once, Raffel handles the protocol translation.
408
+ Drivers: `createMemorySessionDriver()`, `createRedisSessionDriver({ client })`.
311
409
 
312
410
  ---
313
411
 
314
- ## Features at a Glance
412
+ ## OpenAPI + Docs UI
315
413
 
316
- | Category | Features |
317
- |----------|----------|
318
- | **HTTP** | GET/POST/PUT/PATCH/DELETE, path params, query params, headers |
319
- | **Validation** | Zod, Yup, Joi, Ajv, fastest-validator |
320
- | **Auth** | JWT, API Key, OAuth2, OIDC, Basic, Session |
321
- | **Resilience** | Rate Limit, Circuit Breaker, Retry, Timeout, Bulkhead |
322
- | **Observability** | Prometheus Metrics, OpenTelemetry Tracing |
323
- | **Caching** | Memory, Redis, S3, Read-through, Write-through |
324
- | **Real-time** | WebSocket Channels, Presence, Broadcasting |
325
- | **Documentation** | Auto-generated OpenAPI/Swagger from schemas |
326
- | **DX** | Hot Reload, File-based Routing, TypeScript-first |
414
+ ```typescript
415
+ import { generateOpenAPI, mountOpenApiDocs } from 'raffel'
416
+
417
+ // Auto-generate spec from registered schemas
418
+ const spec = generateOpenAPI(server, {
419
+ info: { title: 'My API', version: '1.0.0' },
420
+ servers: [{ url: 'https://api.example.com' }],
421
+ })
422
+
423
+ // Mount /openapi.json + /docs (ReDoc or Swagger UI)
424
+ mountOpenApiDocs(app, {
425
+ spec,
426
+ ui: 'redoc', // or 'swagger'
427
+ path: '/docs',
428
+ })
429
+ ```
327
430
 
328
431
  ---
329
432
 
330
- ## Migration from Express
433
+ ## Metrics & Tracing
331
434
 
332
- Already have an Express app? Migration is straightforward:
435
+ ### Prometheus Metrics
333
436
 
334
- <table>
335
- <tr>
336
- <th>Express</th>
337
- <th>Raffel</th>
338
- </tr>
339
- <tr>
340
- <td>
437
+ ```typescript
438
+ import { createMetricRegistry, createMetricsInterceptor, exportPrometheus } from 'raffel'
341
439
 
342
- ```javascript
343
- const express = require('express')
344
- const app = express()
440
+ const metrics = createMetricRegistry()
345
441
 
346
- app.get('/users/:id', (req, res) => {
347
- const user = getUser(req.params.id)
348
- res.json(user)
349
- })
442
+ server.use(createMetricsInterceptor({ registry: metrics }))
443
+
444
+ // Expose /metrics endpoint
445
+ app.get('/metrics', (c) => c.text(exportPrometheus(metrics), 200, {
446
+ 'Content-Type': 'text/plain; version=0.0.4',
447
+ }))
448
+ ```
449
+
450
+ ### OpenTelemetry Tracing
451
+
452
+ ```typescript
453
+ import { createTracer, createTracingInterceptor, createJaegerExporter } from 'raffel'
350
454
 
351
- app.post('/users', (req, res) => {
352
- const user = createUser(req.body)
353
- res.status(201).json(user)
455
+ const tracer = createTracer({
456
+ serviceName: 'my-api',
457
+ exporter: createJaegerExporter({ endpoint: 'http://jaeger:14268/api/traces' }),
458
+ sampler: createProbabilitySampler(0.1), // 10% sampling
354
459
  })
355
460
 
356
- app.listen(3000)
461
+ server.use(createTracingInterceptor({ tracer }))
357
462
  ```
358
463
 
359
- </td>
360
- <td>
464
+ ---
465
+
466
+ ## Health Checks
361
467
 
362
468
  ```typescript
363
- import { createServer } from 'raffel'
469
+ import { createHealthCheckProcedures, CommonProbes } from 'raffel'
470
+
471
+ const health = createHealthCheckProcedures({
472
+ probes: [
473
+ CommonProbes.memory({ maxHeapMb: 512 }),
474
+ CommonProbes.uptime(),
475
+ {
476
+ name: 'database',
477
+ check: async () => {
478
+ await db.ping()
479
+ return { status: 'healthy' }
480
+ },
481
+ },
482
+ ],
483
+ })
364
484
 
365
- const app = createServer({ port: 3000 })
485
+ server.mount('/', health)
486
+ // Registers: health.live, health.ready, health.startup
487
+ ```
488
+
489
+ ---
366
490
 
367
- app.get('/users/:id', async ({ id }) => {
368
- return getUser(id)
491
+ ## Connection Filters
492
+
493
+ Control who can connect to your TCP, UDP, and WebSocket adapters.
494
+
495
+ ```typescript
496
+ import { createTcpAdapter } from 'raffel'
497
+
498
+ const tcp = createTcpAdapter(router, {
499
+ connectionFilter: {
500
+ allowHosts: ['10.0.0.*', 'trusted.internal'],
501
+ denyHosts: ['*.untrusted.net'],
502
+ onDenied: (host, port) => logger.warn(`Blocked connection from ${host}:${port}`),
503
+ },
369
504
  })
505
+ ```
506
+
507
+ WebSocket adds origin filtering:
370
508
 
371
- app.post('/users', async (body) => {
372
- return createUser(body)
509
+ ```typescript
510
+ const ws = createWebSocketAdapter(router, {
511
+ connectionFilter: {
512
+ allowOrigins: ['https://app.example.com'],
513
+ denyOrigins: ['*'],
514
+ },
373
515
  })
516
+ ```
517
+
518
+ ---
374
519
 
375
- await app.start()
520
+ ## Single-Port Multi-Protocol
521
+
522
+ Run HTTP, WebSocket, gRPC, and gRPC-Web all on the same port. Raffel sniffs the protocol from the first bytes.
523
+
524
+ ```typescript
525
+ const server = createServer({
526
+ port: 3000,
527
+ singlePort: {
528
+ http: true,
529
+ websocket: true,
530
+ grpc: true,
531
+ grpcWeb: true,
532
+ },
533
+ })
376
534
  ```
377
535
 
378
- </td>
379
- </tr>
380
- </table>
536
+ ---
537
+
538
+ ## File-Based Routing
539
+
540
+ Drop files into a directory. Raffel discovers and registers them automatically.
381
541
 
382
- **Key differences:**
383
- - Return values instead of `res.json()`
384
- - Path params and body merged into handler argument
385
- - `async/await` native (no callback hell)
386
- - Errors thrown, not manually handled
542
+ ```
543
+ routes/
544
+ users/
545
+ index.ts → GET /users
546
+ [id].ts → GET /users/:id
547
+ [id]/posts.ts → GET /users/:id/posts
548
+ tcp/
549
+ echo.ts → TCP handler "echo"
550
+ udp/
551
+ ping.ts → UDP handler "ping"
552
+ ```
387
553
 
388
- See [full migration guide](./docs/migration.md) for middleware, error handling, and advanced patterns.
554
+ ```typescript
555
+ const server = createServer({
556
+ port: 3000,
557
+ discovery: { dir: './routes', watch: true }, // hot-reload in dev
558
+ })
559
+ ```
389
560
 
390
561
  ---
391
562
 
392
- ## Examples
563
+ ## Testing Mocks
393
564
 
394
- ```bash
395
- # Clone and run
396
- git clone https://github.com/tetis-io/raffel
397
- cd raffel
398
-
399
- # Basic examples
400
- pnpm tsx examples/00-hello-world.ts
401
- pnpm tsx examples/01-rest-api.ts
402
- pnpm tsx examples/02-websocket-server.ts
403
- pnpm tsx examples/03-rpc-server.ts
404
-
405
- # Advanced
406
- pnpm tsx examples/07-resource-builder.ts
407
- pnpm tsx examples/08-declarative-api.ts
565
+ A complete mock infrastructure for integration tests — no external services needed.
566
+
567
+ ```typescript
568
+ import { MockServiceSuite } from 'raffel'
569
+
570
+ const suite = new MockServiceSuite()
571
+ await suite.start()
572
+
573
+ const { http, ws, tcp, udp, dns, sse, proxy } = suite
574
+
575
+ // HTTP mock with request recording
576
+ http.onGet('/users', { body: [{ id: '1' }] })
577
+ const requests = await http.waitForRequests(1)
578
+
579
+ // WebSocket mock with pattern responses
580
+ ws.setResponse(/ping/, 'pong')
581
+ ws.dropRate = 0.1 // simulate 10% packet loss
582
+
583
+ // DNS mock
584
+ dns.addRecord('api.example.com', 'A', '127.0.0.1')
585
+
586
+ // SSE mock
587
+ sse.emit('data', { event: 'update', data: '{"count":42}' })
588
+
589
+ await suite.stop()
408
590
  ```
409
591
 
592
+ | Mock | Features |
593
+ |------|---------|
594
+ | `MockHttpServer` | CORS, global delay, streaming, `times`, statistics |
595
+ | `MockWebSocketServer` | Pattern responses, drop rate, max connections, auto-close |
596
+ | `MockTcpServer` | Echo + custom handlers |
597
+ | `MockUdpServer` | UDP responder |
598
+ | `MockDnsServer` | DNS over UDP (RFC 1035), no deps |
599
+ | `MockSSEServer` | Server-Sent Events |
600
+ | `MockProxyServer` | HTTP forward + MITM with hooks |
601
+
410
602
  ---
411
603
 
412
- ## Documentation
604
+ ## Validation
413
605
 
414
- | Topic | Description |
415
- |-------|-------------|
416
- | [Quickstart](https://forattini-dev.github.io/raffel/#/quickstart) | 5-minute guide |
417
- | [HTTP Deep Dive](https://forattini-dev.github.io/raffel/#/protocols/http) | REST, middleware, routing |
418
- | [Authentication](https://forattini-dev.github.io/raffel/#/auth/overview) | JWT, API Key, OAuth2, OIDC |
419
- | [Validation](https://forattini-dev.github.io/raffel/#/validation) | Zod, Yup, Joi integration |
420
- | [WebSocket](https://forattini-dev.github.io/raffel/#/protocols/websocket) | Real-time, channels, presence |
421
- | [Interceptors](https://forattini-dev.github.io/raffel/#/interceptors) | Rate limit, retry, cache |
422
- | [Core Model](https://forattini-dev.github.io/raffel/#/core-model) | Envelope, Context, architecture |
423
- | [File-based Routing](https://forattini-dev.github.io/raffel/#/file-system-discovery) | Zero-config discovery |
606
+ Bring your own validator. Raffel adapts to it.
607
+
608
+ ```typescript
609
+ import { registerValidator, createZodAdapter } from 'raffel'
610
+ import { z } from 'zod'
611
+
612
+ registerValidator(createZodAdapter(z))
613
+
614
+ server
615
+ .procedure('users.create')
616
+ .input(z.object({ name: z.string().min(2), email: z.string().email() }))
617
+ .handler(async (input) => db.users.create(input))
618
+ ```
619
+
620
+ Adapters available: `createZodAdapter`, `createYupAdapter`, `createJoiAdapter`, `createAjvAdapter`, `createFastestValidatorAdapter`.
424
621
 
425
622
  ---
426
623
 
427
624
  ## MCP Server (AI Integration)
428
625
 
429
- Raffel includes an MCP server for AI-powered development:
626
+ Raffel ships an MCP server for AI-assisted development. It gives tools like Claude direct knowledge of your API.
430
627
 
431
628
  ```bash
432
629
  # Add to Claude Code
433
630
  claude mcp add raffel npx raffel-mcp
434
631
 
435
632
  # Or run directly
436
- npx raffel-mcp --category minimal
633
+ npx raffel-mcp
437
634
  ```
438
635
 
439
- Tools: `raffel_create_server`, `raffel_create_procedure`, `raffel_add_middleware`, `raffel_api_patterns`
636
+ Provides: live documentation, code generation prompts (`add_oauth2`, `add_sessions`, etc.), and pattern guidance.
637
+
638
+ ---
639
+
640
+ ## Migration from Hono
641
+
642
+ Raffel's `HttpApp` is intentionally Hono-compatible. Most migrations are a find-and-replace:
643
+
644
+ ```diff
645
+ - import { Hono } from 'hono'
646
+ - import { serve } from '@hono/node-server'
647
+ + import { HttpApp, serve } from 'raffel'
648
+
649
+ - const app = new Hono()
650
+ + const app = new HttpApp()
651
+
652
+ app.get('/users', async (c) => c.json(await db.users.findMany()))
653
+
654
+ - serve(app)
655
+ + serve({
656
+ + fetch: app.fetch,
657
+ + port: 3000,
658
+ + keepAliveTimeout: 65000, // recommended for production
659
+ + headersTimeout: 66000,
660
+ + })
661
+ ```
662
+
663
+ Everything else is identical: routes, middleware signature, context API, `app.route()`, `app.notFound()`, `app.onError()`.
664
+
665
+ See [full migration guide](./docs/guides/migration.md) for advanced patterns.
666
+
667
+ ---
668
+
669
+ ## Documentation
670
+
671
+ | Topic | Description |
672
+ |-------|-------------|
673
+ | [Quick Start](https://forattini-dev.github.io/raffel/#/quickstart) | 5-minute guide |
674
+ | [HTTP Guide](https://forattini-dev.github.io/raffel/#/protocols/http) | REST, middleware, routing, serve() |
675
+ | [Authentication](https://forattini-dev.github.io/raffel/#/auth/overview) | JWT, API Key, OAuth2, OIDC, Sessions |
676
+ | [Interceptors](https://forattini-dev.github.io/raffel/#/interceptors) | Rate limit, circuit breaker, cache, etc. |
677
+ | [WebSocket](https://forattini-dev.github.io/raffel/#/protocols/websocket) | Real-time, channels, presence |
678
+ | [Proxy Suite](https://forattini-dev.github.io/raffel/#/proxy) | Forward, CONNECT, SOCKS5, transparent |
679
+ | [Metrics & Tracing](https://forattini-dev.github.io/raffel/#/observability) | Prometheus, OpenTelemetry |
680
+ | [Core Model](https://forattini-dev.github.io/raffel/#/core-model) | Envelope, Context, Router, architecture |
681
+ | [File-based Routing](https://forattini-dev.github.io/raffel/#/file-system-discovery) | Zero-config discovery |
682
+ | [Migration from Hono](./docs/guides/migration.md) | Step-by-step migration guide |
440
683
 
441
684
  ---
442
685