Novedades¶
Lo que cambia al actualizar tai-api, empezando por lo que rompe y qué hacer con ello. Después de
actualizar, siempre: tai-api generate (la API y el cliente TypeScript), tai-api rbac push si
cambian permisos y tai-api rules install para las reglas del asistente.
Sin publicar¶
Lo que se arregla¶
El aviso de root_path al arrancar la API. Desde FastAPI 0.146, FastAPI(root_path=...) está
desaprobado («Configure the server's root path instead»), y la API generada lo usaba: avisaba al
arrancar, y una versión de FastAPI que quite el parámetro la dejaría sin arrancar. Ahora la app se
construye sin él, y un middleware ASGI de la propia app (RootPath, en app/__init__.py) pone el
ROOT_PATH de config.py en cada petición, HTTP y WebSocket, como hacía FastAPI. No depende de cómo
se arranque (tai-api dev, uvicorn en el contenedor, __main__.py), y ROOT_PATH sigue en un solo
sitio: de él cuelgan la ruta de la cookie de sesión y los enlaces de la documentación.
Nada rompe. Tras actualizar, tai-api generate. Lo único distinto: app.root_path ya no lleva el
ROOT_PATH (el de cada petición está en request.scope["root_path"], como antes).
POST /<tabla>/bulk lento en una tabla con owner(). Después de insertar, el endpoint
comprobaba que cada fila creada fuera del usuario con una consulta por fila, y lo creado anidado con
otras dos por fila: mil registros eran mil consultas, o miles con hijos anidados, cada una un viaje a
la base de datos. Ahora es una consulta por tabla y nivel para todo el lote. En local, 10.000
registros pasan de 12-18 s a unos 2 s, y mil publicaciones con dos etiquetas cada una, de 4.000
consultas a 3. Con una base de datos remota la diferencia es mucho mayor: lo que se ahorra son los
viajes.
Nada rompe. Lo único nuevo es que el 403 OWNER de /bulk dice qué registro fue: su field es la
posición (3), o la posición y la relación si es algo creado anidado (3.etiquetas), que antes ya
llevaba. Basta con tai-api generate.
Lo que se crea vuelve sin sus columnas vectoriales, como lo que se lee. POST /<tabla>, /bulk
y el create de _tx devolvían el vector entero de cada fila creada, aunque ninguna lectura lo
trae: mil filas con un vector de 3.072 dimensiones eran decenas de MB de respuesta. Ahora sale a
null, y se sigue guardando. Lo hace el DAO de tai-sql (create/create_many con
include_vectors=False por defecto, en la versión siguiente a la 0.8.0); tai-api no lo pide, así
que al regenerar con ella basta. Lo único que rompe es leer el vector de la respuesta de un alta, que
era el único sitio de la API donde salía.
0.7.2¶
Lo nuevo¶
Lo propio en el cliente TypeScript, como routers/custom/ en la API. Los endpoints de
routers/custom/ se declaran en <output_dir>/custom/, que es del proyecto: tai-api generate no
lo escribe, no lo borra y no lo retira. custom/index.ts exporta la lista de dominios con
defineCustomResources, y createTaiClient la registra junto a los schemas, con sus tipos en
TaiClient (y en useTai()):
// custom/index.ts
export const customResources = defineCustomResources({
informes: informesResources, // routers/custom/informes → api.informes
facturas: facturasResources(REALM), // un dominio que necesita algo más: lo recibe y devuelve el dominio
})
Ver Lo propio. generate avisa de un custom/index.ts que no exporta
customResources —no lo registra— y de un import de custom/ que llega al index.ts del cliente,
que sería un ciclo.
Lo que rompe¶
- Un schema no puede llamarse
custom: es una carpeta reservada del cliente, comocoreoauth.generatefalla con la solución antes de escribir nada. - Un dominio propio no puede llamarse como algo del cliente:
http, un schema y, con autenticación,sessionorbac. No compila (satisfies), en vez de pisarlo en silencio. Si tenías la gestión de/rbacescrita a mano encustom/, quítala: el cliente la genera desde la 0.7.1 (api.rbac(realm)). - Un
custom/index.tsque creaba el cliente (createTaiClient()dentro decustom/, el apaño de antes) no se registra: pásalo a la lista dedefineCustomResourcesy crea la instancia fuera.
Tras actualizar, tai-api generate y tai-api rules install.
0.7.1¶
Lo nuevo¶
tai-api dev se nota en el frontend. Con la autorización apagada (tai-api dev, sin
--auth), toda respuesta de la API lleva X-Tai-Auth: off, y el cliente TypeScript lo expone en
session.state.authDisabled —en useSession() y en getSession()— y avisa una vez por consola
(createTaiClient({ onAuthDisabled: false }) lo calla). Hasta ahora nada lo decía: el frontend
exigía login y aplicaba los permisos del usuario mientras la API los ignoraba, sin un 403.
Con el frontend, tai-api dev sirve para desarrollar pantallas con un usuario de verdad: hace falta
el schema auth con usuarios (tai-sql push -s auth --with-feed y tai-api rbac push), y la
interfaz usa los permisos reales aunque la API no los compruebe. Para probar permisos y RLS,
tai-api dev --auth. Ver Contra tai-api dev.
Nada rompe: la cabecera es nueva y solo aparece con la autorización apagada, y SessionState gana
un campo. Tras actualizar, tai-api generate (la API y el cliente) y tai-api rules install.
0.7.0¶
El cliente TypeScript, rehecho a partir del primer proyecto que lo adoptó entero, y lo que ese
proyecto le pidió a la API: transacciones de varias tablas, el registro en la respuesta de un PATCH,
un modo estricto y la gestión de usuarios en el cliente.
Requiere tai-sql 0.8.0. Es la que trae buena parte de lo de abajo: las escrituras anidadas de un
update en orden delete → update → create (quitar y volver a poner un hijo de clave natural en
el mismo PATCH ya no es un 409), acotadas a la fila que se actualiza y con el RLS del usuario; un
update que solo trae hijos se aplica (antes era un 404); in_<pk>; un in_ vacío que no casa con
ninguna fila; bytes en base64; y las agregaciones imposibles como error.
Lo que rompe en la API¶
Afecta a cualquier frontend, use o no el cliente TypeScript.
| Qué | Hasta la 0.6.2 | Ahora | Qué hacer |
|---|---|---|---|
Escrituras anidadas (los hijos dentro del POST o del PATCH del padre, también en /bulk) |
Bastaba el permiso del padre, y un update/delete anidado no miraba el RLS ni el owner() de la hija |
Piden el permiso de la tabla hija y le aplican su RLS y su owner(): 403 con details.required_permission y field en el anidado |
Dar al rol <hija>-create/-update/-delete (o <hija>-admin) en rbac/<realm>.py y tai-api rbac push. Ver Escrituras anidadas |
| Una hija anidada que no existe, no se alcanza o es de otro padre | Se ignoraba, era un 500 o se aplicaba a la fila de otro padre | 404 con field en el anidado, sin escribir nada |
Corregir la clave que se envía |
Un update anidado que cambia la relación con el padre (mueve la hija a otro) |
Se aplicaba | 422 con field en la columna |
Moverla por el PATCH de su tabla |
Una agregación imposible (mean de un texto, max de un enumerado, agrupar por un decimal) |
200, y la clave faltaba en data |
422 con el motivo, la petición entera; max/min de un texto, que antes se descartaban, ahora valen |
Quitar la operación, o contar con group_by |
Columnas bytes |
Se guardaba el texto que llegaba y se devolvía tal cual; un binario de verdad era un 500 | Base64 estándar al leer (lo ya guardado como texto base64 sale igual); al escribir, base64 sin el prefijo data:…;base64, (con él, o lo que no es base64, 422) |
Quitar el prefijo data: al enviar (Los tipos) |
PATCH /{tabla} masivo con relaciones en values |
Se perdían en silencio, o se aplicaban una vez por fila según la tabla | 422 sobre values. El cuerpo es <Clase>UpdateMany, sin relaciones |
Quitarlas: los hijos, por el PATCH de cada padre o por su tabla |
Un in_ vacío en un filtro de cuerpo (PATCH masivo, agg, los masivos de _tx) |
Contaba como «sin filtro»: el PATCH actualizaba todas las filas del usuario sin confirm_all, y agg agregaba la tabla entera |
No casa con ninguna fila: 0, o la agregación de cero filas | Nada, salvo que se buscara eso: entonces, sin filtro y con confirm_all |
| El OpenAPI y la marca del contrato | — | El componente del cuerpo del masivo pasa de <Clase>Update a <Clase>UpdateMany; X-Tai-Contract cambia en todos los schemas y gana auth= |
Regenerar los clientes hechos desde el OpenAPI |
Nada más cambia sin pedirlo: lo nuevo de la API va con una cabecera (Prefer, X-Tai-Strict) o en
un endpoint nuevo (_tx, el DELETE de rol por ruta).
Lo que rompe en el cliente TypeScript¶
El cliente no es compatible con el de la 0.6: las mutaciones de TanStack son factorías, las keys
cambian de forma, <Tabla>Read deja de tener relaciones (el tipo sale de los includes), el realm
es un tipo y el modo estricto va por defecto. Paso a paso, en
Migrar desde el cliente 0.6.
Y tai-api generate falla (A012) si el bloque frontend activa un adaptador o zod que el
package.json del frontend no tiene: antes generaba un cliente que no compilaba.
Lo nuevo¶
La API
POST /{schema}/_tx: hasta 100 escrituras de varias tablas en una transacción, con$refa lo creado por una anterior y el permiso de cada operación (Escritura).Prefer: return=representationenPATCH /{tabla}/{clave}: el registro actualizado, con susincludes, en la misma petición.X-Tai-Strict: 1: lo que la API ignoraría —un filtro, unorder_byo unincludesque no existen— es un 422 (El modo estricto).DELETE /rbac/{realm}/user/{username}/role/{role_name}, sin cuerpo (la del cuerpo se conserva).in_<pk>: la clave autoincremental se filtra por lista (?in_id=3&in_id=7), también en los masivos y enagg; no tiene igualdad ni rango (Filtros).
El cliente TypeScript
- Compila con el
tsconfigy pasa el eslint de las plantillas de Vite. - El registro con los
includesque se pidieron, sin?; una ruta que no existe o pasa demax_depthno compila.aggtipado por la petición. api.<schema>.transactiontipada por operación;update(…, { returning: true, includes }).api.rbac(realm)para la gestión de usuarios,session.updateProfile(),session.me(),canAdminRealm()y el tipoRealm.- TanStack: factorías que suman los callbacks a la invalidación, invalidación por tablas,
publicQuery/publicMutationpara lo propio,infinite,agg,connectQueryClient. - Un
in_vacío no llama a la API; fechas en hora de pared y bytes en base64 (core/dates,core/bytes);isTaiApiError(error, código),error.constraint,error.requiredPermission. - Segmentos opcionales en las rutas (
/ordenes/:id/ciclos/:numero?), como react-router 7.
tai-api generate avisa de un ROOT_PATH distinto del base_path y de
ALLOWED_ORIGINS = ["*"]: las dos cosas que pierden la sesión con cookie en producción.
Cómo actualizar¶
pip install -U tai-api, que trae tai-sql 0.8.0 o posterior.tai-api generate.- Los permisos de las tablas hijas en los roles que guardan hijos anidados, y
tai-api rbac push.endpoints-<schema>.md(detai-api rules install) lista las hijas de cada endpoint. - En el frontend,
npm run buildy lo que diga el compilador: Migrar desde el cliente 0.6. tai-api rules install(ytai-sql rules install).