El cliente TypeScript¶
tai-api generate escribe, dentro de tu frontend, todo lo que hace falta para hablar con la
API: los tipos de cada tabla, una función por endpoint, la sesión ya resuelta, los permisos, las
consultas de TanStack Query, los esquemas de los formularios y el cuadro de luces de las
pantallas —qué pantallas hay, cuáles están activas y qué permiso pide cada una—.
Si cambia el schema o el RBAC, tai-api generate cambia la API y el cliente en la misma ejecución,
y el compilador de TypeScript te señala en el frontend lo que se rompió.
La regla de oro
El cliente no se edita: se regenera. Lo tuyo —tus pantallas, tu declaración de rutas, tus componentes— vive fuera de su carpeta y lo importa.
Un núcleo y un adaptador por framework¶
El cliente está hecho en dos capas, para que sirva igual en React, en Svelte o en cualquier otro framework de TypeScript:
- El núcleo, que no depende de nada: las llamadas, la sesión, los permisos y la lógica del cuadro de luces (qué hacer en cada URL, el menú, las migas). Es el mismo para todos.
- Un adaptador por framework, que solo pone el núcleo en su idioma: en React, un provider, unos hooks y las rutas de react-router; en Svelte, el contexto y unos stores. Los dos adaptadores son gemelos: las mismas piezas, con los mismos nombres donde se puede, y deciden lo mismo con la misma declaración.
Para otro framework —Vue, Solid, Angular…— no hace falta esperar a que exista su adaptador: el núcleo es lo que usan los otros dos, y uno propio son unas pocas líneas. Ver Otro framework.
El protocolo, paso a paso¶
Un frontend con el cliente se monta siempre igual, sea cual sea el framework. Cada página de esta sección es un paso, y en cada una el código está en pestañas: React, Svelte y, donde hace falta, Otro framework. Elige la tuya en cualquier página y se queda elegida en todas.
| Paso | Qué se hace | Dónde |
|---|---|---|
| 1 | Activar el cliente en la API y generarlo | abajo |
| 2 | Una instancia del cliente y la sesión en la raíz de la aplicación; la página de login | Montar la aplicación |
| 3 | Declarar las pantallas: ruta, nombre, icono, activa o en construcción y permiso | Las pantallas |
| 4 | Protegerlas, y sacar de la misma declaración el menú y las migas | Las pantallas |
| 5 | La sesión y los permisos dentro de cada pantalla | La sesión y los permisos |
| 5b | Si la aplicación administra usuarios: api.rbac(realm) |
La gestión de usuarios |
| 6 | Pedir datos a la API, con sus tipos | Llamadas y tipos |
| 7 | La caché de las consultas | TanStack Query |
| 8 | Los formularios | Formularios |
| 9 | Servirlo junto a la API | Despliegue |
Si tu frontend ya tiene la autenticación escrita a mano, Migrar dice qué se borra y qué se queda; si ya usa el cliente de tai-api 0.6, qué cambia (Desde el cliente 0.6).
Activarlo¶
En el .taiapiproject de la API, el bloque frontend:
{
"frontend": {
"output_dir": "../app/frontend/src/lib/api",
"base_path": "/api",
"tanstack": true,
"zod": true,
"svelte": true
}
}
| Clave | Qué es | Por defecto |
|---|---|---|
output_dir |
Dónde se escribe, relativo al .taiapiproject |
obligatorio |
base_path |
De dónde cuelga la API vista desde el navegador | /api |
tanstack |
Las consultas y mutaciones de TanStack Query de cada tabla | true |
zod |
Los esquemas zod de alta y edición de cada tabla | true |
react |
El adaptador de React, con react-router | false |
svelte |
El adaptador de Svelte (Svelte 4 o 5, con SvelteKit o sin él) | false |
Se pueden activar los dos adaptadores, o ninguno: el núcleo siempre se genera. Después,
tai-api generate. Sin el bloque no se escribe nada fuera de la API.
Con autenticación, exige la sesión con cookie
El cliente guarda el access token en memoria y el refresh en una cookie que el JavaScript
no puede leer. Por eso generate se niega si la API tiene autenticación y no declara
"auth": { …, "session": { "transport": "cookie" } }. Ver La sesión con cookie.
Lo que escribe¶
src/lib/api/ ← output_dir: es del generador, no lo toques
├── index.ts createTaiClient(), PERMISSIONS, hasPermission y los tipos
├── vite.ts taiApiProxy(): el proxy /api para Vite
├── core/ el fetch, los errores y los tipos comunes
├── auth/
│ ├── session.ts login, logout, refresco y la vista de la sesión (sessionView)
│ └── permissions.ts PERMISSIONS, sacado de tus rbac/<realm>.py, y can / hasPermission
├── routes/ el cuadro de luces sin framework: access, navigation, breadcrumbs
├── react/ con "react": true — TaiProvider, useSession, renderRoutes, useNavigation…
├── svelte/ con "svelte": true — setTaiClient, getSession, accessStore, navigationStore…
└── <schema>/ uno por schema: public/, …
├── models.ts los tipos: LoteRead, LoteCreate, LoteUpdateValues, LoteFilters…
├── resources.ts una función por endpoint
├── query.ts opciones de TanStack Query
└── zod.ts esquemas de formulario
Cada fichero abre con una marca de «no editar». Un fichero sin ella no se reescribe (se avisa), y puedes tener tus propios ficheros en la carpeta: no se tocan.
Lo que necesita tu frontend¶
El núcleo no depende de nada, y compila con el tsconfig que crea Vite para un proyecto nuevo
(react-ts y svelte-ts: erasableSyntaxOnly, noUnusedLocals, verbatimModuleSyntax…) y con
lo más estricto que se le puede sumar (noUncheckedIndexedAccess, exactOptionalPropertyTypes,
noImplicitOverride). Lo demás, solo si lo activas:
| Para | React | Svelte |
|---|---|---|
| El adaptador | react 19 y react-router-dom 7 |
svelte 4 o 5 |
"tanstack": true |
@tanstack/react-query 5 |
@tanstack/svelte-query |
"zod": true |
zod 4 |
zod 4 |
tai-api generate nunca ejecuta npm, pero lee el package.json más cercano a output_dir:
si el bloque frontend activa algo que el frontend no tiene —"svelte": true en un proyecto de
React, "zod": true sin zod— falla antes de escribir nada y dice qué instalar o qué quitar, porque
ese cliente no compilaría. Sin package.json, avisa y genera. "tanstack": true no se comprueba:
query.ts no importa TanStack.
Las piezas, en cada framework¶
| Para | Núcleo (cualquier framework) | React | Svelte |
|---|---|---|---|
| Crear el cliente | createTaiClient() |
||
| Ponerlo al alcance de la aplicación y recuperar la sesión | api.session.restore() |
<TaiProvider client={api}> |
setTaiClient(api) |
| El cliente desde un componente | useTai() |
getTai() |
|
| La sesión como estado | sessionView(api.session) + api.session.subscribe |
useSession() |
getSession() → $session |
| Un permiso | hasPermission(user, p) |
hasPermission(p), <Can permission> |
$session.hasPermission(p) |
| Declarar las pantallas | defineRoutes |
defineRoutes (con element) |
defineRoutes |
| Qué hacer en una URL | access(…) |
renderRoutes(routes, { layout, … }) |
accessStore(routes, page) |
| El menú | navigation(…) |
useNavigation(routes) |
navigationStore(routes) |
| Las migas y el título | breadcrumbs(…), currentRoute(…) |
useBreadcrumbs, useCurrentRoute |
breadcrumbsStore, currentRouteStore |
| Volver tras el login | safeReturnPath(from) |
location.state.from |
?from= + safeReturnPath |