raiko.js 0.0.0-stage → 1.0.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.es.md +508 -0
  3. package/README.md +507 -2
  4. package/README.pt-BR.md +508 -0
  5. package/bin/raiko.mjs +149 -0
  6. package/dist/adapters/node-ws.d.ts +40 -0
  7. package/dist/adapters/node.d.ts +6 -0
  8. package/dist/adapters/node.js +298 -0
  9. package/dist/adapters/serve.d.ts +12 -0
  10. package/dist/admin/adapters.d.ts +35 -0
  11. package/dist/admin/index.d.ts +50 -0
  12. package/dist/admin/index.js +214 -0
  13. package/dist/app-router/dev-error.d.ts +15 -0
  14. package/dist/app-router/image.d.ts +1 -0
  15. package/dist/app-router/index.d.ts +40 -0
  16. package/dist/app-router/index.js +489 -0
  17. package/dist/app-router/metadata.d.ts +51 -0
  18. package/dist/app-router/scan.d.ts +20 -0
  19. package/dist/app.d.ts +230 -0
  20. package/dist/auth/index.d.ts +65 -0
  21. package/dist/auth/index.js +334 -0
  22. package/dist/auth/jwt.d.ts +13 -0
  23. package/dist/auth/providers.d.ts +43 -0
  24. package/dist/auth/totp.d.ts +35 -0
  25. package/dist/body.d.ts +10 -0
  26. package/dist/chunk-4NOVAJBA.js +499 -0
  27. package/dist/chunk-6VHSSIDV.js +186 -0
  28. package/dist/chunk-FU36MTH6.js +83 -0
  29. package/dist/chunk-H6BWMS4P.js +87 -0
  30. package/dist/chunk-IJNNQKTG.js +626 -0
  31. package/dist/chunk-LAFHS4ZI.js +45 -0
  32. package/dist/chunk-MGKXQINB.js +45 -0
  33. package/dist/chunk-SNAWC6GY.js +123 -0
  34. package/dist/chunk-U626USOW.js +72 -0
  35. package/dist/chunk-VC57IKQN.js +268 -0
  36. package/dist/chunk-Y3YIWI5S.js +217 -0
  37. package/dist/chunk-Z52Z3E6A.js +122 -0
  38. package/dist/client/index.d.ts +91 -0
  39. package/dist/client/index.js +228 -0
  40. package/dist/client/socket.d.ts +46 -0
  41. package/dist/components/index.d.ts +50 -0
  42. package/dist/components/index.js +4 -0
  43. package/dist/context-7E3DE2UC.js +3 -0
  44. package/dist/context.d.ts +47 -0
  45. package/dist/cookie.d.ts +39 -0
  46. package/dist/error.d.ts +34 -0
  47. package/dist/i18n/index.d.ts +36 -0
  48. package/dist/i18n/index.js +47 -0
  49. package/dist/image-FBZPWI2W.js +41 -0
  50. package/dist/index.d.ts +23 -0
  51. package/dist/index.js +23 -0
  52. package/dist/jsx/jsx-dev-runtime.d.ts +1 -0
  53. package/dist/jsx/jsx-dev-runtime.js +1 -0
  54. package/dist/jsx/jsx-runtime.d.ts +1 -0
  55. package/dist/jsx/jsx-runtime.js +1 -0
  56. package/dist/jsx/runtime.d.ts +75 -0
  57. package/dist/mail/index.d.ts +88 -0
  58. package/dist/mail/index.js +147 -0
  59. package/dist/p2p/client.d.ts +26 -0
  60. package/dist/p2p/index.d.ts +1 -0
  61. package/dist/p2p/index.js +39 -0
  62. package/dist/p2p/server.d.ts +16 -0
  63. package/dist/p2p-client.d.ts +2 -0
  64. package/dist/p2p-client.js +88 -0
  65. package/dist/pipeline.d.ts +36 -0
  66. package/dist/plugins/index.d.ts +7 -0
  67. package/dist/plugins/index.js +254 -0
  68. package/dist/plugins/openapi.d.ts +27 -0
  69. package/dist/plugins/ops.d.ts +60 -0
  70. package/dist/plugins/security.d.ts +67 -0
  71. package/dist/plugins/static.d.ts +16 -0
  72. package/dist/react/index.d.ts +36 -0
  73. package/dist/react/index.js +72 -0
  74. package/dist/response-FIW5C377.js +5 -0
  75. package/dist/response.d.ts +23 -0
  76. package/dist/router.d.ts +31 -0
  77. package/dist/schema.d.ts +149 -0
  78. package/dist/serve-GGEVATIS.js +101 -0
  79. package/dist/server/request-store.d.ts +48 -0
  80. package/dist/server/request-store.js +2 -0
  81. package/dist/static-4LGL4QSH.js +8 -0
  82. package/dist/types.d.ts +112 -0
  83. package/dist/upgrade-HCGQKTSH.js +8 -0
  84. package/dist/vite/index.d.ts +19 -0
  85. package/dist/vite/index.js +41 -0
  86. package/dist/vue/index.d.ts +29 -0
  87. package/dist/vue/index.js +55 -0
  88. package/dist/ws/connection.d.ts +37 -0
  89. package/dist/ws/crypto.d.ts +24 -0
  90. package/dist/ws/hub.d.ts +11 -0
  91. package/dist/ws/types.d.ts +47 -0
  92. package/dist/ws/upgrade.d.ts +9 -0
  93. package/package.json +174 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandre Kohler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.es.md ADDED
@@ -0,0 +1,508 @@
1
+ <div align="center">
2
+
3
+ <a href="https://kwy404.github.io/Raiko.js/">
4
+ <picture>
5
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/kwy404/Raiko.js@main/assets/banner-dark.svg">
6
+ <img src="https://cdn.jsdelivr.net/gh/kwy404/Raiko.js@main/assets/banner.svg" alt="Raiko.js: backend and frontend in one file tree, every runtime" width="820">
7
+ </picture>
8
+ </a>
9
+
10
+ ### Backend y frontend en un solo árbol de archivos. En cualquier runtime.
11
+
12
+ **Un framework de TypeScript con un app router al estilo de Next.js, server components, una API tipada, WebSockets cifrados, autenticación con 2FA, un panel de administración y envío de correos. Funciona en Bun, Node, Deno, Cloudflare Workers y Vercel.**
13
+
14
+ [![npm](https://img.shields.io/npm/v/raiko.js?color=FFD23F&labelColor=0a0a0b&label=npm)](https://www.npmjs.com/package/raiko.js)
15
+ [![CI](https://github.com/kwy404/Raiko.js/actions/workflows/ci.yml/badge.svg)](https://github.com/kwy404/Raiko.js/actions/workflows/ci.yml)
16
+ [![downloads](https://img.shields.io/npm/dm/raiko.js?color=FFD23F&labelColor=0a0a0b)](https://www.npmjs.com/package/raiko.js)
17
+ [![license](https://img.shields.io/badge/license-MIT-FFD23F?labelColor=0a0a0b)](LICENSE)
18
+ [![types](https://img.shields.io/badge/TypeScript-strict-3178C6?labelColor=0a0a0b)](https://kwy404.github.io/Raiko.js/docs/)
19
+
20
+ **[Sitio web](https://kwy404.github.io/Raiko.js/) · [Documentación](https://kwy404.github.io/Raiko.js/docs/) · [Benchmarks](#benchmarks) · [Comparación](#cómo-se-compara) · [GitHub](https://github.com/kwy404/Raiko.js) · [Read in English](README.md) · [Leia em Português](README.pt-BR.md)**
21
+
22
+ </div>
23
+
24
+ ## Empieza con un solo comando
25
+
26
+ ```bash
27
+ npx create-raiko@latest my-app
28
+ # or
29
+ bun create raiko my-app
30
+ ```
31
+
32
+ El instalador te pregunta qué necesitas y genera un proyecto que ya funciona: frontend (Raiko Pages con Voodoo.js, React, Vue, Svelte, Solid, Preact o Angular), CSS (Monsoon, Tailwind, StyleX, UnoCSS), íconos, base de datos (Kastor, Prisma o Drizzle sobre SQLite o PostgreSQL), inicio de sesión (correo y contraseña, GitHub, Google, Discord, GitLab), autenticación de dos factores, proveedor de correo, traducciones, roles de administración, un banner de cookies, una app instalable y un destino de despliegue. Cada combinación se instala, se verifica con el chequeo de tipos, se compila y se arranca en CI.
33
+
34
+ ```bash
35
+ cd my-app
36
+ npm run dev
37
+ ```
38
+
39
+ ## El recorrido de 30 segundos
40
+
41
+ Una API con validación, tipos y documentación OpenAPI a partir de un solo esquema:
42
+
43
+ ```ts
44
+ import { Raiko, t } from 'raiko.js'
45
+
46
+ const app = new Raiko()
47
+ .get('/', 'Hello')
48
+ .get('/users/:id', ({ params }) => ({ id: params.id }))
49
+ .post('/users', ({ body, status }) => status(201, { id: 1, ...body }), {
50
+ body: t.Object({ email: t.String({ format: 'email' }), name: t.String({ minLength: 2 }) }),
51
+ })
52
+
53
+ app.listen(3000)
54
+ export default app
55
+ ```
56
+
57
+ Una página es un server component asíncrono. Lee la base de datos y no envía nada de JavaScript a menos que lo pidas:
58
+
59
+ ```tsx
60
+ // app/blog/[slug]/page.tsx
61
+ import { notFound } from 'raiko.js/server'
62
+ import { db } from '../../../db'
63
+
64
+ export const revalidate = 60
65
+ export async function generateMetadata({ params }) {
66
+ return { title: params.slug }
67
+ }
68
+
69
+ export default async function Post({ params }) {
70
+ const post = await db.posts.findUnique({ where: { slug: params.slug } })
71
+ if (!post) notFound()
72
+ return <article><h1>{post.title}</h1><p>{post.body}</p></article>
73
+ }
74
+ ```
75
+
76
+ El cliente conoce cada ruta, parámetro, body y respuesta:
77
+
78
+ ```ts
79
+ import { client } from 'raiko.js/client'
80
+ import type { App } from './server'
81
+
82
+ const api = client<App>('https://api.example.com')
83
+ const { data, error } = await api.post('/users', { body: { email: 'ana@raiko.dev', name: 'Ana' } })
84
+ // ^ { id: number; email: string; name: string } error.status is 422 with the exact field
85
+ ```
86
+
87
+ ## Contenido
88
+
89
+ - [Por qué Raiko](#por-qué-raiko)
90
+ - [Rutas y handlers](#rutas-y-handlers)
91
+ - [Validación](#validación)
92
+ - [Hooks del ciclo de vida](#hooks-del-ciclo-de-vida)
93
+ - [Estado, decoradores y plugins](#estado-decoradores-y-plugins)
94
+ - [Respuestas y streaming](#respuestas-y-streaming)
95
+ - [App router](#app-router)
96
+ - [Páginas protegidas](#páginas-protegidas)
97
+ - [Componentes](#componentes)
98
+ - [Cliente tipado y hooks](#cliente-tipado-y-hooks)
99
+ - [WebSockets](#websockets)
100
+ - [Auth y 2FA](#auth-y-2fa)
101
+ - [Panel de administración](#panel-de-administración)
102
+ - [Correo electrónico](#correo-electrónico)
103
+ - [Traducciones](#traducciones)
104
+ - [Plugins incluidos](#plugins-incluidos)
105
+ - [Errores que te dicen cómo arreglarlos](#errores-que-te-dicen-cómo-arreglarlos)
106
+ - [Cualquier frontend](#cualquier-frontend)
107
+ - [Despliegue](#despliegue)
108
+ - [Seguridad](#seguridad)
109
+ - [Benchmarks](#benchmarks)
110
+ - [Cómo se compara](#cómo-se-compara)
111
+ - [Problemas de Next.js que resuelve](#problemas-de-nextjs-que-resuelve)
112
+ - [Pruebas](#pruebas)
113
+
114
+ ## Por qué Raiko
115
+
116
+ | | |
117
+ | --- | --- |
118
+ | **App router como en Next.js** | Archivos `page`, `layout`, `loading`, `error`, `not-found`, `forbidden`, `unauthorized`, `route` y `socket`, metadata, form actions e ISR. |
119
+ | **Server components sin React** | TSX asíncrono renderizado a un string o a un stream con `<Suspense>`. Cero JavaScript en el cliente por defecto. |
120
+ | **Interactividad sin build** | Pon `v-data` y `v-on:click` en tu TSX y Voodoo.js se agrega solo a esa página. |
121
+ | **Tipos del servidor al cliente** | Params, query, body y cada status de respuesta llegan a `client<App>()`. Sin generación de código. |
122
+ | **WebSockets en cualquier runtime** | Bun, Node (con su propia implementación de RFC 6455, sin dependencias), Deno y Workers, con salas y validación. |
123
+ | **Mensajes cifrados** | `encrypt: true` agrega intercambio de claves ECDH y AES-GCM, con una clave por dirección y protección contra replay. |
124
+ | **P2P** | Señalización WebRTC y un cliente mesh para data channels y medios. |
125
+ | **Auth** | OAuth con PKCE (GitHub, Google, Discord, GitLab, cualquier OAuth 2), credenciales con PBKDF2, 2FA con TOTP, sesiones JWT firmadas. |
126
+ | **Panel de administración** | CRUD, búsqueda, ordenamiento y paginación con permisos por rol y por acción, para Kastor, Prisma y Drizzle. |
127
+ | **Correo electrónico** | Plantillas en TSX con vista previa en vivo, enviadas por Resend, Postmark, SendGrid o SMTP. |
128
+ | **Errores al estilo Rust** | Cada error tiene un código, el archivo y la línea, el fragmento de código fuente y una línea `help:`. |
129
+ | **Rápido** | Rutas rápidas compiladas para cada ruta y el router nativo de Bun. Mira los [benchmarks](#benchmarks). |
130
+ | **Probado para romperse** | 107 tests, más de 3,4 millones de verificaciones de propiedades generadas, dos auditorías de seguridad, 11 plantillas probadas de punta a punta. |
131
+
132
+ ## Rutas y handlers
133
+
134
+ ```ts
135
+ new Raiko()
136
+ .get('/', 'static text') // constant: served by Bun's native router, no JS per request
137
+ .get('/users/:id', ({ params }) => params.id) // params are typed from the path
138
+ .get('/files/*', ({ params }) => params['*']) // wildcard
139
+ .get('/archive/:year?', ({ params }) => params.year ?? 'all')
140
+ .group('/v1', (v1) => v1.get('/health', 'ok'))
141
+ .all('/echo', ({ request }) => request.method)
142
+ .route('PROPFIND', '/dav', () => 'custom method')
143
+ .mount('/legacy', otherFetchHandler) // mount Hono, another app, any fetch handler
144
+ ```
145
+
146
+ El router es un árbol radix con un hash map al frente para las rutas estáticas, con backtracking para que gane la ruta más específica, y con `HEAD`, `OPTIONS` y `405` automáticos con el header `Allow`.
147
+
148
+ ## Validación
149
+
150
+ `t` construye esquemas con inferencia completa, convierte los strings que llegan de params, query y formularios, y reporta la ruta exacta de cada problema:
151
+
152
+ ```ts
153
+ import { t } from 'raiko.js'
154
+
155
+ const User = t.Object({
156
+ email: t.String({ format: 'email' }),
157
+ age: t.Optional(t.Integer({ minimum: 13 })),
158
+ tags: t.Array(t.String(), { maxItems: 10 }),
159
+ role: t.Enum(['user', 'admin']),
160
+ })
161
+ ```
162
+
163
+ Zod, Valibot y ArkType también funcionan, gracias a Standard Schema: pásalos en cualquier lugar donde iría un esquema `t`.
164
+
165
+ Cuando la validación falla, la respuesta es un `422` con problem details según RFC 9457:
166
+
167
+ ```json
168
+ { "status": 422, "detail": "body.email: Expected a valid email", "errors": [{ "path": ["email"], "on": "body" }] }
169
+ ```
170
+
171
+ ## Hooks del ciclo de vida
172
+
173
+ `onRequest`, `onParse`, `onTransform`, `derive`, validación, `resolve`, `onBeforeHandle`, handler, `onAfterHandle`, `mapResponse`, `onAfterResponse` y `onError`. Un hook se aplica a las rutas registradas después de él. Pasa `'global'` para que también se aplique a la app que use tu plugin.
174
+
175
+ ```ts
176
+ app
177
+ .derive(({ headers }) => ({ token: headers.authorization?.slice(7) }))
178
+ .onBeforeHandle(({ token }) => (token ? undefined : new Response('Unauthorized', { status: 401 })))
179
+ .get('/me', ({ token }) => ({ token }))
180
+ ```
181
+
182
+ ## Estado, decoradores y plugins
183
+
184
+ ```ts
185
+ const db = new Raiko({ name: 'db' }).decorate('db', createDb()).state('hits', 0)
186
+
187
+ const app = new Raiko()
188
+ .use(db) // deduplicated by name
189
+ .get('/hits', ({ store }) => ++store.hits)
190
+ .get('/users', ({ db }) => db.users.findMany())
191
+ ```
192
+
193
+ ## Respuestas y streaming
194
+
195
+ Devuelve lo que quieras: los objetos se convierten en JSON, los strings en texto, el JSX en HTML, los generadores en Server Sent Events, y `Blob`, `File`, `ReadableStream` y `Response` pasan tal cual. `status(201, body)` define el código y tipa el cliente según cada status.
196
+
197
+ ```ts
198
+ import { sse } from 'raiko.js'
199
+
200
+ app.get('/ticks', async function* () {
201
+ for (let i = 0; i < 5; i++) yield sse({ i }, { event: 'tick' })
202
+ })
203
+ ```
204
+
205
+ ## App router
206
+
207
+ ```
208
+ app/
209
+ layout.tsx root layout, metadata
210
+ page.tsx /
211
+ loading.tsx streamed fallback while pages load
212
+ error.tsx error boundary
213
+ not-found.tsx 404
214
+ forbidden.tsx 403
215
+ blog/[slug]/page.tsx /blog/:slug
216
+ docs/[...path]/page.tsx
217
+ (marketing)/about/page.tsx route group, not in the URL
218
+ api/users/route.ts export GET, POST...
219
+ chat/socket.ts WebSocket
220
+ middleware.ts
221
+ ```
222
+
223
+ ```ts
224
+ import { Raiko } from 'raiko.js'
225
+ import { appRouter } from 'raiko.js/app'
226
+
227
+ const app = new Raiko().use(await appRouter({ dir: './app' }))
228
+ ```
229
+
230
+ Las páginas reciben `params` y `searchParams`, exportan `metadata` o `generateMetadata`, `revalidate` para el renderizado en caché y `actions` para formularios (`<Form action="like">` envía a `?/like`). Dentro de cualquier componente: `headers()`, `cookies()`, `usePathname()`, `useParams()`, `useSearchParams()`, `currentUser()`, `notFound()`, `redirect()`, `forbidden()`, `unauthorized()`.
231
+
232
+ `sitemap.xml` y `robots.txt` se generan a partir de tus páginas. En desarrollo, cada página tiene un overlay de errores, un badge de DevTools (ruta, params, tiempo de renderizado, caché) y un verificador de accesibilidad.
233
+
234
+ ## Páginas protegidas
235
+
236
+ ```tsx
237
+ // app/dashboard/page.tsx
238
+ export const auth = true // signed in users only
239
+ export default function Dashboard() { ... }
240
+
241
+ // app/admin/layout.tsx
242
+ export const roles = ['admin'] // every page under app/admin
243
+ ```
244
+
245
+ ```ts
246
+ appRouter({ getUser: async (ctx) => (await auth.getSession(ctx.request))?.user })
247
+ ```
248
+
249
+ Los usuarios sin sesión van a `/login?callbackUrl=...` (o a tu `unauthorized.tsx`). Los usuarios sin el rol reciben `forbidden.tsx` con status 403.
250
+
251
+ ## Componentes
252
+
253
+ `<Link>` con prefetch, `<Image>` con `srcset`, carga diferida y redimensionamiento con sharp cuando está instalado, `<Script>` con estrategias de carga, `<Head>`, `<JsonLd>`, `<SkipLink>` y `<Form>`.
254
+
255
+ ```tsx
256
+ import { Image, Link, JsonLd } from 'raiko.js/components'
257
+
258
+ <Image src="/hero.png" alt="Product screenshot" width={1200} height={630} priority />
259
+ ```
260
+
261
+ ## Cliente tipado y hooks
262
+
263
+ ```ts
264
+ const api = client<App>('', { retry: 2, timeout: 10_000, headers: () => ({ authorization: `Bearer ${token}` }) })
265
+ ```
266
+
267
+ Hooks de React y Vue para queries, mutations y sockets:
268
+
269
+ ```tsx
270
+ import { useQuery, useSocket } from 'raiko.js/react' // or 'raiko.js/vue'
271
+
272
+ const users = useQuery(() => api.get('/users'), { key: 'users' })
273
+ const chat = useSocket(() => api.ws('/chat'), (msg) => setMessages((m) => [...m, msg]))
274
+ ```
275
+
276
+ ## WebSockets
277
+
278
+ ```ts
279
+ app.ws('/chat/:room', {
280
+ body: t.Object({ text: t.String({ maxLength: 500 }) }),
281
+ encrypt: true,
282
+ open: (ws) => ws.subscribe(ws.data.params.room),
283
+ message: (ws, msg) => ws.publish(ws.data.params.room, { from: ws.id, text: msg.text }),
284
+ })
285
+ ```
286
+
287
+ El cliente del navegador se reconecta con backoff, encola los mensajes mientras no hay conexión y se encarga del intercambio de claves. Con `encrypt: true`, las claves se acuerdan con ECDH P-256, cada dirección obtiene su propia clave AES-GCM 256 derivada con HKDF y los nonces son contadores de frames, así que se rechazan los frames repetidos, reordenados o reflejados. Todo esto corre sobre TLS (`wss://`): mantiene el contenido de los mensajes fuera del alcance de proxies y logs, pero no del propio navegador del usuario.
288
+
289
+ P2P con WebRTC:
290
+
291
+ ```ts
292
+ import { signaling } from 'raiko.js/p2p'
293
+ app.use(signaling({ path: '/p2p/:room' }))
294
+
295
+ // browser
296
+ import { joinRoom } from 'raiko.js/p2p/client'
297
+ const room = joinRoom(api.ws('/p2p/lobby'))
298
+ room.on('message', (from, data) => console.log(from, data))
299
+ room.send({ hi: true })
300
+ ```
301
+
302
+ ## Auth y 2FA
303
+
304
+ ```ts
305
+ import { raikoAuth, GitHub, Google, Credentials, verifyPassword } from 'raiko.js/auth'
306
+
307
+ export const auth = raikoAuth({
308
+ secret: process.env.AUTH_SECRET!,
309
+ providers: [
310
+ GitHub({ clientId, clientSecret }),
311
+ Credentials({ authorize: async ({ email, password }) => /* your lookup */ null }),
312
+ ],
313
+ twoFactor: { getSecret: (user) => user.totpSecret },
314
+ })
315
+
316
+ app.use(auth.plugin).get('/me', ({ auth }) => auth?.user, { beforeHandle: auth.protect })
317
+ ```
318
+
319
+ OAuth usa PKCE y un state firmado. Las contraseñas usan PBKDF2 SHA-256 con 210.000 iteraciones. TOTP funciona con Google Authenticator, Authy y 1Password, permite cinco intentos por inicio de sesión y rechaza un código ya usado. `generateTotpSecret()`, `totpUri()` y `recoveryCodes()` te ayudan a construir la pantalla de configuración.
320
+
321
+ ## Panel de administración
322
+
323
+ ```ts
324
+ import { admin, modelAdapter } from 'raiko.js/admin'
325
+
326
+ app.use(admin({
327
+ auth: ({ auth }) => auth?.user.role ?? null,
328
+ resources: {
329
+ users: {
330
+ adapter: modelAdapter(db.users, { searchable: ['email'] }),
331
+ fields: ['id', 'email', 'name', 'role'],
332
+ hidden: ['passwordHash'],
333
+ permissions: { admin: '*', support: ['read', 'update'], viewer: ['list'] },
334
+ },
335
+ },
336
+ }))
337
+ ```
338
+
339
+ Cada acción (`list`, `read`, `create`, `update`, `delete`) se verifica por separado, así que un rol de soporte puede corregir un usuario sin poder listarlo ni eliminarlo. `modelAdapter` funciona con Kastor y Prisma, y `drizzleAdapter` con Drizzle.
340
+
341
+ ## Correo electrónico
342
+
343
+ ```tsx
344
+ // emails/welcome.tsx
345
+ /** @jsxImportSource raiko.js */
346
+ export default function Welcome({ name }: { name: string }) {
347
+ return <h1>Welcome, {name}</h1>
348
+ }
349
+ Welcome.subject = (p) => `Welcome, ${p.name}`
350
+ Welcome.preview = { name: 'Ana' }
351
+ ```
352
+
353
+ ```ts
354
+ import { createMailer, resend } from 'raiko.js/mail'
355
+
356
+ const mailer = createMailer({ from: 'hi@my.app', transport: resend({ apiKey }), templates: { welcome: Welcome } })
357
+ await mailer.send({ to: 'ana@x.io', template: 'welcome', props: { name: 'Ana' } })
358
+ app.use(mailer.preview()) // /_raiko/mail lists and renders every template
359
+ ```
360
+
361
+ Para cada correo se genera una versión en texto plano. Las plantillas también pueden devolver HTML de React Email, Vue o cualquier string.
362
+
363
+ ## Traducciones
364
+
365
+ ```ts
366
+ import { createI18n } from 'raiko.js/i18n'
367
+
368
+ const i18n = createI18n({ en, 'pt-BR': ptBR }, { fallback: 'en', cookie: 'locale' })
369
+ i18n.t('pt-BR', 'cart.items', { count: 3 }) // "3 itens", plurals with Intl.PluralRules
370
+ ```
371
+
372
+ Las claves se tipan a partir de tu diccionario de respaldo. La detección del idioma lee el prefijo de la URL, una cookie y `Accept-Language`.
373
+
374
+ ## Plugins incluidos
375
+
376
+ Desde `raiko.js/plugins`: `cors`, `secureHeaders`, `rateLimit`, `csrf`, `logger`, `requestId`, `compress`, `etag`, `responseCache`, `health`, `staticFiles` y `openapi` (OpenAPI 3.1 con una página de documentación en `/docs`).
377
+
378
+ ## Errores que te dicen cómo arreglarlos
379
+
380
+ ```
381
+ error[RK0104]: two routes use different param names at the same position
382
+ --> app/blog/[slug]/page.tsx
383
+ |
384
+ 3 | app/blog/[id]/page.tsx
385
+ | ^^^^ already declared as [slug]
386
+ |
387
+ = help: rename the folder to [slug], or move one of the pages
388
+ ```
389
+
390
+ Cada error con código (`RK0100` en adelante) tiene su página en la [referencia de errores](https://kwy404.github.io/Raiko.js/docs/errors.html).
391
+
392
+ ## Cualquier frontend
393
+
394
+ Las Raiko Pages se renderizan en el servidor. Para una single page app, el plugin de Vite ejecuta el backend dentro del servidor de desarrollo de Vite, así React, Vue, Svelte, Solid, Preact o Angular comparten un solo puerto con la API:
395
+
396
+ ```ts
397
+ // vite.config.ts
398
+ import react from '@vitejs/plugin-react'
399
+ import { raiko } from 'raiko.js/vite'
400
+
401
+ export default { plugins: [react(), raiko({ entry: './server/index.ts' })] }
402
+ ```
403
+
404
+ Los builds de producción salen minificados y ofuscados, sin source maps. La ofuscación hace más lenta la lectura del código, pero no la impide: los secretos y las reglas se quedan en el servidor.
405
+
406
+ ## Despliegue
407
+
408
+ | Destino | Cómo |
409
+ | --- | --- |
410
+ | Bun, Node, Deno | `app.listen(3000)`, o `raiko build` y luego `raiko start` |
411
+ | Varios núcleos | `app.listen(3000, { cluster: true })`: un proceso por núcleo en el mismo puerto |
412
+ | Vercel | `export default { fetch: app.fetch }` en `api/index.ts` (el instalador lo escribe) |
413
+ | Cloudflare Workers | `export default { fetch: app.fetch }`, `wrangler deploy` |
414
+ | Docker | el instalador escribe un `Dockerfile` multi stage que no corre como root |
415
+
416
+ `raiko deploy` elige el destino según tu proyecto.
417
+
418
+ ## Seguridad
419
+
420
+ Valores por defecto que resisten ataques, cada uno respaldado por un test:
421
+
422
+ - El HTML se escapa en todas partes, y los nombres de atributos y etiquetas se validan.
423
+ - El parseo de query, cookies y formularios usa objetos con prototipo null (sin prototype pollution).
424
+ - Los archivos estáticos y el endpoint de imágenes rechazan path traversal.
425
+ - Las cookies se pueden firmar, y las firmas tienen una única codificación canónica.
426
+ - `cors({ credentials: true })` se niega a funcionar sin una lista explícita de orígenes.
427
+ - Las redirecciones después del inicio de sesión y de las form actions se quedan en tu sitio.
428
+ - Los límites de body se aplican a las subidas chunked y multipart.
429
+ - Los errores de validación de respuestas nunca envían datos del servidor al cliente.
430
+ - La caché nunca le sirve a un usuario la respuesta de otro.
431
+ - El rate limiter no confía en `X-Forwarded-For` a menos que se lo indiques.
432
+
433
+ Dos auditorías independientes encontraron 21 problemas antes de esta versión. Todos están corregidos, y cada uno tiene un test que reproduce el ataque. Reporta una vulnerabilidad a través de [SECURITY.md](SECURITY.md).
434
+
435
+ ## Benchmarks
436
+
437
+ Requests por segundo, más es mejor. Las mismas tres rutas en cada framework, sobre Bun, por HTTP real con autocannon (64 conexiones, pipelining 4), mediana de tres rondas.
438
+
439
+ <!-- bench:start -->
440
+ | Server rendered page | Raiko.js (Bun) | Raiko.js (Node) | Next.js 15 (Node) |
441
+ | --- | ---: | ---: | ---: |
442
+ | req/s | 8,910 | 3,737 | 155 |
443
+
444
+ | Route | Raiko.js | Elysia 1.4 | Hono 4.13 |
445
+ | --- | ---: | ---: | ---: |
446
+ | text | 99,040 | 83,192 | 63,920 |
447
+ | params + json | 66,943 | 75,723 | 60,477 |
448
+ | json body | 51,136 | 53,168 | 47,029 |
449
+
450
+ AMD EPYC 9J45, 1 vCPU, 1.9 GB RAM, Linux 6.8, Bun 1.4.4, Node 22.23. autocannon -c 64 -p 4, median of 3 rounds.
451
+ <!-- bench:end -->
452
+
453
+ Notas honestas: la máquina tiene una sola vCPU, así que el generador de carga y el servidor la comparten y los números quedan comprimidos. En las rutas estáticas, Raiko y Elysia responden desde el router nativo de Bun sin ejecutar JavaScript, por lo que empatan en el techo. Ejecuta `bun bench/http.ts` en tu propio hardware.
454
+
455
+ ## Cómo se compara
456
+
457
+ | | Raiko.js | Elysia | Next.js | Hono |
458
+ | --- | --- | --- | --- | --- |
459
+ | Runtimes | Bun, Node, Deno, Workers, Vercel | Bun primero | Node, Vercel | Todos |
460
+ | App router, layouts, loading, error | Sí | No | Sí | No |
461
+ | Server components | Sí, sin necesidad de React | No | Sí | Solo JSX |
462
+ | Cliente tipado | Sí | Sí (Eden) | Server actions | Sí (RPC) |
463
+ | WebSockets en cualquier runtime | Sí | Bun | No | Según el adaptador |
464
+ | Mensajes de WebSocket cifrados | Sí | No | No | No |
465
+ | Auth con OAuth y 2FA | Incluido | Plugins | NextAuth | Middleware |
466
+ | Panel de administración con permisos | Incluido | No | No | No |
467
+ | Plantillas de correo con vista previa | Incluido | No | No | No |
468
+ | Tamaño del ecosistema | Nuevo | En crecimiento | Enorme | Grande |
469
+
470
+ ## Problemas de Next.js que resuelve
471
+
472
+ - **Un solo proceso para las páginas y la API.** Sin backend aparte, sin tipos duplicados.
473
+ - **No necesitas React en el servidor.** Las páginas son funciones asíncronas comunes que devuelven TSX.
474
+ - **WebSockets de verdad.** Next.js no tiene servidor de WebSockets, y Raiko tiene uno en cada runtime.
475
+ - **Caché predecible.** Solo se guardan en caché las páginas que exportan `revalidate`, nunca las requests personalizadas.
476
+ - **Funciona en cualquier lado.** La misma app se despliega en Bun, Node, Deno, Workers y Vercel sin un proyecto adaptador.
477
+ - **Errores con los que puedes hacer algo.** Errores con código y su solución, no un stack trace minificado.
478
+ - **Auth, administración y correo incluidos.** Lo que todo producto necesita no son librerías separadas que tengas que conectar.
479
+
480
+ ## Pruebas
481
+
482
+ ```ts
483
+ const res = await app.request('/users', { json: { email: 'a@b.co', name: 'Ana' } })
484
+ expect(res.status).toBe(201)
485
+ ```
486
+
487
+ Sin servidor, sin puerto: `app.request()` y `app.fetch()` ejecutan el ciclo de vida completo en memoria.
488
+
489
+ ## Más proyectos de kwy404
490
+
491
+ | Proyecto | Qué hace |
492
+ | --- | --- |
493
+ | [Voodoo.js](https://github.com/kwy404/Voodoo.js) | JSX en HTML plano sin paso de build, usado para la interactividad en Raiko Pages |
494
+ | [Monsoon](https://github.com/kwy404/monsoon) | CSS utility first sin paso de build, un tema a partir de un solo color |
495
+ | [glyphfox](https://github.com/kwy404/glyphfox) | Íconos transformables para cualquier framework |
496
+ | [Kastor](https://github.com/kwy404/kastor) | El ORM de TypeScript para casi cualquier base de datos |
497
+ | [a11ymedic](https://github.com/kwy404/a11ymedic) | Verificador de accesibilidad que explica cómo arreglar cada problema |
498
+ | [cookieowl](https://github.com/kwy404/cookieowl) | Consentimiento de cookies para LGPD y GDPR que de verdad bloquea los trackers |
499
+ | [installkite](https://github.com/kwy404/installkite) | Haz que cualquier sitio web sea instalable y funcione sin conexión |
500
+ | [edgegoat](https://github.com/kwy404/edgegoat) | Entradas de prueba para casos límite en JavaScript |
501
+
502
+ ## Autor
503
+
504
+ Creado por **Alexandre Kohler** ([GitHub](https://github.com/kwy404)).
505
+
506
+ ## Licencia
507
+
508
+ [MIT](LICENSE)