Otro framework¶
React y Svelte tienen su adaptador generado, pero el cliente no está atado a ellos: los dos están hechos sobre el núcleo, que no depende de nada, y cualquier otro framework de TypeScript —Vue, Solid, Angular, Lit o ninguno— puede usar el núcleo tal cual. Esta página es el contrato: lo que da el núcleo y lo que tiene que hacer un adaptador.
Lo que da el núcleo¶
| Qué | De dónde | Para qué |
|---|---|---|
createTaiClient() |
./api |
Las llamadas, la sesión y los tipos |
api.session.restore() |
Recuperar la sesión al arrancar | |
api.session.subscribe(fn) |
Avisa con el estado ahora y en cada cambio; devuelve la función para dejar de escuchar. Es el contrato de store de Svelte, y lo que necesita useSyncExternalStore de React |
|
api.session.getSnapshot() / .state |
El estado actual | |
sessionView(api.session, estado) |
./api |
La vista de la sesión de una pantalla: user, loading, isAuthenticated, login, logout, hasPermission… |
hasPermission(user, p), can(user, tabla, acción) |
./api |
Los permisos, como funciones puras del usuario |
defineRoutes, resolveRoutes |
./api/routes |
La declaración de pantallas, y la misma con lo heredado ya aplicado |
access(resueltas, pathname, vista) |
./api/routes |
Qué hacer en una URL: loading, login, underConstruction, forbidden o allowed |
navigation, breadcrumbs, currentRoute |
./api/routes |
El menú, las migas y la pantalla actual |
safeReturnPath(from) |
./api/routes |
Adónde volver tras el login, sin salir del sitio |
Son las mismas funciones que usan por dentro react/ y svelte/: un adaptador nuevo decide lo
mismo que los otros dos con la misma declaración.
Lo que hace un adaptador¶
Tres cosas, y ninguna más:
- Dar el cliente a los componentes y recuperar la sesión al arrancar. En React es un provider; en Svelte, el contexto; en otros, una inyección de dependencias o un módulo con la instancia.
- Convertir la sesión en estado del framework: escuchar con
api.session.subscribey entregarsessionView(api.session, estado)cada vez que cambia. - Llevar la regla de
access()al router: antes de entrar en una URL —y otra vez cuando cambia la sesión—, ir al login, enseñar la 404 o «en construcción», o dejar pasar. Y recalcular el menú y las migas cuando cambian la sesión o la URL.
Ejemplo: Vue 3 con vue-router¶
La sesión¶
// src/composables/sesion.ts
import { onScopeDispose, shallowRef, type ShallowRef } from 'vue'
import { sessionView, type SessionView } from '@/lib/api'
import { api } from '@/lib/tai'
export function useSession(): ShallowRef<SessionView> {
const vista = shallowRef(sessionView(api.session))
const dejar = api.session.subscribe((estado) => { vista.value = sessionView(api.session, estado) })
onScopeDispose(dejar)
return vista
}
<script setup lang="ts">
const session = useSession()
</script>
<template>
<button v-if="session.hasPermission(PERMISSIONS.GESTION_USUARIOS.CREATE)">Nuevo usuario</button>
</template>
Las pantallas¶
// src/router.ts
import { createRouter, createWebHistory } from 'vue-router'
import { access, resolveRoutes } from '@/lib/api/routes'
import { sessionView } from '@/lib/api'
import { api } from '@/lib/tai'
import { routes } from '@/routes'
const resueltas = resolveRoutes(routes)
export const router = createRouter({ history: createWebHistory(), routes: [/* las de vue-router */] })
router.beforeEach(async (to) => {
if (api.session.state.status === 'unknown') await api.session.restore()
const acceso = access(resueltas, to.path, sessionView(api.session))
if (acceso.status === 'login') return acceso.redirectTo
if (acceso.status === 'forbidden') return { name: 'not-found' }
if (acceso.status === 'underConstruction') return { name: 'en-construccion' }
return true
})
// Al cerrarse la sesión, la pantalla actual se vuelve a comprobar: lleva al login.
// `force`: sin él, vue-router no vuelve a navegar a la URL en la que ya está, y no pasa por el guard.
api.session.onSessionEnd(() => {
const { path, query } = router.currentRoute.value
void router.replace({ path, query, force: true })
})
El menú¶
import { computed } from 'vue'
import { navigation } from '@/lib/api/routes'
const session = useSession()
const items = computed(() => navigation(resueltas, session.value.hasPermission, session.value.isAuthenticated))
Sin framework¶
Lo mismo, a mano:
await api.session.restore()
api.session.subscribe((estado) => {
const vista = sessionView(api.session, estado)
pintarMenú(navigation(resueltas, vista.hasPermission, vista.isAuthenticated))
const acceso = access(resueltas, location.pathname, vista)
if (acceso.status === 'login') location.assign(acceso.redirectTo)
})
Un adaptador para todos¶
Si un framework se usa en más de un proyecto, lo que tiene sentido es que su adaptador se genere
como los de React y Svelte —con "<framework>": true en el bloque frontend— en vez de copiarse
de proyecto en proyecto. Es un adaptador más sobre el mismo núcleo, con su test gemelo: el que
comprueba que decide lo mismo que los otros dos con las mismas declaraciones.