Schema mode
When both ends know the shape, derive the wire schema from the valibot schema you already have and skip shape detection entirely.
Both ends of an internal endpoint already know the row shape from the feature contract, so nothing descriptive needs to travel. Derive the wire schema from the valibot schema in shared/schema.c:
// src/lib/norns/notes/shared/schema.c
import * as v from 'valibot'
import { tronSchemaFromValibot } from '@human-synthesis/norns-tron/valibot'
export noteSchema := v.object
id: v.number()
title: v.string()
status: v.picklist ['draft', 'published']
export noteWire := tronSchemaFromValibot noteSchema, { id: 'notes.v1', path: '$.data' }Server:
export GET := route
serializer: tronSerializer({ schema: noteWire })
handler: async ({ container }) =>
data: await notes(container).list()Client: the same noteWire decodes it, or api.get('/api/notes') when the client only ever sees this shape.
#Rules
picklist/enumfields become dictionary columns (integers on the wire); booleans become 0/1.- Compile once at module scope, never per request.
tronSchemaFromValibot(anddefineSchema) resolve field order, enum tables and the row path, and compile the row constructor. Doing that per request is the difference between winning and losing on small payloads. pathsays where the row array lives:'$'for the root,'$.data'for an envelope.- The
#notes.v1tag (id) is emitted as a header line and makes version mismatches fail loudly instead of misdecoding. A client that consumes several shapes usescreateRegistry(), registers each schema, and callsregistry.decode(text), which peeks the tag and picks the schema.
#Without valibot
defineSchema({ id, fields, enums, path }) builds the same compiled schema by hand; see the core reference.