El framework gate

El esquema y la capa de datos de svitrio están construidos sobre gate — un pequeño lenguaje declarativo de esquemas (ficheros .gate) y gate-go, un toolkit propio que convierte esos ficheros en SQL y Go en tiempo de compilación. Entender gate explica mucho sobre por qué svitrio se comporta como lo hace.

Qué es

Un fichero .gate declara tipos, campos, relaciones y restricciones. A partir de un solo esquema, gate-go emite tanto el SQL DDL (CREATE TABLE IF NOT EXISTS + índices + claves foráneas) como el store de Go (la struct, un escáner de filas y un CRUD completo con ámbito).

type ai_provider {
  @index(project_id, is_default)
  project_id:   ref<project> @external @on_delete(cascade) @scoped(project_id)
  name:         string  @required
  kind:         string  @required
  api_key:      string  @default("") @sealed
  is_default:   bool    @default(false) @exclusive
}

Las anotaciones aportan comportamiento: @scoped inyecta WHERE project_id = ? en cada consulta; @sealed cifra un campo en reposo (sella al escribir, abre al leer); @exclusive mantiene como máximo una fila en true por ámbito dentro de una transacción; @external + @on_delete(cascade) enlaza con FK a la tabla de otro módulo y limpia cuando se elimina un tenant.

Cómo lo usa svitrio

Cada módulo coloca su esquema .gate junto a su código Go y lo empotra. go generate produce un store *_generated.go (un guardián de deriva en CI hace fallar la compilación si queda obsoleto). En el arranque, el host carga el esquema de cada módulo y ejecuta CREATE TABLE IF NOT EXISTS — de modo que componer el esquema completo es idempotente y el binario arranca desde una base de datos en blanco. Añadir un módulo nuevo consiste en: entregar su .gate, su gen.go y un import en blanco — nunca se edita ningún fichero SQL central.

Qué obtienes

  • Única fuente de verdad — el fichero .gate impulsa tanto la tabla como el store tipado; no pueden derivar.

  • Boilerplate casi nulo — un tipo de ~30 líneas produce una struct, un escáner, cinco métodos CRUD, sellado transaccional y el invariante de exclusividad por defecto.

  • Sin baile de migraciones — cada migración es IF NOT EXISTS; las columnas aditivas usan AddColumnIfMissing. Sin tabla de versiones, sin scripts up/down.

  • Multitenancy gratis@scoped + un ref<project> en cascada significa que cada módulo está aislado por tenant por construcción.

  • La base de la capa de producto — el admin ya construye la UI a partir de un esquema .gate más una capa de presentación. Convertir «escribir un esquema» en una superficie que un cliente pueda usar para modelar su propio producto es el siguiente paso natural.