dec-0115

Vista generada de dec-0115: TypeBox sustituye a Arktype en los contratos contract-first

ImplementadoSin versión del tren todavía· generada desde docs/decisions/dec-0115-typebox-sustituye-arktype.md
track/docsdec-0124track/docs:DC10

Vista generada desde docs/decisions/dec-0115-typebox-sustituye-arktype.md. No se edita a mano: bun run docs:gen la regenera y bun run docs:check falla si difiere. El estado aquí es el del model: si discrepa con otra página, manda el model.

CampoValor
EstadoSUPERSEDED
Fecha2026-09-28
Ficherodocs/decisions/dec-0115-typebox-sustituye-arktype.md

Enmienda a: r18, r20

Enmendado o sustituido por: dec-0116

Por qué importa (del frontmatter del ADR):

Decide la librería de los wire contracts contract-first de @styx/api-contracts y @styx/domain (r20 §2.3). La validan en runtime todos los svcs NATS y el transport HTTP de playback-svc, y la librería se carga en cada contenedor (r17).

Nodos del roadmap que lo citan en refs: ninguno.

Páginas de la documentación que lo citan: ninguna todavía.

Texto del ADR

Leído de docs/decisions/dec-0115-typebox-sustituye-arktype.md, el fichero canónico.

dec-0115 — TypeBox sustituye a Arktype en los contratos contract-first

  • Fecha: 2026-09-28
  • Estado: SUPERSEDED por dec-0116 (2026-09-28), que cierra la línea de TypeBox, el orden respecto a Elysia 2 y las divergencias semánticas. Este texto se conserva como registro; no planificar sobre él.
  • Decisor: waxin, con una regla fijada antes de tener los datos: "si TypeBox consume menos que Arktype, fuera Arktype".
  • Enmienda: r18/r20 §2.3. Cambia la librería; el principio contract-first se mantiene (los contratos siguen escritos a mano en @styx/api-contracts, no se derivan de Elysia).
  • Evidencia: spikes/typebox-vs-arktype/RESULTS.md + data/*.json (rama bench/typebox-vs-arktype, base bb1d967).

Contexto

r18/r20 fijaron Arktype como librería contract-first. spire ya usa TypeBox (el t.* de Elysia). waxin pidió decidir con datos. El spike porta 1:1 un subconjunto real de 17 schemas (MessageEnvelope<T>, Work/Edition/MediaAsset/SourceBinding, PlaybackPlan con unions discriminadas, TransportSignalsSnapshot, identity, errores tipados). Los 6 engines tienen paridad verificada (0 divergencias en bun y node) y la inferencia se verifica con tsgo y un control mutante.

Datos (p50; detalle y dispersión en RESULTS.md)

ejearktype 2.2.5typebox 1.3.34@sinclair/typebox 0.34.52
heap total bun / node17.95 / 19.62 MB3.17 / 6.59 MB2.16 / 2.82 MB
cold start lib bun / node323 / 433 ms123 / 205 ms50 / 94 ms
bundle web gzip47 KB34 KB23 KB
tsgo check / memoria151 ms / 43 MB69 ms / 29 MB60 ms / 21 MB
mix válido ops/s bun / node4.25M / 3.69M6.30M / 3.50M4.54M / 4.82M
mix 90/10 con errores detallados520k / 500k156k / 153k794k / 720k

La condición se cumple: TypeBox consume menos en heap, cold start, bundle y typecheck, y empata o gana en CPU en el camino válido. Pierde en el camino de error con TypeBox 1.x (≈ 3.3× más lento) y en la calidad de los mensajes de error en unions.

Decisión (propuesta)

  1. Arktype sale de @styx/api-contracts y @styx/domain. Los contratos pasan a TypeBox, validados con el validador compilado (Compile / TypeCompiler). El modo Value.* sin compilar es 10–270× más lento y queda prohibido en hot paths.
  2. Línea: typebox 1.x, la vigente en npm y la única que acepta Elysia 2.0. @sinclair/ typebox 0.34 consume aún menos, pero es la línea anterior. Sólo se acepta como puente mientras el repo siga en Elysia 1.4, que no acepta TypeBox 1.x (verificado: 500 en runtime).
  3. Orden: la migración va junto con el salto a Elysia 2, o usa 0.34 como puente con un bump posterior. No se mezclan dos líneas de TypeBox en un mismo svc.
  4. JSON Schema pasa a ser la representación canónica del contrato. Desbloquea OpenAPI, el generador de SDK de spire y los clientes Swift sin pasar por .toJsonSchema(), que hoy falla con el .narrow() de SourceBinding.preference.

Consecuencias

  • Coste de migración: todos los módulos de @styx/api-contracts y @styx/domain/models, los sitios de validación listados en RESULTS.md §Inventario y parseEnvelope. El genérico MessageEnvelope<T> pasa a ser EnvelopeOf<T extends TSchema>(t).
  • Peores mensajes de error en unions discriminadas: en 1.x la ruta se repite una vez por rama, y en 0.34 sale Expected union value sin ruta. Mitigación: formatear los errores en parseEnvelope (primer error por ruta más profunda).
  • El camino de error de TypeBox 1.x es más caro. Sólo importa si un emisor malicioso o roto inunda el bus de payloads inválidos. Mitigación: rechazar con Check booleano y materializar Errors sólo para el log (con rate-limit).
  • Independiente de esta decisión: apps/web arrastra hoy la librería de validación entera por el barrel de @styx/api-contracts (createRequestId). Hay que importar el módulo hoja.

Alternativas descartadas

  • Mantener Arktype: mejor DX de errores y agnóstico de la major de Elysia (Standard Schema), pero incumple la regla de waxin en los cuatro ejes de consumo.
  • TypeBox sin compilar (Value.Check): gana en memoria y cold start, pero es 10–200× más lento en validación.