Reef schemas
reef is the fixed-width schema vocabulary — the structural barrier between your data and the raw buffer. Every helper mints a schema that is both the validator and the binary layout spec: one declaration, no second place for the layout to drift.
import { reef } from '@atolljs/core';
const incidentRow = reef.object({
id: reef.u32(), // 4 bytes — uint32 domain
severity: reef.int(0, 3), // 1 byte — domain 0..3, narrowest covering kind
alarms: reef.u16(), // 2 bytes
open: reef.boolean(), // flag byte
site: reef.string(10), // 10 inline UTF-8 bytes — budget IS the schema
openedAt: reef.u64(), // 8-byte bigint
});
// The schema validates AND lays out — a million of these become a flat
// array of structs under field.list:
field.list({ schema: incidentRow, count: 1_000_000 });The vocabulary
Only helpers whose schemas can promise a byte width exist — there is no reef.number() because an unbounded width can't compile to a fixed layout. Ten scalar kinds, one spelling each; the format-based kinds are tagged directly and bounded ints cover the rest.
| Helper | Storage | Domain |
|---|---|---|
reef.i8() · reef.u8() | 1 byte | −128..127 · 0..255 |
reef.i16() · reef.u16() | 2 bytes | −32768..32767 · 0..65535 |
reef.i32() · reef.u32() | 4 bytes | int32 · uint32 |
reef.i64() · reef.u64() | 8 bytes | int64 · uint64 (bigint) |
reef.f32() · reef.f64() | 4 / 8 bytes | float |
reef.int(min, max) | narrowest covering kind | min..max |
reef.boolean() | flag byte | — |
reef.string(bytes) | bytes inline UTF-8 | byte length ≤ bytes |
reef.object(shape) | fixed record — member widths summed + aligned | per member |
reef.array(el) | bounded via .max(n) / .length(n) | per element |
reef.int(0, 3) declares a domain — the compiler picks the narrowest covering width (u8 → i8 → u16 → i16 → u32 → i32). Writes of 9 still reject: bounds can be stricter than storage, never looser.
Where schemas plug in
| Field factory | Schema it takes | Layout it derives |
|---|---|---|
field.list({ schema, count }) | reef.object(...) | record array — readAt(i)/writeAt(i) are pointer arithmetic |
field.object({ schema }) | reef.object(...) | one inline record — byteLength derived, no maxBytes |
field.array({ schema }) | reef.array(el).max(n) | count header + n inline elements |
field.string({ schema }) | reef.string(n) | length header + n inline UTF-8 bytes |
Your own zod works too
Classic zod and zod/mini schemas are accepted anywhere reef schemas are — the compiler duck-types on the zod-style _zod.def, so z.uint32(), z.number().int().min(0).max(3), and z.string().meta({ bytes: n }) compile identically:
import { z } from 'zod';
reef.object({
id: z.uint32(), // classic format → 'u32'
level: z.number().int().min(0).max(3), // bounds → narrowest kind ('u8')
tag: z.string().meta({ bytes: 8 }), // meta → 8 inline bytes
ref: reef.u16(), // reef members mix the other way too
});Reef schemas are minted by a vendored engine — no zod dependency ships, and the classic chain spellings stay attached: .refine(), .meta(), .min()/.max(), .length(), .int()all work, and the compiler reads the final schema. Two honest limits: they aren't instanceof z.ZodType (check s._zod.def + .parse instead), and they can't be members of a real z.object/z.array — zod dispatches member parsing through its own internals; when a wire schema needs a reef value, compose with reef.object/reef.array.
What the reef rejects
Compile fails loudly rather than guessing a width — unbounded strings (no bytes meta), unbounded arrays, nested objects inside a record, and types with no fixed encoding (z.date(), z.enum(), unions) all throw at defineSharedMemory/field.* declaration time. Dynamic payloads aren't banned — they go through the codec escape hatch: field.object({ maxBytes }) encodes arbitrary JSON-shaped values with the contract's codec, schema optional.
Bundle note: reef runs on a vendored schema engine — zod isn't an @atolljs/core dependency at all, and it's fully opt-in — no reef/listSchema import, no schema code in your bundle. See Bundle size & load and Shared memory.