diff --git a/serializer/.gitignore b/serializer/.gitignore new file mode 100644 index 0000000..e0fc267 --- /dev/null +++ b/serializer/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +*.log +.DS_Store diff --git a/serializer/index.html b/serializer/index.html new file mode 100644 index 0000000..06d7ce4 --- /dev/null +++ b/serializer/index.html @@ -0,0 +1,71 @@ + + + + + + @perf/serializer — examples + + + +

@perf/serializer — examples

+

+ Six demonstrations from src/main.ts. The Vite AOT plugin in + vite.config.ts inlines every type(…) call at + build time, so no runtime new Function is involved. +

+

+    
+  
+
diff --git a/serializer/package.json b/serializer/package.json
new file mode 100644
index 0000000..e39920a
--- /dev/null
+++ b/serializer/package.json
@@ -0,0 +1,27 @@
+{
+  "name": "@perf/serializer",
+  "version": "0.1.0",
+  "private": true,
+  "type": "module",
+  "description": "Ultra-high-performance TypeScript serializer with Symbol.serializable contract and JIT-compiled binary codecs.",
+  "engines": {
+    "node": ">=24.0.0"
+  },
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "test": "vitest run",
+    "test:watch": "vitest",
+    "bench": "vitest bench --run",
+    "bench:watch": "vitest bench",
+    "type-check": "tsc --noEmit"
+  },
+  "devDependencies": {
+    "@vitejs/devtools": "^0.1.24",
+    "magic-string": "^0.30.21",
+    "oxc-parser": "^0.132.0",
+    "typescript": "^6.0.3",
+    "vite": "^8.0.14",
+    "vitest": "^4.1.7"
+  }
+}
diff --git a/serializer/plugin/api.ts b/serializer/plugin/api.ts
new file mode 100644
index 0000000..6bfad84
--- /dev/null
+++ b/serializer/plugin/api.ts
@@ -0,0 +1,329 @@
+/**
+ * Simplified façade over the schema + codec system.
+ *
+ * 90% of usage:
+ *   const Order = type('Order', { id: u53, price: f64, side: enumOf(['buy','sell'] as const) });
+ *   type Order = typeof Order.$infer;
+ *   const bytes = Order.encode(order);
+ *   const back = Order.decode(bytes);
+ *
+ * Hot path:
+ *   const w = new Writer(1024);
+ *   Order.encodeInto(order, w);
+ *   socket.send(w.bytes());
+ *
+ * AOT (compile-only) build: the transformer detects `type(...)` calls and
+ * replaces them with precomputed codec literals; the function below never runs
+ * in production. See codegen-plugin/.
+ */
+
+import type {
+  AnySchema,
+  ArraySchema,
+  BitsetSchema,
+  EnumSchema,
+  ObjectSchema,
+  OptionalSchema,
+  TupleSchema,
+  UnionSchema,
+} from './descriptors.ts';
+import type { Reader, Writer } from './io.ts';
+import { Writer as WriterImpl } from './io.ts';
+import { Reader as ReaderImpl } from './io.ts';
+import { defineSchema, s } from './schema.ts';
+import { register as registerSchema } from './register.ts';
+
+// ── Primitive markers (re-export of the descriptor singletons) ─────────────
+
+export const u8 = s.u8;
+export const u16 = s.u16;
+export const u32 = s.u32;
+export const i8 = s.i8;
+export const i16 = s.i16;
+export const i32 = s.i32;
+export const u53 = s.u53;
+export const i53 = s.i53;
+export const u64 = s.u64;
+export const i64 = s.i64;
+export const f32 = s.f32;
+export const f64 = s.f64;
+export const bool = s.bool;
+export const str = s.str;
+export const bytes = s.bytes;
+
+export const f32Array = s.f32Array;
+export const f64Array = s.f64Array;
+export const u8Array = s.u8Array;
+export const u16Array = s.u16Array;
+export const u32Array = s.u32Array;
+export const i32Array = s.i32Array;
+
+// ── Combinators ────────────────────────────────────────────────────────────
+
+export function list(elem: E): ArraySchema {
+  return s.array(elem);
+}
+
+export function opt(elem: E): OptionalSchema {
+  return s.optional(elem);
+}
+
+export function enumOf(values: L): EnumSchema {
+  return s.enum(values);
+}
+
+export function flags(names: L): BitsetSchema {
+  return s.bitset(names);
+}
+
+export function tuple(...elems: E): TupleSchema {
+  return s.tuple(...elems);
+}
+
+// ── TypeCodec: schema + runtime API in one value ───────────────────────────
+
+/**
+ * A `TypeCodec` is BOTH:
+ *   - an `ObjectSchema` (so it can be used as a field in another `type(...)`)
+ *   - a runtime codec with `encode`/`decode` methods
+ *
+ * Plus a phantom `$infer` field for `typeof T.$infer` extraction.
+ */
+export interface TypeCodec extends ObjectSchema {
+  readonly id: number;
+
+  /** One-shot encode. Allocates a Writer; for hot paths prefer `encodeInto`. */
+  encode(value: T): Uint8Array;
+  encode(value: T, into: Writer): Uint8Array;
+
+  /** One-shot decode from a complete byte buffer. */
+  decode(bytes: Uint8Array): T;
+
+  /** Hot path: writes directly into a pre-allocated, pooled Writer. */
+  encodeInto(value: T, w: Writer): void;
+
+  /** Hot path: reads from a positioned Reader (does not advance past end). */
+  decodeFrom(r: Reader): T;
+
+  /** Phantom: `typeof Codec.$infer` gives the inferred TS type. Never read at runtime. */
+  readonly $infer: T;
+}
+
+// ── Type-level inference ───────────────────────────────────────────────────
+
+type InferPrim =
+  K extends 'u8' | 'u16' | 'u32' | 'i8' | 'i16' | 'i32' | 'u53' | 'i53' | 'f32' | 'f64' ? number
+  : K extends 'u64' | 'i64' ? bigint
+  : K extends 'bool' ? boolean
+  : K extends 'str' ? string
+  : K extends 'bytes' ? Uint8Array
+  : K extends 'f32Array' ? Float32Array
+  : K extends 'f64Array' ? Float64Array
+  : K extends 'u8Array' ? Uint8Array
+  : K extends 'u16Array' ? Uint16Array
+  : K extends 'u32Array' ? Uint32Array
+  : K extends 'i32Array' ? Int32Array
+  : never;
+
+export type InferType =
+  S extends { $infer: infer T } ? T
+  : S extends ArraySchema ? InferType[]
+  : S extends OptionalSchema ? InferType | undefined
+  : S extends EnumSchema ? L[number]
+  : S extends BitsetSchema ? { [K in L[number]]: boolean }
+  : S extends TupleSchema ? { -readonly [K in keyof E]: InferType }
+  : S extends UnionSchema
+    ? V extends Record
+      ? {
+          [K in keyof V & string]: V[K] extends ObjectSchema
+            ? { [P in D]: K } & { [Pk in keyof F]: InferType }
+            : never
+        }[keyof V & string]
+      : never
+  : S extends ObjectSchema ? { [K in keyof F]: InferType }
+  : S extends { kind: infer K } ? InferPrim
+  : unknown;
+
+type Fields = Record;
+
+// ── Anonymous naming ───────────────────────────────────────────────────────
+
+let __anonCounter = 0;
+function anonName(prefix = 'Anon'): string {
+  return `__${prefix}_${++__anonCounter}`;
+}
+
+// ── type() ────────────────────────────────────────────────────────────────
+
+/**
+ * Define a serializable type.
+ *
+ * The `name` is REQUIRED for wire-stable types (anything sent over a network
+ * or stored to disk) because the schema ID is a hash of the name and the ID
+ * appears in the wire frame. Without a name we generate one, which is fine
+ * for transient/in-process use only.
+ */
+export function type(fields: F): TypeCodec<{ [K in keyof F]: InferType }>;
+export function type(name: string, fields: F): TypeCodec<{ [K in keyof F]: InferType }>;
+export function type(nameOrFields: string | Fields, maybeFields?: Fields): TypeCodec {
+  const isNamed = typeof nameOrFields === 'string';
+  const name = isNamed ? nameOrFields : anonName('Type');
+  const fields = isNamed ? maybeFields! : nameOrFields;
+
+  const schema = defineSchema(name, () => fields);
+  const codec = registerSchema(schema);
+
+  const enc = codec.encode;
+  const dec = codec.decode;
+
+  function encode(value: unknown, into?: Writer): Uint8Array {
+    if (into) {
+      enc(into, value);
+      return into.bytes();
+    }
+    const tmp = new WriterImpl();
+    enc(tmp, value);
+    return tmp.bytesCopy();
+  }
+
+  function decode(b: Uint8Array): unknown {
+    const r = new ReaderImpl(b);
+    return dec(r);
+  }
+
+  function encodeInto(value: unknown, w: Writer): void {
+    enc(w, value);
+  }
+
+  function decodeFrom(r: Reader): unknown {
+    return dec(r);
+  }
+
+  return {
+    kind: 'object',
+    name,
+    fields: schema.fields,
+    id: codec.id,
+    encode,
+    decode,
+    encodeInto,
+    decodeFrom,
+    $infer: undefined as unknown,
+  } as TypeCodec;
+}
+
+// ── oneOf() — discriminated union ──────────────────────────────────────────
+
+/**
+ * Discriminated union. Each variant is a field map; at runtime the variant is
+ * identified by a `discriminator` key on the value.
+ *
+ *   const Event = oneOf('kind', {
+ *     fill:   { price: f64, qty: f64 },
+ *     cancel: { reason: str },
+ *   });
+ *   //  fill   → { kind: 'fill', price, qty }
+ *   //  cancel → { kind: 'cancel', reason }
+ */
+// Less-precise but stable inference for unions; user can narrow via `as`.
+export function oneOf>(
+  discriminator: D,
+  variants: V,
+): TypeCodec<{ [P in D]: keyof V & string } & Record>;
+export function oneOf>(
+  name: string,
+  discriminator: D,
+  variants: V,
+): TypeCodec<{ [P in D]: keyof V & string } & Record>;
+export function oneOf(
+  arg1: string,
+  arg2: string | Record,
+  arg3?: Record,
+): TypeCodec {
+  const isNamed = arg3 !== undefined;
+  const name = isNamed ? arg1 : anonName('Union');
+  const discriminator = (isNamed ? arg2 : arg1) as string;
+  const variants = (isNamed ? arg3! : arg2) as Record;
+
+  const schema = s.union(name, discriminator, variants);
+  const codec = registerSchema(schema);
+
+  const enc = codec.encode;
+  const dec = codec.decode;
+
+  function encode(value: unknown, into?: Writer): Uint8Array {
+    if (into) {
+      enc(into, value);
+      return into.bytes();
+    }
+    const tmp = new WriterImpl();
+    enc(tmp, value);
+    return tmp.bytesCopy();
+  }
+
+  function decode(b: Uint8Array): unknown {
+    const r = new ReaderImpl(b);
+    return dec(r);
+  }
+
+  // Union doesn't satisfy ObjectSchema directly — it's UnionSchema. We expose
+  // the runtime API on the same value but the descriptor shape is union.
+  // For use as a field, accept it via AnySchema (TypeScript inference handles it).
+  return {
+    kind: 'union',
+    name,
+    discriminator,
+    variants: (schema as unknown as { variants: unknown }).variants,
+    fields: {}, // unused for unions
+    id: codec.id,
+    encode,
+    decode,
+    encodeInto(value: unknown, w: Writer) { enc(w, value); },
+    decodeFrom(r: Reader) { return dec(r); },
+    $infer: undefined as unknown,
+  } as unknown as TypeCodec;
+}
+
+// ── Router: framed multi-type dispatch ─────────────────────────────────────
+
+export interface Router {
+  /** Encode with the 2-byte schema-ID frame. */
+  encode(value: T, codec: TypeCodec): Uint8Array;
+  encode(value: T, codec: TypeCodec, into: Writer): Uint8Array;
+  /** Decode a framed message, dispatching by schema ID. */
+  decode(bytes: Uint8Array): unknown;
+}
+
+/**
+ * Build a router for framed multi-message protocols. The router prepends a
+ * 2-byte schema ID on encode and dispatches on it during decode.
+ */
+export function router(...codecs: TypeCodec[]): Router {
+  const byId = new Map>();
+  for (const c of codecs) byId.set(c.id, c);
+
+  return {
+    encode(value: T, codec: TypeCodec, into?: Writer): Uint8Array {
+      if (into) {
+        into.u16(codec.id);
+        codec.encodeInto(value, into);
+        return into.bytes();
+      }
+      const w = new WriterImpl();
+      w.u16(codec.id);
+      codec.encodeInto(value, w);
+      return w.bytesCopy();
+    },
+    decode(bytes: Uint8Array): unknown {
+      const r = new ReaderImpl(bytes);
+      const id = r.u16();
+      const codec = byId.get(id);
+      if (!codec) throw new Error(`Router: unknown schema ID 0x${id.toString(16)}`);
+      return codec.decodeFrom(r);
+    },
+  };
+}
+
+// ── Writer/Reader re-exports for hot path users ────────────────────────────
+
+export { Writer, Reader } from './io.ts';
diff --git a/serializer/plugin/codegen.ts b/serializer/plugin/codegen.ts
new file mode 100644
index 0000000..82b542f
--- /dev/null
+++ b/serializer/plugin/codegen.ts
@@ -0,0 +1,729 @@
+import type {
+  AnySchema,
+  ObjectSchema,
+  TypedArrayKind,
+  UnionSchema,
+} from './descriptors.ts';
+
+export interface CodegenResult {
+  /** Inner-function body for encoder: takes (w, o), uses lifted pos/buf/view locals. */
+  encodeBody: string;
+  /** Inner-function body for decoder: takes (r), uses lifted pos/buf/view locals. */
+  decodeBody: string;
+  /** Cross-codec dependencies for ref/codec fields only (nested objects are inlined). */
+  deps: Map;
+  /** Closure-captured values (enum maps, codec functions). */
+  closure: Map;
+}
+
+function sanitize(name: string): string {
+  return name.replace(/[^A-Za-z0-9_]/g, '_');
+}
+
+const TA_ELEM_SIZE: Record = {
+  f32Array: 4,
+  f64Array: 8,
+  u8Array: 1,
+  u16Array: 2,
+  u32Array: 4,
+  i32Array: 4,
+};
+
+const TA_CTOR: Record = {
+  f32Array: 'Float32Array',
+  f64Array: 'Float64Array',
+  u8Array: 'Uint8Array',
+  u16Array: 'Uint16Array',
+  u32Array: 'Uint32Array',
+  i32Array: 'Int32Array',
+};
+
+function isTypedArrayKind(k: string): k is TypedArrayKind {
+  return k in TA_ELEM_SIZE;
+}
+
+/**
+ * Upper bound on bytes the schema may write, or null when truly variable
+ * (str, bytes, array, typed-array, ref, codec, and any composite containing them).
+ */
+function maxBytes(schema: AnySchema): number | null {
+  switch (schema.kind) {
+    case 'u8':
+    case 'i8':
+    case 'bool':
+    case 'enum':
+      return 1;
+    case 'u16':
+    case 'i16':
+      return 2;
+    case 'u32':
+    case 'i32':
+    case 'f32':
+      return 4;
+    case 'f64':
+      return 8;
+    case 'u53':
+    case 'i53':
+    case 'u64':
+    case 'i64':
+      return 10;
+    case 'bitset': {
+      const n = schema.flags.length;
+      return n <= 8 ? 1 : n <= 16 ? 2 : n <= 32 ? 4 : 10;
+    }
+    case 'tuple': {
+      let sum = 0;
+      for (const e of schema.elems) {
+        const m = maxBytes(e);
+        if (m === null) return null;
+        sum += m;
+      }
+      return sum;
+    }
+    case 'object': {
+      let sum = 0;
+      for (const f of Object.values(schema.fields)) {
+        const m = maxBytes(f);
+        if (m === null) return null;
+        sum += m;
+      }
+      return sum;
+    }
+    case 'optional': {
+      const m = maxBytes(schema.elem);
+      return m === null ? null : 1 + m;
+    }
+    case 'union': {
+      let max = 0;
+      for (const v of Object.values(schema.variants)) {
+        const m = maxBytes(v);
+        if (m === null) return null;
+        if (m > max) max = m;
+      }
+      return 1 + max;
+    }
+    case 'array':
+    case 'str':
+    case 'bytes':
+    case 'ref':
+    case 'codec':
+      return null;
+  }
+  return null;
+}
+
+class Ctx {
+  private counter = 0;
+  mode: 'enc' | 'dec';
+  deps = new Map();
+  closure = new Map();
+
+  constructor(mode: 'enc' | 'dec') {
+    this.mode = mode;
+  }
+
+  fresh(prefix: string): string {
+    return `_${prefix}${++this.counter}`;
+  }
+
+  closureVar(prefix: string, value: unknown): string {
+    const name = `__cv_${prefix}_${++this.counter}`;
+    this.closure.set(name, value);
+    return name;
+  }
+}
+
+/**
+ * Builds an encoder body. Bounded ops are buffered with a running budget;
+ * unbounded ops (str, array, ref, etc.) flush the buffer with a single
+ * `ensure(budget)` then emit themselves. Inside arrays with bounded elements,
+ * we pre-ensure the whole batch and run the element loop in "noEnsure" mode.
+ */
+class SegBuilder {
+  private buffered: string[] = [];
+  private budget = 0;
+  private output: string[] = [];
+  noEnsure = false;
+
+  addBounded(stmt: string, maxBytesSize: number): void {
+    if (this.noEnsure) {
+      this.output.push(stmt);
+      return;
+    }
+    this.buffered.push(stmt);
+    this.budget += maxBytesSize;
+  }
+
+  addPrelude(stmt: string): void {
+    if (this.noEnsure) {
+      this.output.push(stmt);
+      return;
+    }
+    this.buffered.push(stmt);
+  }
+
+  flush(): void {
+    if (this.buffered.length === 0) return;
+    if (this.budget > 0 && !this.noEnsure) {
+      this.output.push(
+        `if (pos + ${this.budget} > buf.byteLength) { w.pos = pos; w.grow(${this.budget}); buf = w.buf; view = w.view; }`,
+      );
+    }
+    this.output.push(...this.buffered);
+    this.buffered = [];
+    this.budget = 0;
+  }
+
+  addUnbounded(stmt: string): void {
+    this.flush();
+    this.output.push(stmt);
+  }
+
+  build(): string {
+    this.flush();
+    return this.output.join('\n');
+  }
+}
+
+function emitEnc(schema: AnySchema, ctx: Ctx, seg: SegBuilder, vx: string): void {
+  switch (schema.kind) {
+    case 'u8':
+      seg.addBounded(`buf[pos++] = (${vx}) & 0xff;`, 1);
+      return;
+    case 'i8':
+      seg.addBounded(`buf[pos++] = (${vx}) & 0xff;`, 1);
+      return;
+    case 'bool':
+      seg.addBounded(`buf[pos++] = ${vx} ? 1 : 0;`, 1);
+      return;
+    case 'u16':
+      seg.addBounded(`view.setUint16(pos, ${vx}, true); pos += 2;`, 2);
+      return;
+    case 'i16':
+      seg.addBounded(`view.setInt16(pos, ${vx}, true); pos += 2;`, 2);
+      return;
+    case 'u32':
+      seg.addBounded(`view.setUint32(pos, ${vx}, true); pos += 4;`, 4);
+      return;
+    case 'i32':
+      seg.addBounded(`view.setInt32(pos, ${vx}, true); pos += 4;`, 4);
+      return;
+    case 'f32':
+      seg.addBounded(`view.setFloat32(pos, ${vx}, true); pos += 4;`, 4);
+      return;
+    case 'f64':
+      seg.addBounded(`view.setFloat64(pos, ${vx}, true); pos += 8;`, 8);
+      return;
+    case 'u53': {
+      const v = ctx.fresh('v');
+      seg.addBounded(
+        `{ let ${v} = ${vx}; while (${v} >= 0x80) { buf[pos++] = (${v} & 0x7f) | 0x80; ${v} = Math.floor(${v} / 128); } buf[pos++] = ${v}; }`,
+        10,
+      );
+      return;
+    }
+    case 'i53': {
+      const v = ctx.fresh('v');
+      const src = ctx.fresh('s');
+      seg.addBounded(
+        `{ const ${src} = ${vx}; let ${v} = ${src} >= 0 ? BigInt(${src}) * 2n : -BigInt(${src}) * 2n - 1n; while (${v} >= 0x80n) { buf[pos++] = Number(${v} & 0x7fn) | 0x80; ${v} >>= 7n; } buf[pos++] = Number(${v}); }`,
+        10,
+      );
+      return;
+    }
+    case 'u64': {
+      const v = ctx.fresh('v');
+      seg.addBounded(
+        `{ let ${v} = ${vx}; while (${v} >= 0x80n) { buf[pos++] = Number(${v} & 0x7fn) | 0x80; ${v} >>= 7n; } buf[pos++] = Number(${v}); }`,
+        10,
+      );
+      return;
+    }
+    case 'i64': {
+      const v = ctx.fresh('v');
+      const src = ctx.fresh('s');
+      seg.addBounded(
+        `{ const ${src} = ${vx}; let ${v} = ${src} >= 0n ? ${src} << 1n : (-${src} << 1n) - 1n; while (${v} >= 0x80n) { buf[pos++] = Number(${v} & 0x7fn) | 0x80; ${v} >>= 7n; } buf[pos++] = Number(${v}); }`,
+        10,
+      );
+      return;
+    }
+    case 'enum': {
+      const vals = schema.values;
+      if (vals.length === 2) {
+        seg.addBounded(`buf[pos++] = ${vx} === ${JSON.stringify(vals[0])} ? 0 : 1;`, 1);
+      } else if (vals.length === 3) {
+        seg.addBounded(
+          `buf[pos++] = ${vx} === ${JSON.stringify(vals[0])} ? 0 : ${vx} === ${JSON.stringify(vals[1])} ? 1 : 2;`,
+          1,
+        );
+      } else {
+        const map: Record = Object.create(null);
+        for (let i = 0; i < vals.length; i++) map[vals[i]!] = i;
+        const cv = ctx.closureVar('enum', Object.freeze(map));
+        seg.addBounded(`buf[pos++] = ${cv}[${vx}];`, 1);
+      }
+      return;
+    }
+    case 'bitset': {
+      const b = ctx.fresh('b');
+      const flags = schema.flags;
+      if (flags.length <= 32) {
+        const parts: string[] = ['0'];
+        for (let i = 0; i < flags.length; i++) {
+          parts.push(`((${b})[${JSON.stringify(flags[i])}] ? ${1 << i} : 0)`);
+        }
+        const expr = parts.join(' | ');
+        if (flags.length <= 8) {
+          seg.addBounded(`{ const ${b} = ${vx}; buf[pos++] = ${expr}; }`, 1);
+        } else if (flags.length <= 16) {
+          seg.addBounded(`{ const ${b} = ${vx}; view.setUint16(pos, ${expr}, true); pos += 2; }`, 2);
+        } else {
+          seg.addBounded(`{ const ${b} = ${vx}; view.setUint32(pos, ${expr}, true); pos += 4; }`, 4);
+        }
+      } else {
+        let big = '0n';
+        for (let i = 0; i < flags.length; i++) {
+          big = `(${big}) | ((${b})[${JSON.stringify(flags[i])}] ? ${1n << BigInt(i)}n : 0n)`;
+        }
+        const v = ctx.fresh('v');
+        seg.addBounded(
+          `{ const ${b} = ${vx}; let ${v} = ${big}; while (${v} >= 0x80n) { buf[pos++] = Number(${v} & 0x7fn) | 0x80; ${v} >>= 7n; } buf[pos++] = Number(${v}); }`,
+          10,
+        );
+      }
+      return;
+    }
+    case 'str': {
+      seg.addUnbounded(
+        `w.pos = pos; w.str(${vx}); pos = w.pos; buf = w.buf; view = w.view;`,
+      );
+      return;
+    }
+    case 'bytes': {
+      seg.addUnbounded(
+        `w.pos = pos; w.bytesPrefixed(${vx}); pos = w.pos; buf = w.buf; view = w.view;`,
+      );
+      return;
+    }
+    case 'tuple': {
+      const t = ctx.fresh('t');
+      seg.addPrelude(`const ${t} = ${vx};`);
+      for (let i = 0; i < schema.elems.length; i++) {
+        emitEnc(schema.elems[i]!, ctx, seg, `${t}[${i}]`);
+      }
+      return;
+    }
+    case 'optional': {
+      const o = ctx.fresh('o');
+      const innerMax = maxBytes(schema.elem);
+      if (innerMax !== null) {
+        const innerSeg = new SegBuilder();
+        innerSeg.noEnsure = true;
+        emitEnc(schema.elem, ctx, innerSeg, o);
+        const innerSrc = innerSeg.build();
+        seg.addBounded(
+          `{ const ${o} = ${vx}; if (${o} === undefined || ${o} === null) { buf[pos++] = 0; } else { buf[pos++] = 1; ${innerSrc} } }`,
+          1 + innerMax,
+        );
+      } else {
+        const innerSeg = new SegBuilder();
+        emitEnc(schema.elem, ctx, innerSeg, o);
+        const innerSrc = innerSeg.build();
+        seg.addUnbounded(
+          `{ const ${o} = ${vx}; if (pos + 1 > buf.byteLength) { w.pos = pos; w.grow(1); buf = w.buf; view = w.view; } if (${o} === undefined || ${o} === null) { buf[pos++] = 0; } else { buf[pos++] = 1; ${innerSrc} } }`,
+        );
+      }
+      return;
+    }
+    case 'object': {
+      const oo = ctx.fresh('oo');
+      seg.addPrelude(`const ${oo} = ${vx};`);
+      for (const fname of Object.keys(schema.fields)) {
+        emitEnc(schema.fields[fname]!, ctx, seg, `${oo}[${JSON.stringify(fname)}]`);
+      }
+      return;
+    }
+    case 'union': {
+      const u = ctx.fresh('u');
+      const disc = JSON.stringify(schema.discriminator);
+      const keys = Object.keys(schema.variants);
+      const cases: string[] = [];
+      for (let i = 0; i < keys.length; i++) {
+        const variant = schema.variants[keys[i]!]!;
+        const innerSeg = new SegBuilder();
+        innerSeg.addBounded(`buf[pos++] = ${i};`, 1);
+        for (const fname of Object.keys(variant.fields)) {
+          emitEnc(variant.fields[fname]!, ctx, innerSeg, `${u}[${JSON.stringify(fname)}]`);
+        }
+        cases.push(`case ${JSON.stringify(keys[i])}: { ${innerSeg.build()} break; }`);
+      }
+      seg.addUnbounded(
+        `{ const ${u} = ${vx}; switch (${u}[${disc}]) { ${cases.join('\n')} default: throw new Error('Bad union variant: ' + ${u}[${disc}]); } }`,
+      );
+      return;
+    }
+    case 'array': {
+      const arr = ctx.fresh('arr');
+      const L = ctx.fresh('L');
+      const i = ctx.fresh('i');
+      const elemMax = maxBytes(schema.elem);
+
+      seg.addPrelude(`const ${arr} = ${vx}; const ${L} = ${arr}.length;`);
+      const vL = ctx.fresh('vL');
+      seg.addBounded(
+        `{ let ${vL} = ${L}; while (${vL} >= 0x80) { buf[pos++] = (${vL} & 0x7f) | 0x80; ${vL} = Math.floor(${vL} / 128); } buf[pos++] = ${vL}; }`,
+        10,
+      );
+
+      if (elemMax !== null) {
+        seg.addUnbounded(
+          `if (pos + ${L} * ${elemMax} > buf.byteLength) { w.pos = pos; w.grow(${L} * ${elemMax}); buf = w.buf; view = w.view; }`,
+        );
+        const elemSeg = new SegBuilder();
+        elemSeg.noEnsure = true;
+        emitEnc(schema.elem, ctx, elemSeg, `${arr}[${i}]`);
+        const elemSrc = elemSeg.build();
+        seg.addUnbounded(`for (let ${i} = 0; ${i} < ${L}; ${i}++) { ${elemSrc} }`);
+      } else {
+        const elemSeg = new SegBuilder();
+        emitEnc(schema.elem, ctx, elemSeg, `${arr}[${i}]`);
+        const elemSrc = elemSeg.build();
+        seg.addUnbounded(`for (let ${i} = 0; ${i} < ${L}; ${i}++) { ${elemSrc} }`);
+      }
+      return;
+    }
+    case 'ref': {
+      const resolved = schema.thunk();
+      const dep = `__enc_${sanitize(resolved.name)}`;
+      ctx.deps.set(dep, { mode: 'enc', targetName: resolved.name });
+      seg.addUnbounded(
+        `w.pos = pos; ${dep}(w, ${vx}); pos = w.pos; buf = w.buf; view = w.view;`,
+      );
+      return;
+    }
+    case 'codec': {
+      const cv = ctx.closureVar('codec_enc', schema.encode);
+      seg.addUnbounded(
+        `w.pos = pos; ${cv}(w, ${vx}); pos = w.pos; buf = w.buf; view = w.view;`,
+      );
+      return;
+    }
+  }
+  if (isTypedArrayKind(schema.kind)) {
+    const ta = ctx.fresh('ta');
+    seg.addUnbounded(
+      `{ const ${ta} = ${vx}; w.pos = pos; w.varu53(${ta}.length); w.raw(new Uint8Array(${ta}.buffer, ${ta}.byteOffset, ${ta}.byteLength)); pos = w.pos; buf = w.buf; view = w.view; }`,
+    );
+    return;
+  }
+  throw new Error(`emitEnc: unknown kind ${(schema as { kind: string }).kind}`);
+}
+
+function emitDec(schema: AnySchema, ctx: Ctx): { pre: string; expr: string } {
+  switch (schema.kind) {
+    case 'u8':
+      return { pre: '', expr: 'buf[pos++]' };
+    case 'i8':
+      return { pre: '', expr: '(buf[pos++] << 24 >> 24)' };
+    case 'bool':
+      return { pre: '', expr: '(buf[pos++] !== 0)' };
+    case 'u16': {
+      const v = ctx.fresh('v');
+      return { pre: `const ${v} = view.getUint16(pos, true); pos += 2;`, expr: v };
+    }
+    case 'i16': {
+      const v = ctx.fresh('v');
+      return { pre: `const ${v} = view.getInt16(pos, true); pos += 2;`, expr: v };
+    }
+    case 'u32': {
+      const v = ctx.fresh('v');
+      return { pre: `const ${v} = view.getUint32(pos, true); pos += 4;`, expr: v };
+    }
+    case 'i32': {
+      const v = ctx.fresh('v');
+      return { pre: `const ${v} = view.getInt32(pos, true); pos += 4;`, expr: v };
+    }
+    case 'f32': {
+      const v = ctx.fresh('v');
+      return { pre: `const ${v} = view.getFloat32(pos, true); pos += 4;`, expr: v };
+    }
+    case 'f64': {
+      const v = ctx.fresh('v');
+      return { pre: `const ${v} = view.getFloat64(pos, true); pos += 8;`, expr: v };
+    }
+    case 'u53': {
+      const v = ctx.fresh('v');
+      const vv = ctx.fresh('vv');
+      const m = ctx.fresh('m');
+      const b = ctx.fresh('b');
+      return {
+        pre: `let ${v}; { let ${vv} = 0, ${m} = 1, ${b}; do { ${b} = buf[pos++]; ${vv} += (${b} & 0x7f) * ${m}; ${m} *= 128; } while (${b} & 0x80); ${v} = ${vv}; }`,
+        expr: v,
+      };
+    }
+    case 'i53': {
+      const v = ctx.fresh('v');
+      const vv = ctx.fresh('vv');
+      const sh = ctx.fresh('sh');
+      const b = ctx.fresh('b');
+      return {
+        pre: `let ${v}; { let ${vv} = 0n, ${sh} = 0n, ${b}; do { ${b} = buf[pos++]; ${vv} |= BigInt(${b} & 0x7f) << ${sh}; ${sh} += 7n; } while (${b} & 0x80); const _z = (${vv} & 1n) === 0n ? ${vv} >> 1n : -((${vv} >> 1n) + 1n); ${v} = Number(_z); }`,
+        expr: v,
+      };
+    }
+    case 'u64': {
+      const v = ctx.fresh('v');
+      const vv = ctx.fresh('vv');
+      const sh = ctx.fresh('sh');
+      const b = ctx.fresh('b');
+      return {
+        pre: `let ${v}; { let ${vv} = 0n, ${sh} = 0n, ${b}; do { ${b} = buf[pos++]; ${vv} |= BigInt(${b} & 0x7f) << ${sh}; ${sh} += 7n; } while (${b} & 0x80); ${v} = ${vv}; }`,
+        expr: v,
+      };
+    }
+    case 'i64': {
+      const v = ctx.fresh('v');
+      const vv = ctx.fresh('vv');
+      const sh = ctx.fresh('sh');
+      const b = ctx.fresh('b');
+      return {
+        pre: `let ${v}; { let ${vv} = 0n, ${sh} = 0n, ${b}; do { ${b} = buf[pos++]; ${vv} |= BigInt(${b} & 0x7f) << ${sh}; ${sh} += 7n; } while (${b} & 0x80); ${v} = (${vv} & 1n) === 0n ? ${vv} >> 1n : -((${vv} >> 1n) + 1n); }`,
+        expr: v,
+      };
+    }
+    case 'enum': {
+      const vals = schema.values;
+      if (vals.length <= 2) {
+        return {
+          pre: '',
+          expr: `(buf[pos++] === 0 ? ${JSON.stringify(vals[0])} : ${JSON.stringify(vals[1])})`,
+        };
+      } else if (vals.length === 3) {
+        const v = ctx.fresh('v');
+        const t = ctx.fresh('t');
+        return {
+          pre: `let ${v}; { const ${t} = buf[pos++]; ${v} = ${t} === 0 ? ${JSON.stringify(vals[0])} : ${t} === 1 ? ${JSON.stringify(vals[1])} : ${JSON.stringify(vals[2])}; }`,
+          expr: v,
+        };
+      } else {
+        const cv = ctx.closureVar('enum_dec', Object.freeze(vals.slice()));
+        return { pre: '', expr: `${cv}[buf[pos++]]` };
+      }
+    }
+    case 'bitset': {
+      const v = ctx.fresh('v');
+      const raw = ctx.fresh('raw');
+      const flags = schema.flags;
+      let rawDecl: string;
+      let isBig = false;
+      if (flags.length <= 8) {
+        rawDecl = `const ${raw} = buf[pos++];`;
+      } else if (flags.length <= 16) {
+        rawDecl = `const ${raw} = view.getUint16(pos, true); pos += 2;`;
+      } else if (flags.length <= 32) {
+        rawDecl = `const ${raw} = view.getUint32(pos, true); pos += 4;`;
+      } else {
+        isBig = true;
+        const vv = ctx.fresh('vv');
+        const sh = ctx.fresh('sh');
+        const b = ctx.fresh('b');
+        rawDecl = `let ${raw}; { let ${vv} = 0n, ${sh} = 0n, ${b}; do { ${b} = buf[pos++]; ${vv} |= BigInt(${b} & 0x7f) << ${sh}; ${sh} += 7n; } while (${b} & 0x80); ${raw} = ${vv}; }`;
+      }
+      const props: string[] = [];
+      for (let i = 0; i < flags.length; i++) {
+        if (isBig) {
+          props.push(`${JSON.stringify(flags[i])}: (${raw} & ${1n << BigInt(i)}n) !== 0n`);
+        } else {
+          props.push(`${JSON.stringify(flags[i])}: (${raw} & ${1 << i}) !== 0`);
+        }
+      }
+      return {
+        pre: `${rawDecl} const ${v} = { ${props.join(', ')} };`,
+        expr: v,
+      };
+    }
+    case 'str': {
+      const v = ctx.fresh('v');
+      return {
+        pre: `r.pos = pos; const ${v} = r.str(); pos = r.pos;`,
+        expr: v,
+      };
+    }
+    case 'bytes': {
+      const v = ctx.fresh('v');
+      return {
+        pre: `r.pos = pos; const ${v} = r.bytesPrefixed(); pos = r.pos;`,
+        expr: v,
+      };
+    }
+    case 'tuple': {
+      let pre = '';
+      const exprs: string[] = [];
+      for (const elem of schema.elems) {
+        const inner = emitDec(elem, ctx);
+        if (inner.pre === '') {
+          exprs.push(inner.expr);
+        } else {
+          const tmp = ctx.fresh('tupv');
+          pre += `${inner.pre} const ${tmp} = ${inner.expr};`;
+          exprs.push(tmp);
+        }
+      }
+      const tup = ctx.fresh('tup');
+      return { pre: `${pre} const ${tup} = [${exprs.join(', ')}];`, expr: tup };
+    }
+    case 'optional': {
+      const v = ctx.fresh('opt');
+      const inner = emitDec(schema.elem, ctx);
+      return {
+        pre: `let ${v} = undefined; if (buf[pos++] !== 0) { ${inner.pre} ${v} = ${inner.expr}; }`,
+        expr: v,
+      };
+    }
+    case 'object': {
+      let pre = '';
+      const props: string[] = [];
+      for (const fname of Object.keys(schema.fields)) {
+        const inner = emitDec(schema.fields[fname]!, ctx);
+        const tmp = ctx.fresh(`f_${sanitize(fname)}`);
+        pre += `${inner.pre} const ${tmp} = ${inner.expr};`;
+        props.push(`${JSON.stringify(fname)}: ${tmp}`);
+      }
+      const obj = ctx.fresh('obj');
+      return { pre: `${pre} const ${obj} = { ${props.join(', ')} };`, expr: obj };
+    }
+    case 'union': {
+      const v = ctx.fresh('un');
+      const disc = JSON.stringify(schema.discriminator);
+      const keys = Object.keys(schema.variants);
+      const cases: string[] = [];
+      for (let i = 0; i < keys.length; i++) {
+        const variant = schema.variants[keys[i]!]!;
+        let varPre = '';
+        const props: string[] = [`${disc}: ${JSON.stringify(keys[i])}`];
+        for (const fname of Object.keys(variant.fields)) {
+          const inner = emitDec(variant.fields[fname]!, ctx);
+          if (inner.pre === '') {
+            props.push(`${JSON.stringify(fname)}: ${inner.expr}`);
+          } else {
+            const tmp = ctx.fresh('uv');
+            varPre += `${inner.pre} const ${tmp} = ${inner.expr};`;
+            props.push(`${JSON.stringify(fname)}: ${tmp}`);
+          }
+        }
+        cases.push(`case ${i}: { ${varPre} ${v} = { ${props.join(', ')} }; break; }`);
+      }
+      return {
+        pre: `let ${v}; switch (buf[pos++]) { ${cases.join(' ')} default: throw new Error('Bad union tag'); }`,
+        expr: v,
+      };
+    }
+    case 'array': {
+      const a = ctx.fresh('arr');
+      const L = ctx.fresh('len');
+      const i = ctx.fresh('i');
+      const vv = ctx.fresh('vv');
+      const m = ctx.fresh('m');
+      const b = ctx.fresh('b');
+      const elem = emitDec(schema.elem, ctx);
+      const lenRead = `let ${L}; { let ${vv} = 0, ${m} = 1, ${b}; do { ${b} = buf[pos++]; ${vv} += (${b} & 0x7f) * ${m}; ${m} *= 128; } while (${b} & 0x80); ${L} = ${vv}; }`;
+      return {
+        pre: `${lenRead} const ${a} = new Array(${L}); for (let ${i} = 0; ${i} < ${L}; ${i}++) { ${elem.pre} ${a}[${i}] = ${elem.expr}; }`,
+        expr: a,
+      };
+    }
+    case 'ref': {
+      const resolved = schema.thunk();
+      const dep = `__dec_${sanitize(resolved.name)}`;
+      ctx.deps.set(dep, { mode: 'dec', targetName: resolved.name });
+      const v = ctx.fresh('v');
+      return {
+        pre: `r.pos = pos; const ${v} = ${dep}(r); pos = r.pos;`,
+        expr: v,
+      };
+    }
+    case 'codec': {
+      const cv = ctx.closureVar('codec_dec', schema.decode);
+      const v = ctx.fresh('v');
+      return {
+        pre: `r.pos = pos; const ${v} = ${cv}(r); pos = r.pos;`,
+        expr: v,
+      };
+    }
+  }
+  if (isTypedArrayKind(schema.kind)) {
+    const len = ctx.fresh('talen');
+    const arr = ctx.fresh('ta');
+    const size = TA_ELEM_SIZE[schema.kind];
+    const ctor = TA_CTOR[schema.kind];
+    const vv = ctx.fresh('vv');
+    const m = ctx.fresh('m');
+    const b = ctx.fresh('b');
+    return {
+      pre: `let ${len}; { let ${vv} = 0, ${m} = 1, ${b}; do { ${b} = buf[pos++]; ${vv} += (${b} & 0x7f) * ${m}; ${m} *= 128; } while (${b} & 0x80); ${len} = ${vv}; } const ${arr} = new ${ctor}(${len}); { const _bytes = buf.subarray(pos, pos + ${len} * ${size}); new Uint8Array(${arr}.buffer, ${arr}.byteOffset, ${len} * ${size}).set(_bytes); pos += ${len} * ${size}; }`,
+      expr: arr,
+    };
+  }
+  throw new Error(`emitDec: unknown kind ${(schema as { kind: string }).kind}`);
+}
+
+const BARE_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
+
+export function compileObject(schema: ObjectSchema): CodegenResult {
+  const encCtx = new Ctx('enc');
+  const decCtx = new Ctx('dec');
+
+  // Encoder body
+  const encSeg = new SegBuilder();
+  for (const fname of Object.keys(schema.fields)) {
+    emitEnc(schema.fields[fname]!, encCtx, encSeg, `o[${JSON.stringify(fname)}]`);
+  }
+  const encodeBody = encSeg.build();
+
+  // Decoder body: emit as inline object literal in return statement.
+  // For fields whose inner.expr is already a bare identifier (declared via inner.pre),
+  // skip the wrapping `const tmp = expr;` and use the identifier directly.
+  let pre = '';
+  const props: string[] = [];
+  for (const fname of Object.keys(schema.fields)) {
+    const inner = emitDec(schema.fields[fname]!, decCtx);
+    if (inner.pre !== '' && BARE_IDENT.test(inner.expr)) {
+      pre += inner.pre;
+      props.push(`${JSON.stringify(fname)}: ${inner.expr}`);
+    } else {
+      const tmp = decCtx.fresh(`f_${sanitize(fname)}`);
+      pre += `${inner.pre} const ${tmp} = ${inner.expr};`;
+      props.push(`${JSON.stringify(fname)}: ${tmp}`);
+    }
+  }
+  const decodeBody = `${pre} r.pos = pos; return { ${props.join(', ')} };`;
+
+  const deps = new Map();
+  for (const [k, v] of encCtx.deps) deps.set(k, v);
+  for (const [k, v] of decCtx.deps) deps.set(k, v);
+  const closure = new Map();
+  for (const [k, v] of encCtx.closure) closure.set(k, v);
+  for (const [k, v] of decCtx.closure) closure.set(k, v);
+  return { encodeBody, decodeBody, deps, closure };
+}
+
+export function compileUnion(schema: UnionSchema): CodegenResult {
+  const encCtx = new Ctx('enc');
+  const decCtx = new Ctx('dec');
+
+  const encSeg = new SegBuilder();
+  emitEnc(schema, encCtx, encSeg, 'o');
+  const encodeBody = encSeg.build();
+
+  const decRes = emitDec(schema, decCtx);
+  const decodeBody = `${decRes.pre} r.pos = pos; return ${decRes.expr};`;
+
+  const deps = new Map();
+  for (const [k, v] of encCtx.deps) deps.set(k, v);
+  for (const [k, v] of decCtx.deps) deps.set(k, v);
+  const closure = new Map();
+  for (const [k, v] of encCtx.closure) closure.set(k, v);
+  for (const [k, v] of decCtx.closure) closure.set(k, v);
+  return { encodeBody, decodeBody, deps, closure };
+}
diff --git a/serializer/plugin/compile/index.ts b/serializer/plugin/compile/index.ts
new file mode 100644
index 0000000..d07e52b
--- /dev/null
+++ b/serializer/plugin/compile/index.ts
@@ -0,0 +1,2 @@
+export { transform, type TransformOptions, type TransformResult } from './transformer.ts';
+export { serializerCodegen, type SerializerPluginOptions, type VitePlugin } from './vite.ts';
diff --git a/serializer/plugin/compile/transformer.ts b/serializer/plugin/compile/transformer.ts
new file mode 100644
index 0000000..43dc979
--- /dev/null
+++ b/serializer/plugin/compile/transformer.ts
@@ -0,0 +1,457 @@
+/**
+ * Compile-only transformer for @perf/serializer.
+ *
+ * Detects `type(...)` and `oneOf(...)` calls in the source, statically
+ * evaluates their arguments to ObjectSchema/UnionSchema descriptors, runs
+ * the existing codegen, and replaces each call with a self-contained IIFE
+ * that constructs the codec inline — no runtime `new Function`, no codegen
+ * module needed at runtime.
+ *
+ * Scope (v1):
+ *   - Same-file only (no cross-file schema references).
+ *   - Top-level `const X = type(...)` declarations (including `export const`).
+ *   - Field values may be:
+ *       • imported primitive markers (u8 … f64, bool, str, bytes, *Array)
+ *       • calls to imported combinators (list, opt, enumOf, flags, tuple)
+ *       • identifier references to previously-defined codecs in the file
+ *       • inline ObjectExpression literals
+ *   - `enumOf` / `flags` array args may be plain arrays or `[...] as const`.
+ */
+
+import { parseSync } from 'oxc-parser';
+import MagicString from 'magic-string';
+import { compileObject, compileUnion } from '../codegen.ts';
+import { s } from '../schema.ts';
+import type {
+  AnySchema,
+  ObjectSchema,
+  UnionSchema,
+} from '../descriptors.ts';
+
+const PKG_NAMES = new Set([
+  '@perf/serializer',
+  '@perf/serializer/index',
+]);
+
+interface ImportInfo {
+  bindings: Map;
+}
+
+interface CompiledCodec {
+  schemaName: string;
+  schemaKind: 'object' | 'union';
+  fieldsDescriptor: string;
+  encodeBody: string;
+  decodeBody: string;
+  closure: Map;
+  deps: Map;
+  id: number;
+}
+
+function fnv1a16(s: string): number {
+  let h = 0x811c9dc5;
+  for (let i = 0; i < s.length; i++) {
+    h ^= s.charCodeAt(i);
+    h = Math.imul(h, 0x01000193);
+  }
+  return ((h >>> 16) ^ h) & 0xffff;
+}
+
+const PRIMITIVES = new Set([
+  'u8', 'u16', 'u32', 'i8', 'i16', 'i32',
+  'u53', 'i53', 'u64', 'i64', 'f32', 'f64',
+  'bool', 'str', 'bytes',
+  'f32Array', 'f64Array', 'u8Array', 'u16Array', 'u32Array', 'i32Array',
+]);
+
+// oxc AST nodes vary widely per `type`; we treat them as loosely-typed objects
+// and check `.type` before reading per-shape fields.
+type AnyNode = Record;
+
+function collectImportsFromSet(program: AnyNode, aliases: Set): ImportInfo {
+  const bindings = new Map();
+  for (const stmt of program.body as AnyNode[]) {
+    if (stmt.type !== 'ImportDeclaration') continue;
+    const source = stmt.source.value as string;
+    if (!aliases.has(source)) continue;
+    for (const spec of stmt.specifiers as AnyNode[]) {
+      if (spec.type === 'ImportSpecifier') {
+        const local = spec.local.name as string;
+        const importedName = (spec.imported.name ?? spec.imported.value) as string;
+        bindings.set(local, importedName);
+      }
+    }
+  }
+  return { bindings };
+}
+
+interface Scope {
+  imports: ImportInfo;
+  locals: Map;
+}
+
+let anonCounter = 0;
+
+function evalExpr(node: AnyNode, scope: Scope): AnySchema | null {
+  switch (node.type) {
+    case 'Identifier': {
+      const name = node.name as string;
+      const exported = scope.imports.bindings.get(name);
+      if (exported && PRIMITIVES.has(exported)) {
+        const prim = (s as unknown as Record)[exported];
+        if (prim) return prim;
+      }
+      const local = scope.locals.get(name);
+      if (local) return local;
+      return null;
+    }
+    case 'CallExpression': {
+      const callee = node.callee as AnyNode;
+      if (callee.type !== 'Identifier') return null;
+      const exported = scope.imports.bindings.get(callee.name as string);
+      if (!exported) return null;
+      const args = node.arguments as AnyNode[];
+      switch (exported) {
+        case 'list': {
+          if (args.length !== 1) return null;
+          const elem = evalExpr(args[0]!, scope);
+          return elem ? s.array(elem) : null;
+        }
+        case 'opt': {
+          if (args.length !== 1) return null;
+          const elem = evalExpr(args[0]!, scope);
+          return elem ? s.optional(elem) : null;
+        }
+        case 'enumOf': {
+          if (args.length !== 1) return null;
+          const arr = unwrapAsConst(args[0]!);
+          if (!arr || arr.type !== 'ArrayExpression') return null;
+          const values: string[] = [];
+          for (const el of arr.elements as AnyNode[]) {
+            if (el && el.type === 'Literal' && typeof el.value === 'string') {
+              values.push(el.value as string);
+            } else return null;
+          }
+          return s.enum(values);
+        }
+        case 'flags': {
+          if (args.length !== 1) return null;
+          const arr = unwrapAsConst(args[0]!);
+          if (!arr || arr.type !== 'ArrayExpression') return null;
+          const names: string[] = [];
+          for (const el of arr.elements as AnyNode[]) {
+            if (el && el.type === 'Literal' && typeof el.value === 'string') {
+              names.push(el.value as string);
+            } else return null;
+          }
+          return s.bitset(names);
+        }
+        case 'tuple': {
+          const elems: AnySchema[] = [];
+          for (const a of args) {
+            const e = evalExpr(a, scope);
+            if (!e) return null;
+            elems.push(e);
+          }
+          return s.tuple(...elems);
+        }
+        default:
+          return null;
+      }
+    }
+    case 'ObjectExpression': {
+      const fields = collectFields(node, scope);
+      if (!fields) return null;
+      const inlineName = `__InlineObj_${anonCounter++}`;
+      return { kind: 'object' as const, name: inlineName, fields };
+    }
+    default:
+      return null;
+  }
+}
+
+function unwrapAsConst(node: AnyNode): AnyNode | null {
+  if (node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression') {
+    return node.expression as AnyNode;
+  }
+  if (node.type === 'ArrayExpression') return node;
+  return null;
+}
+
+function collectFields(obj: AnyNode, scope: Scope): Record | null {
+  const fields: Record = {};
+  for (const prop of obj.properties as AnyNode[]) {
+    if (prop.type !== 'Property') return null;
+    const key = prop.key as AnyNode;
+    let fname: string;
+    if (key.type === 'Identifier') fname = key.name as string;
+    else if (key.type === 'Literal' && typeof key.value === 'string') fname = key.value as string;
+    else return null;
+    const sub = evalExpr(prop.value as AnyNode, scope);
+    if (!sub) return null;
+    fields[fname] = sub;
+  }
+  return fields;
+}
+
+interface TypeCallInfo {
+  call: AnyNode;
+  declName: string;
+  fn: 'type' | 'oneOf';
+}
+
+function findTypeCalls(program: AnyNode, imports: ImportInfo): TypeCallInfo[] {
+  const calls: TypeCallInfo[] = [];
+  for (const topStmt of program.body as AnyNode[]) {
+    const stmt: AnyNode =
+      topStmt.type === 'ExportNamedDeclaration' && topStmt.declaration
+        ? (topStmt.declaration as AnyNode)
+        : topStmt;
+    if (stmt.type !== 'VariableDeclaration') continue;
+    for (const decl of stmt.declarations as AnyNode[]) {
+      const id = decl.id as AnyNode;
+      const init = decl.init as AnyNode | null;
+      if (!init || id.type !== 'Identifier' || init.type !== 'CallExpression') continue;
+      const callee = init.callee as AnyNode;
+      if (callee.type !== 'Identifier') continue;
+      const exported = imports.bindings.get(callee.name as string);
+      if (exported === 'type' || exported === 'oneOf') {
+        calls.push({ call: init, declName: id.name as string, fn: exported });
+      }
+    }
+  }
+  return calls;
+}
+
+function buildSchemaFromTypeCall(
+  info: TypeCallInfo,
+  scope: Scope,
+): ObjectSchema | UnionSchema | null {
+  const args = info.call.arguments as AnyNode[];
+  if (info.fn === 'type') {
+    let name: string;
+    let fieldsExpr: AnyNode;
+    if (args.length >= 2 && args[0]!.type === 'Literal' && typeof args[0]!.value === 'string') {
+      name = args[0]!.value as string;
+      fieldsExpr = args[1]!;
+    } else {
+      name = info.declName;
+      fieldsExpr = args[0]!;
+    }
+    if (fieldsExpr.type !== 'ObjectExpression') return null;
+    const fields = collectFields(fieldsExpr, scope);
+    if (!fields) return null;
+    return { kind: 'object', name, fields };
+  } else {
+    let name: string;
+    let disc: string;
+    let variantsExpr: AnyNode;
+    if (
+      args.length >= 3 &&
+      args[0]!.type === 'Literal' &&
+      typeof args[0]!.value === 'string'
+    ) {
+      name = args[0]!.value as string;
+      const discNode = args[1]!;
+      if (discNode.type !== 'Literal' || typeof discNode.value !== 'string') return null;
+      disc = discNode.value as string;
+      variantsExpr = args[2]!;
+    } else if (args.length >= 2) {
+      name = info.declName;
+      const discNode = args[0]!;
+      if (discNode.type !== 'Literal' || typeof discNode.value !== 'string') return null;
+      disc = discNode.value as string;
+      variantsExpr = args[1]!;
+    } else return null;
+
+    if (variantsExpr.type !== 'ObjectExpression') return null;
+    const variants: Record> = {};
+    for (const prop of variantsExpr.properties as AnyNode[]) {
+      if (prop.type !== 'Property') return null;
+      const key = prop.key as AnyNode;
+      const variantName =
+        key.type === 'Identifier' ? (key.name as string)
+        : key.type === 'Literal' && typeof key.value === 'string' ? (key.value as string)
+        : null;
+      if (!variantName) return null;
+      const variantFields = prop.value as AnyNode;
+      if (variantFields.type !== 'ObjectExpression') return null;
+      const fields = collectFields(variantFields, scope);
+      if (!fields) return null;
+      variants[variantName] = fields;
+    }
+    return s.union(name, disc, variants);
+  }
+}
+
+function emitDescriptorLiteral(schema: AnySchema): string {
+  return JSON.stringify(schema, (key, value) => {
+    if (key === 'thunk' || typeof value === 'function') return undefined;
+    return value;
+  });
+}
+
+function compileCall(
+  info: TypeCallInfo,
+  scope: Scope,
+): { src: string; compiled: CompiledCodec } | null {
+  const schema = buildSchemaFromTypeCall(info, scope);
+  if (!schema) return null;
+
+  const cg = schema.kind === 'object' ? compileObject(schema) : compileUnion(schema);
+  const id = fnv1a16(schema.name);
+  const fname = sanitize(schema.name);
+  if (cg.deps.size > 0) return null; // ref/codec deps not supported yet
+
+  const closureLines: string[] = [];
+  for (const [k, v] of cg.closure) {
+    closureLines.push(`const ${k} = ${serializeClosureValue(v)};`);
+  }
+
+  const descriptorLit = emitDescriptorLiteral(schema);
+
+  const src = `(function () {
+  ${closureLines.join('\n  ')}
+  function encode_${fname}(w, o) {
+    let pos = w.pos;
+    let buf = w.buf;
+    let view = w.view;
+    ${cg.encodeBody}
+    w.pos = pos;
+  }
+  function decode_${fname}(r) {
+    let pos = r.pos;
+    const buf = r.buf;
+    const view = r.view;
+    ${cg.decodeBody}
+  }
+  const __desc = ${descriptorLit};
+  const __codec = {
+    ...__desc,
+    id: ${id},
+    encode(v, into) {
+      if (into) { encode_${fname}(into, v); return into.bytes(); }
+      const w = new __SerWriter();
+      encode_${fname}(w, v);
+      return w.bytesCopy();
+    },
+    decode(b) {
+      const r = new __SerReader(b);
+      return decode_${fname}(r);
+    },
+    encodeInto(v, w) { encode_${fname}(w, v); },
+    decodeFrom: decode_${fname},
+    $infer: undefined,
+  };
+  Object.freeze(__codec);
+  __serRegisterPrecompiled(__codec, encode_${fname}, decode_${fname});
+  return __codec;
+})()`;
+
+  return {
+    src,
+    compiled: {
+      schemaName: schema.name,
+      schemaKind: schema.kind,
+      fieldsDescriptor: descriptorLit,
+      encodeBody: cg.encodeBody,
+      decodeBody: cg.decodeBody,
+      closure: cg.closure,
+      deps: cg.deps,
+      id,
+    },
+  };
+}
+
+function sanitize(name: string): string {
+  return name.replace(/[^A-Za-z0-9_]/g, '_');
+}
+
+function serializeClosureValue(v: unknown): string {
+  if (v === null || v === undefined) return String(v);
+  if (typeof v === 'number' || typeof v === 'boolean') return String(v);
+  if (typeof v === 'string') return JSON.stringify(v);
+  if (typeof v === 'bigint') return `${v.toString()}n`;
+  if (Array.isArray(v)) return `Object.freeze([${v.map(serializeClosureValue).join(',')}])`;
+  if (typeof v === 'object') {
+    const entries = Object.entries(v as object).map(
+      ([k, val]) => `${JSON.stringify(k)}: ${serializeClosureValue(val)}`,
+    );
+    return `Object.freeze({${entries.join(',')}})`;
+  }
+  throw new Error(`Cannot serialize closure value of type ${typeof v}`);
+}
+
+function makePrelude(importPath: string): string {
+  return `
+import { Writer as __SerWriter, Reader as __SerReader } from ${JSON.stringify(importPath)};
+const __serRegistry = (globalThis.__serRegistry ??= new Map());
+function __serRegisterPrecompiled(codec, enc, dec) {
+  __serRegistry.set(codec.id, codec);
+}
+`;
+}
+
+export interface TransformOptions {
+  importPath?: string;
+  packageAliases?: string[];
+}
+
+export interface TransformResult {
+  code: string;
+  transformedCount: number;
+}
+
+export function transform(source: string, filename = 'input.ts', options: TransformOptions = {}): TransformResult {
+  const importPath = options.importPath ?? '@perf/serializer';
+  const aliases = new Set(PKG_NAMES);
+  for (const a of options.packageAliases ?? []) aliases.add(a);
+  aliases.add(importPath);
+
+  const lang = filename.endsWith('.ts') || filename.endsWith('.tsx') ? 'ts' : 'js';
+  const parsed = parseSync(filename, source, { lang });
+  if (parsed.errors && parsed.errors.length > 0) {
+    const msgs = parsed.errors.map((e) => e.message ?? String(e)).join('\n');
+    throw new Error(`Parse errors in ${filename}:\n${msgs}`);
+  }
+
+  const program = parsed.program as unknown as AnyNode;
+  const imports = collectImportsFromSet(program, aliases);
+
+  let hasTypeImport = false;
+  for (const v of imports.bindings.values()) {
+    if (v === 'type' || v === 'oneOf') { hasTypeImport = true; break; }
+  }
+  if (!hasTypeImport) return { code: source, transformedCount: 0 };
+
+  const calls = findTypeCalls(program, imports);
+  if (calls.length === 0) return { code: source, transformedCount: 0 };
+
+  const scope: Scope = { imports, locals: new Map() };
+  const ms = new MagicString(source);
+  let transformedCount = 0;
+
+  for (const info of calls) {
+    const result = compileCall(info, scope);
+    if (!result) continue;
+    ms.overwrite(info.call.start as number, info.call.end as number, result.src);
+    const schema =
+      result.compiled.schemaKind === 'object'
+        ? ({
+            kind: 'object' as const,
+            name: result.compiled.schemaName,
+            fields: JSON.parse(result.compiled.fieldsDescriptor).fields,
+          } as ObjectSchema)
+        : ({
+            kind: 'union' as const,
+            name: result.compiled.schemaName,
+            ...JSON.parse(result.compiled.fieldsDescriptor),
+          } as UnionSchema);
+    scope.locals.set(info.declName, schema);
+    transformedCount++;
+  }
+
+  if (transformedCount === 0) return { code: source, transformedCount: 0 };
+  ms.prepend(makePrelude(importPath));
+  return { code: ms.toString(), transformedCount };
+}
diff --git a/serializer/plugin/compile/vite.ts b/serializer/plugin/compile/vite.ts
new file mode 100644
index 0000000..d9fed9d
--- /dev/null
+++ b/serializer/plugin/compile/vite.ts
@@ -0,0 +1,69 @@
+import { transform, type TransformOptions } from './transformer.ts';
+
+export interface SerializerPluginOptions extends TransformOptions {
+  /**
+   * Glob patterns to include. Defaults to all `.ts` / `.tsx` / `.mts` / `.cts` files.
+   */
+  include?: RegExp;
+  /**
+   * Glob patterns to exclude (in addition to node_modules which is always excluded).
+   */
+  exclude?: RegExp;
+}
+
+/**
+ * Vite plugin for compile-time codec generation.
+ *
+ * Add to vite.config.ts:
+ *
+ *   import { serializerCodegen } from '@perf/serializer/codegen/vite';
+ *
+ *   export default {
+ *     plugins: [serializerCodegen()],
+ *   };
+ *
+ * In source code, write:
+ *
+ *   import { type, u53, f64, str } from '@perf/serializer';
+ *   const Ticker = type('Ticker', { symbol: str, last: f64 });
+ *
+ * The plugin replaces each `type(...)` / `oneOf(...)` call with an inline IIFE
+ * that produces the same codec, eliminating runtime `new Function` compilation.
+ */
+export interface VitePlugin {
+  name: string;
+  enforce?: 'pre' | 'post';
+  transform(code: string, id: string): { code: string; map: null } | null;
+}
+
+export function serializerCodegen(options: SerializerPluginOptions = {}): VitePlugin {
+  const include = options.include ?? /\.(?:ts|tsx|mts|cts)$/;
+  const exclude = options.exclude;
+
+  return {
+    name: 'perf-serializer-codegen',
+    enforce: 'pre',
+    transform(code: string, id: string) {
+      if (id.includes('node_modules')) return null;
+      if (!include.test(id)) return null;
+      if (exclude && exclude.test(id)) return null;
+      // Quick negative: file doesn't even contain the word `type` or `oneOf`
+      if (!code.includes('type(') && !code.includes('oneOf(')) return null;
+
+      try {
+        const result = transform(code, id, options);
+        if (result.transformedCount === 0) return null;
+        return { code: result.code, map: null };
+      } catch (err) {
+        // Don't break the build on parse errors — leave source as-is so the
+        // runtime fallback handles it.
+        const msg = (err as Error).message ?? String(err);
+        // eslint-disable-next-line no-console
+        console.warn(`[perf-serializer-codegen] skipped ${id}: ${msg}`);
+        return null;
+      }
+    },
+  };
+}
+
+export default serializerCodegen;
diff --git a/serializer/plugin/descriptors.ts b/serializer/plugin/descriptors.ts
new file mode 100644
index 0000000..8260bc6
--- /dev/null
+++ b/serializer/plugin/descriptors.ts
@@ -0,0 +1,89 @@
+import type { Writer, Reader } from './io.ts';
+
+export type PrimitiveKind =
+  | 'u8' | 'u16' | 'u32'
+  | 'i8' | 'i16' | 'i32'
+  | 'u53' | 'i53'
+  | 'u64' | 'i64'
+  | 'f32' | 'f64'
+  | 'bool'
+  | 'str'
+  | 'bytes';
+
+export type TypedArrayKind = 'f32Array' | 'f64Array' | 'u8Array' | 'u16Array' | 'u32Array' | 'i32Array';
+
+export interface PrimitiveSchema {
+  readonly kind: K;
+  readonly __t?: T;
+}
+
+export interface TypedArraySchema {
+  readonly kind: K;
+  readonly __t?: T;
+}
+
+export interface ArraySchema {
+  readonly kind: 'array';
+  readonly elem: E;
+}
+
+export interface OptionalSchema {
+  readonly kind: 'optional';
+  readonly elem: E;
+}
+
+export interface EnumSchema {
+  readonly kind: 'enum';
+  readonly values: L;
+}
+
+export interface BitsetSchema {
+  readonly kind: 'bitset';
+  readonly flags: L;
+}
+
+export interface TupleSchema {
+  readonly kind: 'tuple';
+  readonly elems: E;
+}
+
+export interface ObjectSchema = Record> {
+  readonly kind: 'object';
+  readonly name: string;
+  readonly fields: F;
+}
+
+export interface UnionSchema<
+  D extends string = string,
+  V extends Record = Record,
+> {
+  readonly kind: 'union';
+  readonly name: string;
+  readonly discriminator: D;
+  readonly variants: V;
+}
+
+export interface RefSchema {
+  readonly kind: 'ref';
+  readonly thunk: () => S;
+}
+
+export interface CodecSchema {
+  readonly kind: 'codec';
+  readonly encode: (w: Writer, v: T) => void;
+  readonly decode: (r: Reader) => T;
+  readonly __t?: T;
+}
+
+export type AnySchema =
+  | PrimitiveSchema
+  | TypedArraySchema
+  | ArraySchema
+  | OptionalSchema
+  | EnumSchema
+  | BitsetSchema
+  | TupleSchema
+  | ObjectSchema
+  | UnionSchema
+  | RefSchema
+  | CodecSchema;
diff --git a/serializer/plugin/index.ts b/serializer/plugin/index.ts
new file mode 100644
index 0000000..19f6eaf
--- /dev/null
+++ b/serializer/plugin/index.ts
@@ -0,0 +1,41 @@
+// ── Simplified façade (recommended) ────────────────────────────────────────
+export {
+  type,
+  oneOf,
+  router,
+  u8, u16, u32, i8, i16, i32, u53, i53, u64, i64, f32, f64,
+  bool, str, bytes,
+  f32Array, f64Array, u8Array, u16Array, u32Array, i32Array,
+  list, opt, enumOf, flags, tuple,
+} from './api.ts';
+export type { TypeCodec, InferType, Router } from './api.ts';
+
+// ── Low-level API (advanced) ───────────────────────────────────────────────
+export { Writer, Reader } from './io.ts';
+export { s, defineSchema } from './schema.ts';
+export type { SchemaBuilder } from './schema.ts';
+export { Serializable } from './symbol.ts';
+export {
+  register,
+  registerClass,
+  serialize,
+  deserialize,
+  clearRegistry,
+} from './register.ts';
+export type { Codec } from './register.ts';
+export type {
+  AnySchema,
+  ObjectSchema,
+  UnionSchema,
+  ArraySchema,
+  OptionalSchema,
+  EnumSchema,
+  BitsetSchema,
+  TupleSchema,
+  RefSchema,
+  CodecSchema,
+  PrimitiveSchema,
+  TypedArraySchema,
+  PrimitiveKind,
+  TypedArrayKind,
+} from './descriptors.ts';
diff --git a/serializer/plugin/io.ts b/serializer/plugin/io.ts
new file mode 100644
index 0000000..777b80e
--- /dev/null
+++ b/serializer/plugin/io.ts
@@ -0,0 +1,358 @@
+const TE = new TextEncoder();
+const TD = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true });
+
+export class Writer {
+  buf: Uint8Array;
+  view: DataView;
+  pos: number;
+
+  constructor(initial = 1024) {
+    this.buf = new Uint8Array(initial);
+    this.view = new DataView(this.buf.buffer);
+    this.pos = 0;
+  }
+
+  reset(): void {
+    this.pos = 0;
+  }
+
+  bytes(): Uint8Array {
+    return this.buf.subarray(0, this.pos);
+  }
+
+  bytesCopy(): Uint8Array {
+    return this.buf.slice(0, this.pos);
+  }
+
+  ensure(n: number): void {
+    if (this.pos + n > this.buf.byteLength) this.grow(n);
+  }
+
+  private grow(n: number): void {
+    let next = this.buf.byteLength * 2;
+    const need = this.pos + n;
+    while (next < need) next *= 2;
+    const nb = new Uint8Array(next);
+    nb.set(this.buf);
+    this.buf = nb;
+    this.view = new DataView(nb.buffer);
+  }
+
+  u8(v: number): void {
+    if (this.pos + 1 > this.buf.byteLength) this.grow(1);
+    this.buf[this.pos++] = v;
+  }
+
+  u16(v: number): void {
+    if (this.pos + 2 > this.buf.byteLength) this.grow(2);
+    this.view.setUint16(this.pos, v, true);
+    this.pos += 2;
+  }
+
+  i16(v: number): void {
+    if (this.pos + 2 > this.buf.byteLength) this.grow(2);
+    this.view.setInt16(this.pos, v, true);
+    this.pos += 2;
+  }
+
+  u32(v: number): void {
+    if (this.pos + 4 > this.buf.byteLength) this.grow(4);
+    this.view.setUint32(this.pos, v, true);
+    this.pos += 4;
+  }
+
+  i32(v: number): void {
+    if (this.pos + 4 > this.buf.byteLength) this.grow(4);
+    this.view.setInt32(this.pos, v, true);
+    this.pos += 4;
+  }
+
+  f32(v: number): void {
+    if (this.pos + 4 > this.buf.byteLength) this.grow(4);
+    this.view.setFloat32(this.pos, v, true);
+    this.pos += 4;
+  }
+
+  f64(v: number): void {
+    if (this.pos + 8 > this.buf.byteLength) this.grow(8);
+    this.view.setFloat64(this.pos, v, true);
+    this.pos += 8;
+  }
+
+  bool(v: boolean): void {
+    if (this.pos + 1 > this.buf.byteLength) this.grow(1);
+    this.buf[this.pos++] = v ? 1 : 0;
+  }
+
+  varu32(v: number): void {
+    if (this.pos + 5 > this.buf.byteLength) this.grow(5);
+    while (v >= 0x80) {
+      this.buf[this.pos++] = (v & 0x7f) | 0x80;
+      v >>>= 7;
+    }
+    this.buf[this.pos++] = v;
+  }
+
+  vari32(v: number): void {
+    const z = ((v << 1) ^ (v >> 31)) >>> 0;
+    this.varu32(z);
+  }
+
+  varu53(v: number): void {
+    if (this.pos + 10 > this.buf.byteLength) this.grow(10);
+    while (v >= 0x80) {
+      this.buf[this.pos++] = (v & 0x7f) | 0x80;
+      v = Math.floor(v / 128);
+    }
+    this.buf[this.pos++] = v;
+  }
+
+  vari53(v: number): void {
+    const z = v >= 0 ? BigInt(v) * 2n : -BigInt(v) * 2n - 1n;
+    this.varbu(z);
+  }
+
+  varbu(v: bigint): void {
+    if (this.pos + 10 > this.buf.byteLength) this.grow(10);
+    while (v >= 0x80n) {
+      this.buf[this.pos++] = Number(v & 0x7fn) | 0x80;
+      v >>= 7n;
+    }
+    this.buf[this.pos++] = Number(v);
+  }
+
+  varbi(v: bigint): void {
+    const z = v >= 0n ? v << 1n : (-v << 1n) - 1n;
+    this.varbu(z);
+  }
+
+  raw(src: Uint8Array): void {
+    if (this.pos + src.length > this.buf.byteLength) this.grow(src.length);
+    this.buf.set(src, this.pos);
+    this.pos += src.length;
+  }
+
+  bytesPrefixed(src: Uint8Array): void {
+    this.varu53(src.length);
+    this.raw(src);
+  }
+
+  str(s: string): void {
+    const len = s.length;
+    if (len === 0) {
+      this.u8(0);
+      return;
+    }
+
+    if (len < 64) {
+      if (this.pos + len + 5 > this.buf.byteLength) this.grow(len + 5);
+      let allAscii = true;
+      for (let i = 0; i < len; i++) {
+        if (s.charCodeAt(i) > 127) {
+          allAscii = false;
+          break;
+        }
+      }
+      if (allAscii) {
+        let p = this.pos;
+        let lv = len;
+        while (lv >= 0x80) {
+          this.buf[p++] = (lv & 0x7f) | 0x80;
+          lv >>>= 7;
+        }
+        this.buf[p++] = lv;
+        for (let i = 0; i < len; i++) this.buf[p++] = s.charCodeAt(i);
+        this.pos = p;
+        return;
+      }
+    }
+
+    const maxBytes = len * 3;
+    if (this.pos + maxBytes + 5 > this.buf.byteLength) this.grow(maxBytes + 5);
+    const dst = this.buf.subarray(this.pos + 5);
+    const { written } = TE.encodeInto(s, dst);
+    const w = written ?? 0;
+
+    let lenBytes = 1;
+    let lv = w;
+    while (lv >= 0x80) {
+      lenBytes++;
+      lv >>>= 7;
+    }
+
+    if (lenBytes < 5 && w > 0) {
+      this.buf.copyWithin(this.pos + lenBytes, this.pos + 5, this.pos + 5 + w);
+    }
+
+    let p = this.pos;
+    lv = w;
+    while (lv >= 0x80) {
+      this.buf[p++] = (lv & 0x7f) | 0x80;
+      lv >>>= 7;
+    }
+    this.buf[p++] = lv;
+    this.pos = p + w;
+  }
+}
+
+export class Reader {
+  buf: Uint8Array;
+  view: DataView;
+  pos: number;
+  end: number;
+
+  constructor(buf: Uint8Array) {
+    this.buf = buf;
+    this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
+    this.pos = 0;
+    this.end = buf.byteLength;
+  }
+
+  reset(buf: Uint8Array): void {
+    this.buf = buf;
+    this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
+    this.pos = 0;
+    this.end = buf.byteLength;
+  }
+
+  remaining(): number {
+    return this.end - this.pos;
+  }
+
+  u8(): number {
+    return this.buf[this.pos++]!;
+  }
+
+  u16(): number {
+    const v = this.view.getUint16(this.pos, true);
+    this.pos += 2;
+    return v;
+  }
+
+  i16(): number {
+    const v = this.view.getInt16(this.pos, true);
+    this.pos += 2;
+    return v;
+  }
+
+  u32(): number {
+    const v = this.view.getUint32(this.pos, true);
+    this.pos += 4;
+    return v;
+  }
+
+  i32(): number {
+    const v = this.view.getInt32(this.pos, true);
+    this.pos += 4;
+    return v;
+  }
+
+  f32(): number {
+    const v = this.view.getFloat32(this.pos, true);
+    this.pos += 4;
+    return v;
+  }
+
+  f64(): number {
+    const v = this.view.getFloat64(this.pos, true);
+    this.pos += 8;
+    return v;
+  }
+
+  bool(): boolean {
+    return this.buf[this.pos++] !== 0;
+  }
+
+  varu32(): number {
+    let v = 0;
+    let shift = 0;
+    let byte = 0;
+    do {
+      byte = this.buf[this.pos++]!;
+      v |= (byte & 0x7f) << shift;
+      shift += 7;
+    } while (byte & 0x80);
+    return v >>> 0;
+  }
+
+  vari32(): number {
+    const z = this.varu32();
+    return (z >>> 1) ^ -(z & 1);
+  }
+
+  varu53(): number {
+    let v = 0;
+    let mult = 1;
+    let byte = 0;
+    do {
+      byte = this.buf[this.pos++]!;
+      v += (byte & 0x7f) * mult;
+      mult *= 128;
+    } while (byte & 0x80);
+    return v;
+  }
+
+  vari53(): number {
+    const z = this.varbu();
+    const v = (z & 1n) === 0n ? z >> 1n : -((z >> 1n) + 1n);
+    return Number(v);
+  }
+
+  varbu(): bigint {
+    let v = 0n;
+    let shift = 0n;
+    let byte = 0;
+    do {
+      byte = this.buf[this.pos++]!;
+      v |= BigInt(byte & 0x7f) << shift;
+      shift += 7n;
+    } while (byte & 0x80);
+    return v;
+  }
+
+  varbi(): bigint {
+    const z = this.varbu();
+    return (z & 1n) === 0n ? z >> 1n : -((z >> 1n) + 1n);
+  }
+
+  bytes(n: number): Uint8Array {
+    const slice = this.buf.subarray(this.pos, this.pos + n);
+    this.pos += n;
+    return slice;
+  }
+
+  bytesPrefixed(): Uint8Array {
+    const n = this.varu53();
+    return this.bytes(n);
+  }
+
+  str(): string {
+    const len = this.varu53();
+    if (len === 0) return '';
+    const start = this.pos;
+    const end = start + len;
+    const buf = this.buf;
+
+    if (len < 32) {
+      let allAscii = true;
+      for (let i = start; i < end; i++) {
+        if (buf[i]! > 127) {
+          allAscii = false;
+          break;
+        }
+      }
+      if (allAscii) {
+        let s = '';
+        for (let i = start; i < end; i++) {
+          s += String.fromCharCode(buf[i]!);
+        }
+        this.pos = end;
+        return s;
+      }
+    }
+
+    const slice = buf.subarray(start, end);
+    this.pos = end;
+    return TD.decode(slice);
+  }
+}
diff --git a/serializer/plugin/register.ts b/serializer/plugin/register.ts
new file mode 100644
index 0000000..b03f3a6
--- /dev/null
+++ b/serializer/plugin/register.ts
@@ -0,0 +1,197 @@
+import { compileObject, compileUnion } from './codegen.ts';
+import type { AnySchema, ObjectSchema, UnionSchema } from './descriptors.ts';
+import { Reader, Writer } from './io.ts';
+import { Serializable } from './symbol.ts';
+
+export interface Codec {
+  readonly id: number;
+  readonly name: string;
+  readonly encode: (w: Writer, v: T) => void;
+  readonly decode: (r: Reader) => T;
+}
+
+type AnyCodec = Codec;
+
+const byName = new Map();
+const byId = new Map();
+const byCtor = new WeakMap();
+
+function fnv1a16(s: string): number {
+  let h = 0x811c9dc5;
+  for (let i = 0; i < s.length; i++) {
+    h ^= s.charCodeAt(i);
+    h = Math.imul(h, 0x01000193);
+  }
+  return ((h >>> 16) ^ h) & 0xffff;
+}
+
+function collectRefDeps(schema: AnySchema, acc: Map): void {
+  switch (schema.kind) {
+    case 'ref': {
+      const target = schema.thunk();
+      if (!acc.has(target.name)) {
+        acc.set(target.name, target);
+        for (const f of Object.values(target.fields)) collectRefDeps(f, acc);
+      }
+      return;
+    }
+    case 'object':
+      for (const f of Object.values(schema.fields)) collectRefDeps(f, acc);
+      return;
+    case 'array':
+    case 'optional':
+      collectRefDeps(schema.elem, acc);
+      return;
+    case 'tuple':
+      for (const e of schema.elems) collectRefDeps(e, acc);
+      return;
+    case 'union':
+      for (const v of Object.values(schema.variants)) collectRefDeps(v, acc);
+      return;
+    default:
+      return;
+  }
+}
+
+function sanIdent(name: string): string {
+  return name.replace(/[^A-Za-z0-9_]/g, '_');
+}
+
+export function register(schema: ObjectSchema | UnionSchema): Codec {
+  const existing = byName.get(schema.name);
+  if (existing) return existing as Codec;
+
+  // Only `ref` boundaries require a separately registered codec; nested
+  // objects/unions/arrays/etc. are inlined into the generated function.
+  const refTargets = new Map();
+  if (schema.kind === 'object') {
+    for (const f of Object.values(schema.fields)) collectRefDeps(f, refTargets);
+  } else {
+    for (const v of Object.values(schema.variants)) collectRefDeps(v, refTargets);
+  }
+  for (const dep of refTargets.values()) {
+    if (!byName.has(dep.name)) register(dep);
+  }
+
+  const cg = schema.kind === 'object' ? compileObject(schema) : compileUnion(schema);
+
+  const depsObj: Record = {};
+  for (const [local, info] of cg.deps) {
+    const target = byName.get(info.targetName);
+    if (!target) throw new Error(`Dep not registered: ${info.targetName}`);
+    depsObj[local] = info.mode === 'enc' ? target.encode : target.decode;
+  }
+
+  const closureObj: Record = {};
+  for (const [k, v] of cg.closure) closureObj[k] = v;
+
+  const encDeps: string[] = [];
+  const decDeps: string[] = [];
+  for (const [local, info] of cg.deps) {
+    const line = `const ${local} = deps[${JSON.stringify(local)}];`;
+    if (info.mode === 'enc') encDeps.push(line);
+    else decDeps.push(line);
+  }
+
+  const closureDecls = [...cg.closure.keys()]
+    .map((k) => `const ${k} = closure[${JSON.stringify(k)}];`)
+    .join('\n');
+
+  const fname = sanIdent(schema.name);
+
+  const encSrc = `${encDeps.join('\n')}
+${closureDecls}
+return function encode_${fname}(w, o) {
+  let pos = w.pos;
+  let buf = w.buf;
+  let view = w.view;
+  ${cg.encodeBody}
+  w.pos = pos;
+};`;
+
+  const decSrc = `${decDeps.join('\n')}
+${closureDecls}
+return function decode_${fname}(r) {
+  let pos = r.pos;
+  const buf = r.buf;
+  const view = r.view;
+  ${cg.decodeBody}
+};`;
+
+  let encFn: (w: Writer, v: T) => void;
+  let decFn: (r: Reader) => T;
+  try {
+    encFn = new Function('deps', 'closure', encSrc)(depsObj, closureObj);
+    decFn = new Function('deps', 'closure', decSrc)(depsObj, closureObj);
+  } catch (e) {
+    throw new Error(
+      `Codegen failed for "${schema.name}": ${(e as Error).message}\n--- encode source ---\n${encSrc}\n--- decode source ---\n${decSrc}`,
+    );
+  }
+
+  const id = fnv1a16(schema.name);
+  const idExisting = byId.get(id);
+  if (idExisting) {
+    throw new Error(
+      `Schema ID collision: "${schema.name}" and "${idExisting.name}" both hash to 0x${id.toString(16)}`,
+    );
+  }
+
+  const codec: Codec = Object.freeze({
+    id,
+    name: schema.name,
+    encode: encFn,
+    decode: decFn,
+  });
+
+  byName.set(schema.name, codec);
+  byId.set(id, codec);
+  return codec;
+}
+
+export function registerClass(Ctor: new (...args: never[]) => T): Codec {
+  const cached = byCtor.get(Ctor);
+  if (cached) return cached as Codec;
+  const schema = (Ctor as unknown as Record)[Serializable] as
+    | ObjectSchema
+    | UnionSchema
+    | undefined;
+  if (!schema) {
+    throw new Error(`${Ctor.name} has no [Serializable] schema`);
+  }
+  const codec = register(schema);
+  byCtor.set(Ctor, codec);
+  return codec;
+}
+
+/**
+ * Encode a value into a framed Uint8Array (2-byte schema ID + body).
+ * If a Writer is passed, returns a view; otherwise returns a fresh copy.
+ */
+export function serialize(value: T, codec: Codec, writer?: Writer): Uint8Array {
+  if (writer) {
+    writer.u16(codec.id);
+    codec.encode(writer, value);
+    return writer.bytes();
+  }
+  const w = new Writer();
+  w.u16(codec.id);
+  codec.encode(w, value);
+  return w.bytesCopy();
+}
+
+/**
+ * Decode a framed Uint8Array by looking up its schema ID.
+ */
+export function deserialize(bytes: Uint8Array): T {
+  const r = new Reader(bytes);
+  const id = r.u16();
+  const codec = byId.get(id);
+  if (!codec) throw new Error(`Unknown schema ID: 0x${id.toString(16)}`);
+  return codec.decode(r) as T;
+}
+
+export function clearRegistry(): void {
+  byName.clear();
+  byId.clear();
+}
diff --git a/serializer/plugin/schema.ts b/serializer/plugin/schema.ts
new file mode 100644
index 0000000..34aca3b
--- /dev/null
+++ b/serializer/plugin/schema.ts
@@ -0,0 +1,111 @@
+import type {
+  AnySchema,
+  ArraySchema,
+  BitsetSchema,
+  CodecSchema,
+  EnumSchema,
+  ObjectSchema,
+  OptionalSchema,
+  PrimitiveSchema,
+  RefSchema,
+  TupleSchema,
+  TypedArraySchema,
+  UnionSchema,
+} from './descriptors.ts';
+import type { Reader, Writer } from './io.ts';
+
+type Prim = { readonly kind: K; readonly __t?: T };
+
+function p(kind: K): Prim {
+  return Object.freeze({ kind }) as Prim;
+}
+
+export const s = Object.freeze({
+  u8: p<'u8', number>('u8') as PrimitiveSchema<'u8', number>,
+  u16: p<'u16', number>('u16') as PrimitiveSchema<'u16', number>,
+  u32: p<'u32', number>('u32') as PrimitiveSchema<'u32', number>,
+  i8: p<'i8', number>('i8') as PrimitiveSchema<'i8', number>,
+  i16: p<'i16', number>('i16') as PrimitiveSchema<'i16', number>,
+  i32: p<'i32', number>('i32') as PrimitiveSchema<'i32', number>,
+  u53: p<'u53', number>('u53') as PrimitiveSchema<'u53', number>,
+  i53: p<'i53', number>('i53') as PrimitiveSchema<'i53', number>,
+  u64: p<'u64', bigint>('u64') as PrimitiveSchema<'u64', bigint>,
+  i64: p<'i64', bigint>('i64') as PrimitiveSchema<'i64', bigint>,
+  f32: p<'f32', number>('f32') as PrimitiveSchema<'f32', number>,
+  f64: p<'f64', number>('f64') as PrimitiveSchema<'f64', number>,
+  bool: p<'bool', boolean>('bool') as PrimitiveSchema<'bool', boolean>,
+  str: p<'str', string>('str') as PrimitiveSchema<'str', string>,
+  bytes: p<'bytes', Uint8Array>('bytes') as PrimitiveSchema<'bytes', Uint8Array>,
+
+  f32Array: p<'f32Array', Float32Array>('f32Array') as TypedArraySchema<'f32Array', Float32Array>,
+  f64Array: p<'f64Array', Float64Array>('f64Array') as TypedArraySchema<'f64Array', Float64Array>,
+  u8Array: p<'u8Array', Uint8Array>('u8Array') as TypedArraySchema<'u8Array', Uint8Array>,
+  u16Array: p<'u16Array', Uint16Array>('u16Array') as TypedArraySchema<'u16Array', Uint16Array>,
+  u32Array: p<'u32Array', Uint32Array>('u32Array') as TypedArraySchema<'u32Array', Uint32Array>,
+  i32Array: p<'i32Array', Int32Array>('i32Array') as TypedArraySchema<'i32Array', Int32Array>,
+
+  array(elem: E): ArraySchema {
+    return { kind: 'array', elem };
+  },
+
+  optional(elem: E): OptionalSchema {
+    return { kind: 'optional', elem };
+  },
+
+  enum(values: L): EnumSchema {
+    if (values.length === 0) throw new Error('enum requires at least one value');
+    if (values.length > 256) throw new Error('enum supports up to 256 values');
+    return { kind: 'enum', values };
+  },
+
+  bitset(flags: L): BitsetSchema {
+    if (flags.length === 0) throw new Error('bitset requires at least one flag');
+    if (flags.length > 64) throw new Error('bitset supports up to 64 flags');
+    return { kind: 'bitset', flags };
+  },
+
+  tuple(...elems: E): TupleSchema {
+    return { kind: 'tuple', elems };
+  },
+
+  union>>(
+    name: string,
+    discriminator: D,
+    variants: V,
+  ): UnionSchema }> {
+    const variantSchemas = {} as Record;
+    let i = 0;
+    for (const k of Object.keys(variants)) {
+      variantSchemas[k] = {
+        kind: 'object',
+        name: `${name}::${k}`,
+        fields: variants[k]!,
+      };
+      i++;
+      if (i > 256) throw new Error('union supports up to 256 variants');
+    }
+    return {
+      kind: 'union',
+      name,
+      discriminator,
+      variants: variantSchemas as { [K in keyof V]: ObjectSchema },
+    };
+  },
+
+  ref(thunk: () => S): RefSchema {
+    return { kind: 'ref', thunk };
+  },
+
+  codec(impl: { encode: (w: Writer, v: T) => void; decode: (r: Reader) => T }): CodecSchema {
+    return { kind: 'codec', encode: impl.encode, decode: impl.decode };
+  },
+});
+
+export type SchemaBuilder = typeof s;
+
+export function defineSchema>(
+  name: string,
+  build: (s: SchemaBuilder) => F,
+): ObjectSchema {
+  return { kind: 'object', name, fields: build(s) };
+}
diff --git a/serializer/plugin/symbol.ts b/serializer/plugin/symbol.ts
new file mode 100644
index 0000000..0a14b16
--- /dev/null
+++ b/serializer/plugin/symbol.ts
@@ -0,0 +1,10 @@
+/**
+ * The well-known symbol carrying a schema descriptor on a constructor.
+ *
+ * Registered via `Symbol.for` so multiple module copies (workers, dual builds)
+ * share identity.
+ */
+declare const SerializableBrand: unique symbol;
+
+export const Serializable = Symbol.for('@perf/serializable') as typeof SerializableBrand;
+export type SerializableKey = typeof SerializableBrand;
diff --git a/serializer/pnpm-lock.yaml b/serializer/pnpm-lock.yaml
new file mode 100644
index 0000000..387475e
--- /dev/null
+++ b/serializer/pnpm-lock.yaml
@@ -0,0 +1,2338 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    devDependencies:
+      '@vitejs/devtools':
+        specifier: ^0.1.24
+        version: 0.1.24(@pnpm/logger@1001.0.1)(typescript@6.0.3)(vite@8.0.14)
+      magic-string:
+        specifier: ^0.30.21
+        version: 0.30.21
+      oxc-parser:
+        specifier: ^0.132.0
+        version: 0.132.0
+      typescript:
+        specifier: ^6.0.3
+        version: 6.0.3
+      vite:
+        specifier: ^8.0.14
+        version: 8.0.14(@vitejs/devtools@0.1.24)(jiti@2.7.0)
+      vitest:
+        specifier: ^4.1.7
+        version: 4.1.7(vite@8.0.14)
+
+packages:
+
+  '@babel/code-frame@7.29.0':
+    resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-string-parser@7.27.1':
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.28.5':
+    resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.29.3':
+    resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  '@babel/types@7.29.0':
+    resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+    engines: {node: '>=6.9.0'}
+
+  '@emnapi/core@1.10.0':
+    resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
+
+  '@emnapi/core@1.9.2':
+    resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
+
+  '@emnapi/runtime@1.10.0':
+    resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
+
+  '@emnapi/runtime@1.9.2':
+    resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
+
+  '@emnapi/wasi-threads@1.2.1':
+    resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
+
+  '@floating-ui/core@1.7.5':
+    resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+
+  '@floating-ui/dom@1.7.6':
+    resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+
+  '@floating-ui/utils@0.2.11':
+    resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+
+  '@gwhitney/detect-indent@7.0.1':
+    resolution: {integrity: sha512-7bQW+gkKa2kKZPeJf6+c6gFK9ARxQfn+FKy9ScTBppyKRWH2KzsmweXUoklqeEiHiNVWaeP5csIdsNq6w7QhzA==}
+    engines: {node: '>=12.20'}
+
+  '@jridgewell/gen-mapping@0.3.13':
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+  '@jridgewell/remapping@2.3.5':
+    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.5':
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+  '@napi-rs/wasm-runtime@1.1.4':
+    resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
+    peerDependencies:
+      '@emnapi/core': ^1.7.1
+      '@emnapi/runtime': ^1.7.1
+
+  '@oxc-parser/binding-android-arm-eabi@0.126.0':
+    resolution: {integrity: sha512-svyoHt25J4741QJ5aa4R+h0iiBeSRt63Lr3aAZcxy2c/NeSE1IfDeMnSij6rIg7EjxkdlXzz613wUjeCeilBNA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [android]
+
+  '@oxc-parser/binding-android-arm-eabi@0.132.0':
+    resolution: {integrity: sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [android]
+
+  '@oxc-parser/binding-android-arm64@0.126.0':
+    resolution: {integrity: sha512-hPEBRKgplp1mG9GkINFsr4JVMDNrGJLOqfDaadTWpAoTnzYR5Rmv8RMvB3hJZpiNvbk1aacopdHUP1pggMQ/cw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [android]
+
+  '@oxc-parser/binding-android-arm64@0.132.0':
+    resolution: {integrity: sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [android]
+
+  '@oxc-parser/binding-darwin-arm64@0.126.0':
+    resolution: {integrity: sha512-ccRpu9sdYmznePJQG5halhs0FW5tw5a8zRSoZXOzM1OjoeZ4jiRRruFiPclsD59edoVAK1l83dvfjWz1nQi6lg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@oxc-parser/binding-darwin-arm64@0.132.0':
+    resolution: {integrity: sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@oxc-parser/binding-darwin-x64@0.126.0':
+    resolution: {integrity: sha512-CHB4zVjNSKqx8Fw9pHowzQQnjjuq04i4Ng0Avj+DixlwhwAoMYqlFbocYIlbg+q3zOLGlm7vEHm83jqEMitnyg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [darwin]
+
+  '@oxc-parser/binding-darwin-x64@0.132.0':
+    resolution: {integrity: sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [darwin]
+
+  '@oxc-parser/binding-freebsd-x64@0.126.0':
+    resolution: {integrity: sha512-RQ3nEJdcDKBfBjmLJ3Vl1d0KQERPV1P8eUrnBm7+VTYyoaJSPLVFuPg1mlD1hk3n0/879VLFMfusFkBal4ssWQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@oxc-parser/binding-freebsd-x64@0.132.0':
+    resolution: {integrity: sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@oxc-parser/binding-linux-arm-gnueabihf@0.126.0':
+    resolution: {integrity: sha512-onipc2wCDA7Bauzb4KK1mab0GsEDf4ujiIfWECdnmY/2LlzAoX3xdQRLAUyEDB1kn3yilHBrkmXDdHluyHXxiw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [linux]
+
+  '@oxc-parser/binding-linux-arm-gnueabihf@0.132.0':
+    resolution: {integrity: sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [linux]
+
+  '@oxc-parser/binding-linux-arm-musleabihf@0.126.0':
+    resolution: {integrity: sha512-5BuJJPohrV5NJ8lmcYOMbfRCUGoYH5J9HZHeuqOLwkHXWAuPMN3X1h8bC/2mWjmosdbfTtmyIdX3spS/TkqKNg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [linux]
+
+  '@oxc-parser/binding-linux-arm-musleabihf@0.132.0':
+    resolution: {integrity: sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [linux]
+
+  '@oxc-parser/binding-linux-arm64-gnu@0.126.0':
+    resolution: {integrity: sha512-r2KApRgm2pOJaduRm6GOT8x0whcr67AyejNkSdzPt34GJ+Y3axcXN2mwlTs+8lfO/SSmpO5ZJGYiHYnxEE0jkw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-arm64-gnu@0.132.0':
+    resolution: {integrity: sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-arm64-musl@0.126.0':
+    resolution: {integrity: sha512-FQ+MMh7MT0Dr/u8+RWmWKlfoeWPQyHDbhhxJShJlYtROXXPHsRs9EvmQOZZ3sx4Nn7JU8NX+oyw2YzQ7anBJcA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@oxc-parser/binding-linux-arm64-musl@0.132.0':
+    resolution: {integrity: sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@oxc-parser/binding-linux-ppc64-gnu@0.126.0':
+    resolution: {integrity: sha512-Wv/T8C98hRQhGTlx2XFyLn5raRMp9U1lOQD+YnXNgAr7wHbJJpZ8mDBU7Rw+M3WytGcGTFcr6kqgfyQeHVtLbQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-ppc64-gnu@0.132.0':
+    resolution: {integrity: sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-riscv64-gnu@0.126.0':
+    resolution: {integrity: sha512-DHx1rT1zauW0ZbLHOiQh5AC9Xs3UkWx2XmfZHs+7nnWYr3sagrufoUQC+/XPwwjMIlCFXiFGM0sFh3TyOCZwqA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-riscv64-gnu@0.132.0':
+    resolution: {integrity: sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-riscv64-musl@0.126.0':
+    resolution: {integrity: sha512-umDc2mTShH0U2zcEYf8mIJ163seLJNn54ZUZYeI5jD4qlg9izPwoLrC2aNPKlMJTu6u/ysmQWiEvIiaAG+INkw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [musl]
+
+  '@oxc-parser/binding-linux-riscv64-musl@0.132.0':
+    resolution: {integrity: sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [musl]
+
+  '@oxc-parser/binding-linux-s390x-gnu@0.126.0':
+    resolution: {integrity: sha512-PXXeWayclRtO1pxQEeCpiqIglQdhK2mAI2VX5xnsWdImzSB5GpoQ8TNw7vTCKk2k+GZuxl+q1knncidjCyUP9w==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-s390x-gnu@0.132.0':
+    resolution: {integrity: sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-x64-gnu@0.126.0':
+    resolution: {integrity: sha512-wzocjxm34TbB3bFlqG65JiLtvf6ZDg2ZxRkLLbgXwDQUNU+0MPjQN8zy/0jBKNA5fnPLk3XeVdZ7Uin+7+CVkg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-x64-gnu@0.132.0':
+    resolution: {integrity: sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@oxc-parser/binding-linux-x64-musl@0.126.0':
+    resolution: {integrity: sha512-e83uftP60jmkPs2+CW6T6A1GYzN2H6IumDAiTntv9WyHR73PI3ImHNBkYqnA3ukeKI3xjcCbhSh9QeJWmufxGQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@oxc-parser/binding-linux-x64-musl@0.132.0':
+    resolution: {integrity: sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@oxc-parser/binding-openharmony-arm64@0.126.0':
+    resolution: {integrity: sha512-4WiOILHnPrTDY2/L4mE6PZCYwLN1d3ghma6BuTJ452CCgzRMt3uFplCtR+o3r9zdUWJYb370UizpI9CUcWXr1A==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@oxc-parser/binding-openharmony-arm64@0.132.0':
+    resolution: {integrity: sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@oxc-parser/binding-wasm32-wasi@0.126.0':
+    resolution: {integrity: sha512-Y17hhnrQTrxgAxAyAq401vnN9URsAL4s5AjqpG1NDsXSlhe1yBNnns+rC2P6xcMoitgX5nKH2ryYt9oiFRlzLw==}
+    engines: {node: '>=14.0.0'}
+    cpu: [wasm32]
+
+  '@oxc-parser/binding-wasm32-wasi@0.132.0':
+    resolution: {integrity: sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [wasm32]
+
+  '@oxc-parser/binding-win32-arm64-msvc@0.126.0':
+    resolution: {integrity: sha512-Znug1u1iRvT4VC3jANz6nhGBHsFwEFMxuimYpJFwMtsB6H5FcEoZRMmH26tHkSTD03JvDmG+gB65W3ajLjPcSw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [win32]
+
+  '@oxc-parser/binding-win32-arm64-msvc@0.132.0':
+    resolution: {integrity: sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [win32]
+
+  '@oxc-parser/binding-win32-ia32-msvc@0.126.0':
+    resolution: {integrity: sha512-qrw7mx5hFFTxVSXToOA40hpnjgNB/DJprZchtB4rDKNLKqkD3F26HbzaQeH1nxAKej0efSZfJd5Sw3qdtOLGhw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [ia32]
+    os: [win32]
+
+  '@oxc-parser/binding-win32-ia32-msvc@0.132.0':
+    resolution: {integrity: sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [ia32]
+    os: [win32]
+
+  '@oxc-parser/binding-win32-x64-msvc@0.126.0':
+    resolution: {integrity: sha512-ibB1s+mPUFXvS7MFJO2jpw/aCNs/P6ifnWlRyTYB+WYBpniOiCcHQQskZneJtwcjQMDRol3RGG3ihoYnzXSY4w==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [win32]
+
+  '@oxc-parser/binding-win32-x64-msvc@0.132.0':
+    resolution: {integrity: sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [win32]
+
+  '@oxc-project/types@0.126.0':
+    resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==}
+
+  '@oxc-project/types@0.132.0':
+    resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==}
+
+  '@pnpm/constants@1001.3.1':
+    resolution: {integrity: sha512-2hf0s4pVrVEH8RvdJJ7YRKjQdiG8m0iAT26TTqXnCbK30kKwJW69VLmP5tED5zstmDRXcOeH5eRcrpkdwczQ9g==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/core-loggers@1001.0.9':
+    resolution: {integrity: sha512-pW58m3ssrwVjwhlmTXDW1dh1sv2y6R2Gl5YvQInjM2d01/5mre/sYAY4MK3XfgEShZJQxv6wVXDUvyHHJ0oizg==}
+    engines: {node: '>=18.12'}
+    peerDependencies:
+      '@pnpm/logger': '>=1001.0.0 <1002.0.0'
+
+  '@pnpm/error@1000.1.0':
+    resolution: {integrity: sha512-Dqc2IJJPjUatwc9Letw+vG29rnaMrDGi5g6WCx1HiZYm0obXbTmLygeRafMbgf+sLKXrWE1shOeiayQuczBdoA==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/graceful-fs@1000.1.0':
+    resolution: {integrity: sha512-EsMX4slK0qJN2AR0/AYohY5m0HQNYGMNe+jhN74O994zp22/WbX+PbkIKyw3UQn39yQm2+z6SgwklDxbeapsmQ==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/logger@1001.0.1':
+    resolution: {integrity: sha512-gdwlAMXC4Wc0s7Dmg/4wNybMEd/4lSd9LsXQxeg/piWY0PPXjgz1IXJWnVScx6dZRaaodWP3c1ornrw8mZdFZw==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/manifest-utils@1002.0.5':
+    resolution: {integrity: sha512-2DSwQ6pP73IuJS5mCCtPd5fibJwuAdufXKuSL/Oq1n6AggCqy8616Xea1X3RH3z5dL4mn7Z4EZ+vnX8jX3Wrfw==}
+    engines: {node: '>=18.12'}
+    peerDependencies:
+      '@pnpm/logger': ^1001.0.1
+
+  '@pnpm/read-project-manifest@1001.2.6':
+    resolution: {integrity: sha512-BcNO50lAkE4m9JaJ0WmG3m/DH/qLSvMgZywtmb/dfyyLVu5nDZfDqmOd8U+f1NhLcLMbBK6AnS3hyUqZYvw9Vg==}
+    engines: {node: '>=18.12'}
+    peerDependencies:
+      '@pnpm/logger': ^1001.0.1
+
+  '@pnpm/semver.peer-range@1000.0.0':
+    resolution: {integrity: sha512-r6VzkrdH7ZKjPmAogTNvxuV/UyS/xwHNme+ZuEFiG0UthZgqudDftYtKmG20fcfrjG1lgJbbWICA8KvZy7mmbw==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/text.comments-parser@1000.0.0':
+    resolution: {integrity: sha512-ivv/esrETOq9uMiKOC0ddVZ1BktEGsfsMQ9RWmrDpwPiqFSqWsIspnquxTBmm5GflC5N06fbqjGOpulZVYo3vQ==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/types@1001.3.0':
+    resolution: {integrity: sha512-NLTXheat/u7OEGg5M5vF6Z85zx8uKUZE0+whtX/sbFV2XL48RdnOWGPTKYuVVkv8M+launaLUTgGEXNs/ess2w==}
+    engines: {node: '>=18.12'}
+
+  '@pnpm/write-project-manifest@1000.0.16':
+    resolution: {integrity: sha512-zG68fk03ryot7TWUl9S/ShQ91uHWzIL9sVr2aQCuNHJo8G9kjsG6S0p58Zj/voahdDQeakZYYBSJ0mjNZeiJnw==}
+    engines: {node: '>=18.12'}
+
+  '@publint/pack@0.1.4':
+    resolution: {integrity: sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==}
+    engines: {node: '>=18'}
+
+  '@quansync/fs@1.0.0':
+    resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
+
+  '@rolldown/binding-android-arm64@1.0.2':
+    resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [android]
+
+  '@rolldown/binding-darwin-arm64@1.0.2':
+    resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rolldown/binding-darwin-x64@1.0.2':
+    resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rolldown/binding-freebsd-x64@1.0.2':
+    resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rolldown/binding-linux-arm-gnueabihf@1.0.2':
+    resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm]
+    os: [linux]
+
+  '@rolldown/binding-linux-arm64-gnu@1.0.2':
+    resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rolldown/binding-linux-arm64-musl@1.0.2':
+    resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@rolldown/binding-linux-ppc64-gnu@1.0.2':
+    resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rolldown/binding-linux-s390x-gnu@1.0.2':
+    resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@rolldown/binding-linux-x64-gnu@1.0.2':
+    resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rolldown/binding-linux-x64-musl@1.0.2':
+    resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@rolldown/binding-openharmony-arm64@1.0.2':
+    resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@rolldown/binding-wasm32-wasi@1.0.2':
+    resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [wasm32]
+
+  '@rolldown/binding-win32-arm64-msvc@1.0.2':
+    resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rolldown/binding-win32-x64-msvc@1.0.2':
+    resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    cpu: [x64]
+    os: [win32]
+
+  '@rolldown/debug@1.0.2':
+    resolution: {integrity: sha512-W3V3SWZVRdEWWb8u4Ppci2sQlzj0S0rh/U8BaUmbrzHZ58jz1+UPLxwCZqSqB6UkbbcE1nMu/rXlSXTQ2tszSw==}
+
+  '@rolldown/pluginutils@1.0.1':
+    resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+
+  '@standard-schema/spec@1.1.0':
+    resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+  '@tybys/wasm-util@0.10.2':
+    resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
+
+  '@types/chai@5.2.3':
+    resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+  '@types/deep-eql@4.0.2':
+    resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+  '@types/estree@1.0.9':
+    resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+  '@valibot/to-json-schema@1.7.0':
+    resolution: {integrity: sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw==}
+    peerDependencies:
+      valibot: ^1.4.0
+
+  '@vitejs/devtools-kit@0.1.24':
+    resolution: {integrity: sha512-sHM4i80Rrx4HTv/c2d28pQpeMz99GQe/2lVvJvna9t/YcoVouqpsms8oKiF/NcX8474A5gx3TtJHXWvqbov1dg==}
+    peerDependencies:
+      vite: '*'
+
+  '@vitejs/devtools-rolldown@0.1.24':
+    resolution: {integrity: sha512-KN3Bd7O0/xAq9KKjH9R1hrbw5GK3eKq563IExw8jf/FjiTllppR/iNUnlg6JHBLG3wVBFrhxL6bHjgQUAaBDBQ==}
+
+  '@vitejs/devtools@0.1.24':
+    resolution: {integrity: sha512-XPsNo8fTXtshuQrK5ddnlZzE9wtGJWs1Xfu9g1bMKZoM/XI+ool1zPSM1k8RHE09LPiUf/5E29KykResyyKx/w==}
+    hasBin: true
+    peerDependencies:
+      vite: '*'
+
+  '@vitest/expect@4.1.7':
+    resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==}
+
+  '@vitest/mocker@4.1.7':
+    resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==}
+    peerDependencies:
+      msw: ^2.4.9
+      vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+    peerDependenciesMeta:
+      msw:
+        optional: true
+      vite:
+        optional: true
+
+  '@vitest/pretty-format@4.1.7':
+    resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==}
+
+  '@vitest/runner@4.1.7':
+    resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==}
+
+  '@vitest/snapshot@4.1.7':
+    resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==}
+
+  '@vitest/spy@4.1.7':
+    resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==}
+
+  '@vitest/utils@4.1.7':
+    resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==}
+
+  '@vue/compiler-core@3.5.34':
+    resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==}
+
+  '@vue/compiler-dom@3.5.34':
+    resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==}
+
+  '@vue/compiler-sfc@3.5.34':
+    resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==}
+
+  '@vue/compiler-ssr@3.5.34':
+    resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==}
+
+  '@vue/reactivity@3.5.34':
+    resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==}
+
+  '@vue/runtime-core@3.5.34':
+    resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==}
+
+  '@vue/runtime-dom@3.5.34':
+    resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==}
+
+  '@vue/server-renderer@3.5.34':
+    resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==}
+    peerDependencies:
+      vue: 3.5.34
+
+  '@vue/shared@3.5.34':
+    resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==}
+
+  acorn@8.16.0:
+    resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
+  argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+  assertion-error@2.0.1:
+    resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+    engines: {node: '>=12'}
+
+  birpc@4.0.0:
+    resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==}
+
+  bole@5.0.29:
+    resolution: {integrity: sha512-eYR9i2ubLv5/4TFGyZsQ1cVH4jF9+qLJA72Aow+E7ZZQfqHqQNUZeX3w+pVWF76PQyjl5eDKf2xylyOOX76ozA==}
+
+  cac@7.0.0:
+    resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==}
+    engines: {node: '>=20.19.0'}
+
+  chai@6.2.2:
+    resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
+    engines: {node: '>=18'}
+
+  chokidar@5.0.0:
+    resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
+    engines: {node: '>= 20.19.0'}
+
+  confbox@0.1.8:
+    resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  cookie-es@1.2.3:
+    resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==}
+
+  crossws@0.3.5:
+    resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
+
+  csstype@3.2.3:
+    resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  defu@6.1.7:
+    resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+
+  destr@2.0.5:
+    resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+
+  detect-libc@2.1.2:
+    resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+    engines: {node: '>=8'}
+
+  devframe@0.2.2:
+    resolution: {integrity: sha512-nB5xJR0XREJSVD7Me7j9UUY1NIpPlBGYI/b6EMigeoVPaUv7/RwKf/uyc/94P00yMMxQzSMy/94NzWemDd70SQ==}
+    peerDependencies:
+      '@modelcontextprotocol/sdk': ^1.0.0
+    peerDependenciesMeta:
+      '@modelcontextprotocol/sdk':
+        optional: true
+
+  diff@9.0.0:
+    resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==}
+    engines: {node: '>=0.3.1'}
+
+  entities@7.0.1:
+    resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+    engines: {node: '>=0.12'}
+
+  error-ex@1.3.4:
+    resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+  es-module-lexer@2.1.0:
+    resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
+
+  estree-walker@2.0.2:
+    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+  estree-walker@3.0.3:
+    resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+  expect-type@1.3.0:
+    resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+    engines: {node: '>=12.0.0'}
+
+  fast-deep-equal@3.1.3:
+    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+  fast-safe-stringify@2.1.1:
+    resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+
+  fdir@6.5.0:
+    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  get-port-please@3.2.0:
+    resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==}
+
+  graceful-fs@4.2.11:
+    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+  h3@1.15.11:
+    resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==}
+
+  h3@2.0.1-rc.22:
+    resolution: {integrity: sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==}
+    engines: {node: '>=20.11.1'}
+    hasBin: true
+    peerDependencies:
+      crossws: ^0.4.1
+    peerDependenciesMeta:
+      crossws:
+        optional: true
+
+  imurmurhash@0.1.4:
+    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+    engines: {node: '>=0.8.19'}
+
+  individual@3.0.0:
+    resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==}
+
+  iron-webcrypto@1.2.1:
+    resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
+
+  is-arrayish@0.2.1:
+    resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+  is-windows@1.0.2:
+    resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
+    engines: {node: '>=0.10.0'}
+
+  jiti@2.7.0:
+    resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
+    hasBin: true
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  js-yaml@4.1.1:
+    resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+    hasBin: true
+
+  json-parse-even-better-errors@2.3.1:
+    resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  lightningcss-android-arm64@1.32.0:
+    resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [android]
+
+  lightningcss-darwin-arm64@1.32.0:
+    resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [darwin]
+
+  lightningcss-darwin-x64@1.32.0:
+    resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [darwin]
+
+  lightningcss-freebsd-x64@1.32.0:
+    resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [freebsd]
+
+  lightningcss-linux-arm-gnueabihf@1.32.0:
+    resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm]
+    os: [linux]
+
+  lightningcss-linux-arm64-gnu@1.32.0:
+    resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  lightningcss-linux-arm64-musl@1.32.0:
+    resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  lightningcss-linux-x64-gnu@1.32.0:
+    resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  lightningcss-linux-x64-musl@1.32.0:
+    resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  lightningcss-win32-arm64-msvc@1.32.0:
+    resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [arm64]
+    os: [win32]
+
+  lightningcss-win32-x64-msvc@1.32.0:
+    resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+    engines: {node: '>= 12.0.0'}
+    cpu: [x64]
+    os: [win32]
+
+  lightningcss@1.32.0:
+    resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+    engines: {node: '>= 12.0.0'}
+
+  lines-and-columns@1.2.4:
+    resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+  logs-sdk@0.0.6:
+    resolution: {integrity: sha512-G4M1C9aLLBOIWpmw/Lqk4zrap/T2IJsoUOuUDjRcVSLy6lHQqxr3wCqIT1FvvpYTUYpEwvu4utsMY42jTNvx8Q==}
+
+  lru-cache@11.5.0:
+    resolution: {integrity: sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==}
+    engines: {node: 20 || >=22}
+
+  magic-string@0.30.21:
+    resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+  mlly@1.8.2:
+    resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
+
+  mri@1.2.0:
+    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+    engines: {node: '>=4'}
+
+  mrmime@2.0.1:
+    resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+    engines: {node: '>=10'}
+
+  nanoid@3.3.12:
+    resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  node-fetch-native@1.6.7:
+    resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
+
+  node-mock-http@1.0.4:
+    resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
+
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  obug@2.1.1:
+    resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+
+  ofetch@1.5.1:
+    resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
+
+  oxc-parser@0.126.0:
+    resolution: {integrity: sha512-FktCvLby/mOHyuijZt22+nOt10dS24gGUZE3XwIbUg7Kf4+rer3/5T7RgwzazlNuVsCjPloZ3p8E+4ONT3A8Kw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+
+  oxc-parser@0.132.0:
+    resolution: {integrity: sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+
+  p-limit@7.3.0:
+    resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==}
+    engines: {node: '>=20'}
+
+  package-manager-detector@1.6.0:
+    resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
+
+  parse-json@5.2.0:
+    resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+    engines: {node: '>=8'}
+
+  pathe@2.0.3:
+    resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+  perfect-debounce@2.1.0:
+    resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@2.3.2:
+    resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+    engines: {node: '>=8.6'}
+
+  picomatch@4.0.4:
+    resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+    engines: {node: '>=12'}
+
+  pkg-types@1.3.1:
+    resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
+  postcss@8.5.15:
+    resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  publint@0.3.21:
+    resolution: {integrity: sha512-OqejcnMV6E9zel2oCrUOJEiiFkGiAAni0A6ibfQNh1k9Gu5z4F+Yso8lllam7AzmV6Do0vp7u3UpZNRBwuXaHQ==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  quansync@1.0.0:
+    resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==}
+
+  radix3@1.1.2:
+    resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
+
+  read-yaml-file@2.1.0:
+    resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==}
+    engines: {node: '>=10.13'}
+
+  readdirp@5.0.0:
+    resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
+    engines: {node: '>= 20.19.0'}
+
+  rolldown@1.0.2:
+    resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    hasBin: true
+
+  rou3@0.8.1:
+    resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==}
+
+  sade@1.8.1:
+    resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+    engines: {node: '>=6'}
+
+  semver@7.8.0:
+    resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  siginfo@2.0.0:
+    resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+  signal-exit@4.1.0:
+    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+    engines: {node: '>=14'}
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  split2@4.2.0:
+    resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+    engines: {node: '>= 10.x'}
+
+  srvx@0.11.15:
+    resolution: {integrity: sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg==}
+    engines: {node: '>=20.16.0'}
+    hasBin: true
+
+  stackback@0.0.2:
+    resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+  std-env@4.1.0:
+    resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
+
+  strip-bom@4.0.0:
+    resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+    engines: {node: '>=8'}
+
+  strip-comments-strings@1.2.0:
+    resolution: {integrity: sha512-zwF4bmnyEjZwRhaak9jUWNxc0DoeKBJ7lwSN/LEc8dQXZcUFG6auaaTQJokQWXopLdM3iTx01nQT8E4aL29DAQ==}
+
+  tinybench@2.9.0:
+    resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+  tinyexec@1.1.2:
+    resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==}
+    engines: {node: '>=18'}
+
+  tinyglobby@0.2.16:
+    resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
+    engines: {node: '>=12.0.0'}
+
+  tinyrainbow@3.1.0:
+    resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
+    engines: {node: '>=14.0.0'}
+
+  tslib@2.8.1:
+    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+  typescript@6.0.3:
+    resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  ufo@1.6.4:
+    resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
+
+  unconfig-core@7.5.0:
+    resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==}
+
+  unconfig@7.5.0:
+    resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==}
+
+  uncrypto@0.1.3:
+    resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
+
+  unplugin@3.0.0:
+    resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+
+  unstorage@1.17.5:
+    resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==}
+    peerDependencies:
+      '@azure/app-configuration': ^1.8.0
+      '@azure/cosmos': ^4.2.0
+      '@azure/data-tables': ^13.3.0
+      '@azure/identity': ^4.6.0
+      '@azure/keyvault-secrets': ^4.9.0
+      '@azure/storage-blob': ^12.26.0
+      '@capacitor/preferences': ^6 || ^7 || ^8
+      '@deno/kv': '>=0.9.0'
+      '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
+      '@planetscale/database': ^1.19.0
+      '@upstash/redis': ^1.34.3
+      '@vercel/blob': '>=0.27.1'
+      '@vercel/functions': ^2.2.12 || ^3.0.0
+      '@vercel/kv': ^1 || ^2 || ^3
+      aws4fetch: ^1.0.20
+      db0: '>=0.2.1'
+      idb-keyval: ^6.2.1
+      ioredis: ^5.4.2
+      uploadthing: ^7.4.4
+    peerDependenciesMeta:
+      '@azure/app-configuration':
+        optional: true
+      '@azure/cosmos':
+        optional: true
+      '@azure/data-tables':
+        optional: true
+      '@azure/identity':
+        optional: true
+      '@azure/keyvault-secrets':
+        optional: true
+      '@azure/storage-blob':
+        optional: true
+      '@capacitor/preferences':
+        optional: true
+      '@deno/kv':
+        optional: true
+      '@netlify/blobs':
+        optional: true
+      '@planetscale/database':
+        optional: true
+      '@upstash/redis':
+        optional: true
+      '@vercel/blob':
+        optional: true
+      '@vercel/functions':
+        optional: true
+      '@vercel/kv':
+        optional: true
+      aws4fetch:
+        optional: true
+      db0:
+        optional: true
+      idb-keyval:
+        optional: true
+      ioredis:
+        optional: true
+      uploadthing:
+        optional: true
+
+  valibot@1.4.0:
+    resolution: {integrity: sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==}
+    peerDependencies:
+      typescript: '>=5'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  vite@8.0.14:
+    resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^20.19.0 || >=22.12.0
+      '@vitejs/devtools': ^0.1.18
+      esbuild: ^0.27.0 || ^0.28.0
+      jiti: '>=1.21.0'
+      less: ^4.0.0
+      sass: ^1.70.0
+      sass-embedded: ^1.70.0
+      stylus: '>=0.54.8'
+      sugarss: ^5.0.0
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      '@vitejs/devtools':
+        optional: true
+      esbuild:
+        optional: true
+      jiti:
+        optional: true
+      less:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
+
+  vitest@4.1.7:
+    resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==}
+    engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+    hasBin: true
+    peerDependencies:
+      '@edge-runtime/vm': '*'
+      '@opentelemetry/api': ^1.9.0
+      '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+      '@vitest/browser-playwright': 4.1.7
+      '@vitest/browser-preview': 4.1.7
+      '@vitest/browser-webdriverio': 4.1.7
+      '@vitest/coverage-istanbul': 4.1.7
+      '@vitest/coverage-v8': 4.1.7
+      '@vitest/ui': 4.1.7
+      happy-dom: '*'
+      jsdom: '*'
+      vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+    peerDependenciesMeta:
+      '@edge-runtime/vm':
+        optional: true
+      '@opentelemetry/api':
+        optional: true
+      '@types/node':
+        optional: true
+      '@vitest/browser-playwright':
+        optional: true
+      '@vitest/browser-preview':
+        optional: true
+      '@vitest/browser-webdriverio':
+        optional: true
+      '@vitest/coverage-istanbul':
+        optional: true
+      '@vitest/coverage-v8':
+        optional: true
+      '@vitest/ui':
+        optional: true
+      happy-dom:
+        optional: true
+      jsdom:
+        optional: true
+
+  vue-virtual-scroller@3.0.4:
+    resolution: {integrity: sha512-3qh3c9VUVysuXynaa4fVZ3ncx3VgD7EPRiQcj+jUVZl5u/TTkD3c27XvSEu3JGJfsJt/vVTVziZ3djiiHtW4cQ==}
+    peerDependencies:
+      vue: ^3.3.0
+
+  vue@3.5.34:
+    resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  webpack-virtual-modules@0.6.2:
+    resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
+  why-is-node-running@2.3.0:
+    resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+    engines: {node: '>=8'}
+    hasBin: true
+
+  write-file-atomic@5.0.1:
+    resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+  write-yaml-file@5.0.0:
+    resolution: {integrity: sha512-FdNA4RyH1L43TlvGG8qOMIfcEczwA5ij+zLXUy3Z83CjxhLvcV7/Q/8pk22wnCgYw7PJhtK+7lhO+qqyT4NdvQ==}
+    engines: {node: '>=16.14'}
+
+  ws@8.20.1:
+    resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  yocto-queue@1.2.2:
+    resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
+    engines: {node: '>=12.20'}
+
+snapshots:
+
+  '@babel/code-frame@7.29.0':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.28.5
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
+
+  '@babel/helper-string-parser@7.27.1': {}
+
+  '@babel/helper-validator-identifier@7.28.5': {}
+
+  '@babel/parser@7.29.3':
+    dependencies:
+      '@babel/types': 7.29.0
+
+  '@babel/types@7.29.0':
+    dependencies:
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
+
+  '@emnapi/core@1.10.0':
+    dependencies:
+      '@emnapi/wasi-threads': 1.2.1
+      tslib: 2.8.1
+    optional: true
+
+  '@emnapi/core@1.9.2':
+    dependencies:
+      '@emnapi/wasi-threads': 1.2.1
+      tslib: 2.8.1
+    optional: true
+
+  '@emnapi/runtime@1.10.0':
+    dependencies:
+      tslib: 2.8.1
+    optional: true
+
+  '@emnapi/runtime@1.9.2':
+    dependencies:
+      tslib: 2.8.1
+    optional: true
+
+  '@emnapi/wasi-threads@1.2.1':
+    dependencies:
+      tslib: 2.8.1
+    optional: true
+
+  '@floating-ui/core@1.7.5':
+    dependencies:
+      '@floating-ui/utils': 0.2.11
+
+  '@floating-ui/dom@1.7.6':
+    dependencies:
+      '@floating-ui/core': 1.7.5
+      '@floating-ui/utils': 0.2.11
+
+  '@floating-ui/utils@0.2.11': {}
+
+  '@gwhitney/detect-indent@7.0.1': {}
+
+  '@jridgewell/gen-mapping@0.3.13':
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/remapping@2.3.5':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.5': {}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
+    dependencies:
+      '@emnapi/core': 1.10.0
+      '@emnapi/runtime': 1.10.0
+      '@tybys/wasm-util': 0.10.2
+    optional: true
+
+  '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
+    dependencies:
+      '@emnapi/core': 1.9.2
+      '@emnapi/runtime': 1.9.2
+      '@tybys/wasm-util': 0.10.2
+    optional: true
+
+  '@oxc-parser/binding-android-arm-eabi@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-android-arm-eabi@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-android-arm64@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-android-arm64@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-darwin-arm64@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-darwin-arm64@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-darwin-x64@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-darwin-x64@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-freebsd-x64@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-freebsd-x64@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm-gnueabihf@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm-gnueabihf@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm-musleabihf@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm-musleabihf@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm64-gnu@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm64-gnu@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm64-musl@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-arm64-musl@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-ppc64-gnu@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-ppc64-gnu@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-riscv64-gnu@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-riscv64-gnu@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-riscv64-musl@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-riscv64-musl@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-s390x-gnu@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-s390x-gnu@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-x64-gnu@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-x64-gnu@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-x64-musl@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-linux-x64-musl@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-openharmony-arm64@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-openharmony-arm64@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-wasm32-wasi@0.126.0':
+    dependencies:
+      '@emnapi/core': 1.9.2
+      '@emnapi/runtime': 1.9.2
+      '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+    optional: true
+
+  '@oxc-parser/binding-wasm32-wasi@0.132.0':
+    dependencies:
+      '@emnapi/core': 1.10.0
+      '@emnapi/runtime': 1.10.0
+      '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+    optional: true
+
+  '@oxc-parser/binding-win32-arm64-msvc@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-win32-arm64-msvc@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-win32-ia32-msvc@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-win32-ia32-msvc@0.132.0':
+    optional: true
+
+  '@oxc-parser/binding-win32-x64-msvc@0.126.0':
+    optional: true
+
+  '@oxc-parser/binding-win32-x64-msvc@0.132.0':
+    optional: true
+
+  '@oxc-project/types@0.126.0': {}
+
+  '@oxc-project/types@0.132.0': {}
+
+  '@pnpm/constants@1001.3.1': {}
+
+  '@pnpm/core-loggers@1001.0.9(@pnpm/logger@1001.0.1)':
+    dependencies:
+      '@pnpm/logger': 1001.0.1
+      '@pnpm/types': 1001.3.0
+
+  '@pnpm/error@1000.1.0':
+    dependencies:
+      '@pnpm/constants': 1001.3.1
+
+  '@pnpm/graceful-fs@1000.1.0':
+    dependencies:
+      graceful-fs: 4.2.11
+
+  '@pnpm/logger@1001.0.1':
+    dependencies:
+      bole: 5.0.29
+      split2: 4.2.0
+
+  '@pnpm/manifest-utils@1002.0.5(@pnpm/logger@1001.0.1)':
+    dependencies:
+      '@pnpm/core-loggers': 1001.0.9(@pnpm/logger@1001.0.1)
+      '@pnpm/error': 1000.1.0
+      '@pnpm/logger': 1001.0.1
+      '@pnpm/semver.peer-range': 1000.0.0
+      '@pnpm/types': 1001.3.0
+      semver: 7.8.0
+
+  '@pnpm/read-project-manifest@1001.2.6(@pnpm/logger@1001.0.1)':
+    dependencies:
+      '@gwhitney/detect-indent': 7.0.1
+      '@pnpm/error': 1000.1.0
+      '@pnpm/graceful-fs': 1000.1.0
+      '@pnpm/logger': 1001.0.1
+      '@pnpm/manifest-utils': 1002.0.5(@pnpm/logger@1001.0.1)
+      '@pnpm/text.comments-parser': 1000.0.0
+      '@pnpm/types': 1001.3.0
+      '@pnpm/write-project-manifest': 1000.0.16
+      fast-deep-equal: 3.1.3
+      is-windows: 1.0.2
+      json5: 2.2.3
+      parse-json: 5.2.0
+      read-yaml-file: 2.1.0
+      strip-bom: 4.0.0
+
+  '@pnpm/semver.peer-range@1000.0.0':
+    dependencies:
+      semver: 7.8.0
+
+  '@pnpm/text.comments-parser@1000.0.0':
+    dependencies:
+      strip-comments-strings: 1.2.0
+
+  '@pnpm/types@1001.3.0': {}
+
+  '@pnpm/write-project-manifest@1000.0.16':
+    dependencies:
+      '@pnpm/text.comments-parser': 1000.0.0
+      '@pnpm/types': 1001.3.0
+      json5: 2.2.3
+      write-file-atomic: 5.0.1
+      write-yaml-file: 5.0.0
+
+  '@publint/pack@0.1.4': {}
+
+  '@quansync/fs@1.0.0':
+    dependencies:
+      quansync: 1.0.0
+
+  '@rolldown/binding-android-arm64@1.0.2':
+    optional: true
+
+  '@rolldown/binding-darwin-arm64@1.0.2':
+    optional: true
+
+  '@rolldown/binding-darwin-x64@1.0.2':
+    optional: true
+
+  '@rolldown/binding-freebsd-x64@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-arm-gnueabihf@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-arm64-gnu@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-arm64-musl@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-ppc64-gnu@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-s390x-gnu@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-x64-gnu@1.0.2':
+    optional: true
+
+  '@rolldown/binding-linux-x64-musl@1.0.2':
+    optional: true
+
+  '@rolldown/binding-openharmony-arm64@1.0.2':
+    optional: true
+
+  '@rolldown/binding-wasm32-wasi@1.0.2':
+    dependencies:
+      '@emnapi/core': 1.10.0
+      '@emnapi/runtime': 1.10.0
+      '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+    optional: true
+
+  '@rolldown/binding-win32-arm64-msvc@1.0.2':
+    optional: true
+
+  '@rolldown/binding-win32-x64-msvc@1.0.2':
+    optional: true
+
+  '@rolldown/debug@1.0.2': {}
+
+  '@rolldown/pluginutils@1.0.1': {}
+
+  '@standard-schema/spec@1.1.0': {}
+
+  '@tybys/wasm-util@0.10.2':
+    dependencies:
+      tslib: 2.8.1
+    optional: true
+
+  '@types/chai@5.2.3':
+    dependencies:
+      '@types/deep-eql': 4.0.2
+      assertion-error: 2.0.1
+
+  '@types/deep-eql@4.0.2': {}
+
+  '@types/estree@1.0.9': {}
+
+  '@valibot/to-json-schema@1.7.0(valibot@1.4.0(typescript@6.0.3))':
+    dependencies:
+      valibot: 1.4.0(typescript@6.0.3)
+
+  '@vitejs/devtools-kit@0.1.24(typescript@6.0.3)(vite@8.0.14)':
+    dependencies:
+      birpc: 4.0.0
+      devframe: 0.2.2(typescript@6.0.3)
+      logs-sdk: 0.0.6
+      mlly: 1.8.2
+      pathe: 2.0.3
+      perfect-debounce: 2.1.0
+      tinyexec: 1.1.2
+      vite: 8.0.14(@vitejs/devtools@0.1.24)(jiti@2.7.0)
+    transitivePeerDependencies:
+      - '@modelcontextprotocol/sdk'
+      - bufferutil
+      - crossws
+      - typescript
+      - utf-8-validate
+
+  '@vitejs/devtools-rolldown@0.1.24(@pnpm/logger@1001.0.1)(typescript@6.0.3)(vite@8.0.14)(vue@3.5.34(typescript@6.0.3))':
+    dependencies:
+      '@floating-ui/dom': 1.7.6
+      '@pnpm/read-project-manifest': 1001.2.6(@pnpm/logger@1001.0.1)
+      '@rolldown/debug': 1.0.2
+      '@vitejs/devtools-kit': 0.1.24(typescript@6.0.3)(vite@8.0.14)
+      birpc: 4.0.0
+      cac: 7.0.0
+      d3-shape: 3.2.0
+      devframe: 0.2.2(typescript@6.0.3)
+      diff: 9.0.0
+      get-port-please: 3.2.0
+      h3: 2.0.1-rc.22
+      logs-sdk: 0.0.6
+      mlly: 1.8.2
+      mrmime: 2.0.1
+      p-limit: 7.3.0
+      pathe: 2.0.3
+      publint: 0.3.21
+      split2: 4.2.0
+      tinyglobby: 0.2.16
+      unconfig: 7.5.0
+      unstorage: 1.17.5
+      vue-virtual-scroller: 3.0.4(vue@3.5.34(typescript@6.0.3))
+      ws: 8.20.1
+    transitivePeerDependencies:
+      - '@azure/app-configuration'
+      - '@azure/cosmos'
+      - '@azure/data-tables'
+      - '@azure/identity'
+      - '@azure/keyvault-secrets'
+      - '@azure/storage-blob'
+      - '@capacitor/preferences'
+      - '@deno/kv'
+      - '@modelcontextprotocol/sdk'
+      - '@netlify/blobs'
+      - '@planetscale/database'
+      - '@pnpm/logger'
+      - '@upstash/redis'
+      - '@vercel/blob'
+      - '@vercel/functions'
+      - '@vercel/kv'
+      - aws4fetch
+      - bufferutil
+      - crossws
+      - db0
+      - idb-keyval
+      - ioredis
+      - typescript
+      - uploadthing
+      - utf-8-validate
+      - vite
+      - vue
+
+  '@vitejs/devtools@0.1.24(@pnpm/logger@1001.0.1)(typescript@6.0.3)(vite@8.0.14)':
+    dependencies:
+      '@vitejs/devtools-kit': 0.1.24(typescript@6.0.3)(vite@8.0.14)
+      '@vitejs/devtools-rolldown': 0.1.24(@pnpm/logger@1001.0.1)(typescript@6.0.3)(vite@8.0.14)(vue@3.5.34(typescript@6.0.3))
+      birpc: 4.0.0
+      cac: 7.0.0
+      devframe: 0.2.2(typescript@6.0.3)
+      h3: 2.0.1-rc.22
+      logs-sdk: 0.0.6
+      mlly: 1.8.2
+      obug: 2.1.1
+      pathe: 2.0.3
+      perfect-debounce: 2.1.0
+      tinyexec: 1.1.2
+      vite: 8.0.14(@vitejs/devtools@0.1.24)(jiti@2.7.0)
+      vue: 3.5.34(typescript@6.0.3)
+      ws: 8.20.1
+    transitivePeerDependencies:
+      - '@azure/app-configuration'
+      - '@azure/cosmos'
+      - '@azure/data-tables'
+      - '@azure/identity'
+      - '@azure/keyvault-secrets'
+      - '@azure/storage-blob'
+      - '@capacitor/preferences'
+      - '@deno/kv'
+      - '@modelcontextprotocol/sdk'
+      - '@netlify/blobs'
+      - '@planetscale/database'
+      - '@pnpm/logger'
+      - '@upstash/redis'
+      - '@vercel/blob'
+      - '@vercel/functions'
+      - '@vercel/kv'
+      - aws4fetch
+      - bufferutil
+      - crossws
+      - db0
+      - idb-keyval
+      - ioredis
+      - typescript
+      - uploadthing
+      - utf-8-validate
+
+  '@vitest/expect@4.1.7':
+    dependencies:
+      '@standard-schema/spec': 1.1.0
+      '@types/chai': 5.2.3
+      '@vitest/spy': 4.1.7
+      '@vitest/utils': 4.1.7
+      chai: 6.2.2
+      tinyrainbow: 3.1.0
+
+  '@vitest/mocker@4.1.7(vite@8.0.14)':
+    dependencies:
+      '@vitest/spy': 4.1.7
+      estree-walker: 3.0.3
+      magic-string: 0.30.21
+    optionalDependencies:
+      vite: 8.0.14(@vitejs/devtools@0.1.24)(jiti@2.7.0)
+
+  '@vitest/pretty-format@4.1.7':
+    dependencies:
+      tinyrainbow: 3.1.0
+
+  '@vitest/runner@4.1.7':
+    dependencies:
+      '@vitest/utils': 4.1.7
+      pathe: 2.0.3
+
+  '@vitest/snapshot@4.1.7':
+    dependencies:
+      '@vitest/pretty-format': 4.1.7
+      '@vitest/utils': 4.1.7
+      magic-string: 0.30.21
+      pathe: 2.0.3
+
+  '@vitest/spy@4.1.7': {}
+
+  '@vitest/utils@4.1.7':
+    dependencies:
+      '@vitest/pretty-format': 4.1.7
+      convert-source-map: 2.0.0
+      tinyrainbow: 3.1.0
+
+  '@vue/compiler-core@3.5.34':
+    dependencies:
+      '@babel/parser': 7.29.3
+      '@vue/shared': 3.5.34
+      entities: 7.0.1
+      estree-walker: 2.0.2
+      source-map-js: 1.2.1
+
+  '@vue/compiler-dom@3.5.34':
+    dependencies:
+      '@vue/compiler-core': 3.5.34
+      '@vue/shared': 3.5.34
+
+  '@vue/compiler-sfc@3.5.34':
+    dependencies:
+      '@babel/parser': 7.29.3
+      '@vue/compiler-core': 3.5.34
+      '@vue/compiler-dom': 3.5.34
+      '@vue/compiler-ssr': 3.5.34
+      '@vue/shared': 3.5.34
+      estree-walker: 2.0.2
+      magic-string: 0.30.21
+      postcss: 8.5.15
+      source-map-js: 1.2.1
+
+  '@vue/compiler-ssr@3.5.34':
+    dependencies:
+      '@vue/compiler-dom': 3.5.34
+      '@vue/shared': 3.5.34
+
+  '@vue/reactivity@3.5.34':
+    dependencies:
+      '@vue/shared': 3.5.34
+
+  '@vue/runtime-core@3.5.34':
+    dependencies:
+      '@vue/reactivity': 3.5.34
+      '@vue/shared': 3.5.34
+
+  '@vue/runtime-dom@3.5.34':
+    dependencies:
+      '@vue/reactivity': 3.5.34
+      '@vue/runtime-core': 3.5.34
+      '@vue/shared': 3.5.34
+      csstype: 3.2.3
+
+  '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3))':
+    dependencies:
+      '@vue/compiler-ssr': 3.5.34
+      '@vue/shared': 3.5.34
+      vue: 3.5.34(typescript@6.0.3)
+
+  '@vue/shared@3.5.34': {}
+
+  acorn@8.16.0: {}
+
+  anymatch@3.1.3:
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.2
+
+  argparse@2.0.1: {}
+
+  assertion-error@2.0.1: {}
+
+  birpc@4.0.0: {}
+
+  bole@5.0.29:
+    dependencies:
+      fast-safe-stringify: 2.1.1
+      individual: 3.0.0
+
+  cac@7.0.0: {}
+
+  chai@6.2.2: {}
+
+  chokidar@5.0.0:
+    dependencies:
+      readdirp: 5.0.0
+
+  confbox@0.1.8: {}
+
+  convert-source-map@2.0.0: {}
+
+  cookie-es@1.2.3: {}
+
+  crossws@0.3.5:
+    dependencies:
+      uncrypto: 0.1.3
+
+  csstype@3.2.3: {}
+
+  d3-path@3.1.0: {}
+
+  d3-shape@3.2.0:
+    dependencies:
+      d3-path: 3.1.0
+
+  defu@6.1.7: {}
+
+  destr@2.0.5: {}
+
+  detect-libc@2.1.2: {}
+
+  devframe@0.2.2(typescript@6.0.3):
+    dependencies:
+      '@valibot/to-json-schema': 1.7.0(valibot@1.4.0(typescript@6.0.3))
+      birpc: 4.0.0
+      cac: 7.0.0
+      h3: 2.0.1-rc.22
+      logs-sdk: 0.0.6
+      mrmime: 2.0.1
+      pathe: 2.0.3
+      valibot: 1.4.0(typescript@6.0.3)
+      ws: 8.20.1
+    transitivePeerDependencies:
+      - bufferutil
+      - crossws
+      - typescript
+      - utf-8-validate
+
+  diff@9.0.0: {}
+
+  entities@7.0.1: {}
+
+  error-ex@1.3.4:
+    dependencies:
+      is-arrayish: 0.2.1
+
+  es-module-lexer@2.1.0: {}
+
+  estree-walker@2.0.2: {}
+
+  estree-walker@3.0.3:
+    dependencies:
+      '@types/estree': 1.0.9
+
+  expect-type@1.3.0: {}
+
+  fast-deep-equal@3.1.3: {}
+
+  fast-safe-stringify@2.1.1: {}
+
+  fdir@6.5.0(picomatch@4.0.4):
+    optionalDependencies:
+      picomatch: 4.0.4
+
+  fsevents@2.3.3:
+    optional: true
+
+  get-port-please@3.2.0: {}
+
+  graceful-fs@4.2.11: {}
+
+  h3@1.15.11:
+    dependencies:
+      cookie-es: 1.2.3
+      crossws: 0.3.5
+      defu: 6.1.7
+      destr: 2.0.5
+      iron-webcrypto: 1.2.1
+      node-mock-http: 1.0.4
+      radix3: 1.1.2
+      ufo: 1.6.4
+      uncrypto: 0.1.3
+
+  h3@2.0.1-rc.22:
+    dependencies:
+      rou3: 0.8.1
+      srvx: 0.11.15
+
+  imurmurhash@0.1.4: {}
+
+  individual@3.0.0: {}
+
+  iron-webcrypto@1.2.1: {}
+
+  is-arrayish@0.2.1: {}
+
+  is-windows@1.0.2: {}
+
+  jiti@2.7.0: {}
+
+  js-tokens@4.0.0: {}
+
+  js-yaml@4.1.1:
+    dependencies:
+      argparse: 2.0.1
+
+  json-parse-even-better-errors@2.3.1: {}
+
+  json5@2.2.3: {}
+
+  lightningcss-android-arm64@1.32.0:
+    optional: true
+
+  lightningcss-darwin-arm64@1.32.0:
+    optional: true
+
+  lightningcss-darwin-x64@1.32.0:
+    optional: true
+
+  lightningcss-freebsd-x64@1.32.0:
+    optional: true
+
+  lightningcss-linux-arm-gnueabihf@1.32.0:
+    optional: true
+
+  lightningcss-linux-arm64-gnu@1.32.0:
+    optional: true
+
+  lightningcss-linux-arm64-musl@1.32.0:
+    optional: true
+
+  lightningcss-linux-x64-gnu@1.32.0:
+    optional: true
+
+  lightningcss-linux-x64-musl@1.32.0:
+    optional: true
+
+  lightningcss-win32-arm64-msvc@1.32.0:
+    optional: true
+
+  lightningcss-win32-x64-msvc@1.32.0:
+    optional: true
+
+  lightningcss@1.32.0:
+    dependencies:
+      detect-libc: 2.1.2
+    optionalDependencies:
+      lightningcss-android-arm64: 1.32.0
+      lightningcss-darwin-arm64: 1.32.0
+      lightningcss-darwin-x64: 1.32.0
+      lightningcss-freebsd-x64: 1.32.0
+      lightningcss-linux-arm-gnueabihf: 1.32.0
+      lightningcss-linux-arm64-gnu: 1.32.0
+      lightningcss-linux-arm64-musl: 1.32.0
+      lightningcss-linux-x64-gnu: 1.32.0
+      lightningcss-linux-x64-musl: 1.32.0
+      lightningcss-win32-arm64-msvc: 1.32.0
+      lightningcss-win32-x64-msvc: 1.32.0
+
+  lines-and-columns@1.2.4: {}
+
+  logs-sdk@0.0.6:
+    dependencies:
+      magic-string: 0.30.21
+      oxc-parser: 0.126.0
+      unplugin: 3.0.0
+
+  lru-cache@11.5.0: {}
+
+  magic-string@0.30.21:
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  mlly@1.8.2:
+    dependencies:
+      acorn: 8.16.0
+      pathe: 2.0.3
+      pkg-types: 1.3.1
+      ufo: 1.6.4
+
+  mri@1.2.0: {}
+
+  mrmime@2.0.1: {}
+
+  nanoid@3.3.12: {}
+
+  node-fetch-native@1.6.7: {}
+
+  node-mock-http@1.0.4: {}
+
+  normalize-path@3.0.0: {}
+
+  obug@2.1.1: {}
+
+  ofetch@1.5.1:
+    dependencies:
+      destr: 2.0.5
+      node-fetch-native: 1.6.7
+      ufo: 1.6.4
+
+  oxc-parser@0.126.0:
+    dependencies:
+      '@oxc-project/types': 0.126.0
+    optionalDependencies:
+      '@oxc-parser/binding-android-arm-eabi': 0.126.0
+      '@oxc-parser/binding-android-arm64': 0.126.0
+      '@oxc-parser/binding-darwin-arm64': 0.126.0
+      '@oxc-parser/binding-darwin-x64': 0.126.0
+      '@oxc-parser/binding-freebsd-x64': 0.126.0
+      '@oxc-parser/binding-linux-arm-gnueabihf': 0.126.0
+      '@oxc-parser/binding-linux-arm-musleabihf': 0.126.0
+      '@oxc-parser/binding-linux-arm64-gnu': 0.126.0
+      '@oxc-parser/binding-linux-arm64-musl': 0.126.0
+      '@oxc-parser/binding-linux-ppc64-gnu': 0.126.0
+      '@oxc-parser/binding-linux-riscv64-gnu': 0.126.0
+      '@oxc-parser/binding-linux-riscv64-musl': 0.126.0
+      '@oxc-parser/binding-linux-s390x-gnu': 0.126.0
+      '@oxc-parser/binding-linux-x64-gnu': 0.126.0
+      '@oxc-parser/binding-linux-x64-musl': 0.126.0
+      '@oxc-parser/binding-openharmony-arm64': 0.126.0
+      '@oxc-parser/binding-wasm32-wasi': 0.126.0
+      '@oxc-parser/binding-win32-arm64-msvc': 0.126.0
+      '@oxc-parser/binding-win32-ia32-msvc': 0.126.0
+      '@oxc-parser/binding-win32-x64-msvc': 0.126.0
+
+  oxc-parser@0.132.0:
+    dependencies:
+      '@oxc-project/types': 0.132.0
+    optionalDependencies:
+      '@oxc-parser/binding-android-arm-eabi': 0.132.0
+      '@oxc-parser/binding-android-arm64': 0.132.0
+      '@oxc-parser/binding-darwin-arm64': 0.132.0
+      '@oxc-parser/binding-darwin-x64': 0.132.0
+      '@oxc-parser/binding-freebsd-x64': 0.132.0
+      '@oxc-parser/binding-linux-arm-gnueabihf': 0.132.0
+      '@oxc-parser/binding-linux-arm-musleabihf': 0.132.0
+      '@oxc-parser/binding-linux-arm64-gnu': 0.132.0
+      '@oxc-parser/binding-linux-arm64-musl': 0.132.0
+      '@oxc-parser/binding-linux-ppc64-gnu': 0.132.0
+      '@oxc-parser/binding-linux-riscv64-gnu': 0.132.0
+      '@oxc-parser/binding-linux-riscv64-musl': 0.132.0
+      '@oxc-parser/binding-linux-s390x-gnu': 0.132.0
+      '@oxc-parser/binding-linux-x64-gnu': 0.132.0
+      '@oxc-parser/binding-linux-x64-musl': 0.132.0
+      '@oxc-parser/binding-openharmony-arm64': 0.132.0
+      '@oxc-parser/binding-wasm32-wasi': 0.132.0
+      '@oxc-parser/binding-win32-arm64-msvc': 0.132.0
+      '@oxc-parser/binding-win32-ia32-msvc': 0.132.0
+      '@oxc-parser/binding-win32-x64-msvc': 0.132.0
+
+  p-limit@7.3.0:
+    dependencies:
+      yocto-queue: 1.2.2
+
+  package-manager-detector@1.6.0: {}
+
+  parse-json@5.2.0:
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      error-ex: 1.3.4
+      json-parse-even-better-errors: 2.3.1
+      lines-and-columns: 1.2.4
+
+  pathe@2.0.3: {}
+
+  perfect-debounce@2.1.0: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@2.3.2: {}
+
+  picomatch@4.0.4: {}
+
+  pkg-types@1.3.1:
+    dependencies:
+      confbox: 0.1.8
+      mlly: 1.8.2
+      pathe: 2.0.3
+
+  postcss@8.5.15:
+    dependencies:
+      nanoid: 3.3.12
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  publint@0.3.21:
+    dependencies:
+      '@publint/pack': 0.1.4
+      package-manager-detector: 1.6.0
+      picocolors: 1.1.1
+      sade: 1.8.1
+
+  quansync@1.0.0: {}
+
+  radix3@1.1.2: {}
+
+  read-yaml-file@2.1.0:
+    dependencies:
+      js-yaml: 4.1.1
+      strip-bom: 4.0.0
+
+  readdirp@5.0.0: {}
+
+  rolldown@1.0.2:
+    dependencies:
+      '@oxc-project/types': 0.132.0
+      '@rolldown/pluginutils': 1.0.1
+    optionalDependencies:
+      '@rolldown/binding-android-arm64': 1.0.2
+      '@rolldown/binding-darwin-arm64': 1.0.2
+      '@rolldown/binding-darwin-x64': 1.0.2
+      '@rolldown/binding-freebsd-x64': 1.0.2
+      '@rolldown/binding-linux-arm-gnueabihf': 1.0.2
+      '@rolldown/binding-linux-arm64-gnu': 1.0.2
+      '@rolldown/binding-linux-arm64-musl': 1.0.2
+      '@rolldown/binding-linux-ppc64-gnu': 1.0.2
+      '@rolldown/binding-linux-s390x-gnu': 1.0.2
+      '@rolldown/binding-linux-x64-gnu': 1.0.2
+      '@rolldown/binding-linux-x64-musl': 1.0.2
+      '@rolldown/binding-openharmony-arm64': 1.0.2
+      '@rolldown/binding-wasm32-wasi': 1.0.2
+      '@rolldown/binding-win32-arm64-msvc': 1.0.2
+      '@rolldown/binding-win32-x64-msvc': 1.0.2
+
+  rou3@0.8.1: {}
+
+  sade@1.8.1:
+    dependencies:
+      mri: 1.2.0
+
+  semver@7.8.0: {}
+
+  siginfo@2.0.0: {}
+
+  signal-exit@4.1.0: {}
+
+  source-map-js@1.2.1: {}
+
+  split2@4.2.0: {}
+
+  srvx@0.11.15: {}
+
+  stackback@0.0.2: {}
+
+  std-env@4.1.0: {}
+
+  strip-bom@4.0.0: {}
+
+  strip-comments-strings@1.2.0: {}
+
+  tinybench@2.9.0: {}
+
+  tinyexec@1.1.2: {}
+
+  tinyglobby@0.2.16:
+    dependencies:
+      fdir: 6.5.0(picomatch@4.0.4)
+      picomatch: 4.0.4
+
+  tinyrainbow@3.1.0: {}
+
+  tslib@2.8.1:
+    optional: true
+
+  typescript@6.0.3: {}
+
+  ufo@1.6.4: {}
+
+  unconfig-core@7.5.0:
+    dependencies:
+      '@quansync/fs': 1.0.0
+      quansync: 1.0.0
+
+  unconfig@7.5.0:
+    dependencies:
+      '@quansync/fs': 1.0.0
+      defu: 6.1.7
+      jiti: 2.7.0
+      quansync: 1.0.0
+      unconfig-core: 7.5.0
+
+  uncrypto@0.1.3: {}
+
+  unplugin@3.0.0:
+    dependencies:
+      '@jridgewell/remapping': 2.3.5
+      picomatch: 4.0.4
+      webpack-virtual-modules: 0.6.2
+
+  unstorage@1.17.5:
+    dependencies:
+      anymatch: 3.1.3
+      chokidar: 5.0.0
+      destr: 2.0.5
+      h3: 1.15.11
+      lru-cache: 11.5.0
+      node-fetch-native: 1.6.7
+      ofetch: 1.5.1
+      ufo: 1.6.4
+
+  valibot@1.4.0(typescript@6.0.3):
+    optionalDependencies:
+      typescript: 6.0.3
+
+  vite@8.0.14(@vitejs/devtools@0.1.24)(jiti@2.7.0):
+    dependencies:
+      lightningcss: 1.32.0
+      picomatch: 4.0.4
+      postcss: 8.5.15
+      rolldown: 1.0.2
+      tinyglobby: 0.2.16
+    optionalDependencies:
+      '@vitejs/devtools': 0.1.24(@pnpm/logger@1001.0.1)(typescript@6.0.3)(vite@8.0.14)
+      fsevents: 2.3.3
+      jiti: 2.7.0
+
+  vitest@4.1.7(vite@8.0.14):
+    dependencies:
+      '@vitest/expect': 4.1.7
+      '@vitest/mocker': 4.1.7(vite@8.0.14)
+      '@vitest/pretty-format': 4.1.7
+      '@vitest/runner': 4.1.7
+      '@vitest/snapshot': 4.1.7
+      '@vitest/spy': 4.1.7
+      '@vitest/utils': 4.1.7
+      es-module-lexer: 2.1.0
+      expect-type: 1.3.0
+      magic-string: 0.30.21
+      obug: 2.1.1
+      pathe: 2.0.3
+      picomatch: 4.0.4
+      std-env: 4.1.0
+      tinybench: 2.9.0
+      tinyexec: 1.1.2
+      tinyglobby: 0.2.16
+      tinyrainbow: 3.1.0
+      vite: 8.0.14(@vitejs/devtools@0.1.24)(jiti@2.7.0)
+      why-is-node-running: 2.3.0
+    transitivePeerDependencies:
+      - msw
+
+  vue-virtual-scroller@3.0.4(vue@3.5.34(typescript@6.0.3)):
+    dependencies:
+      vue: 3.5.34(typescript@6.0.3)
+
+  vue@3.5.34(typescript@6.0.3):
+    dependencies:
+      '@vue/compiler-dom': 3.5.34
+      '@vue/compiler-sfc': 3.5.34
+      '@vue/runtime-dom': 3.5.34
+      '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3))
+      '@vue/shared': 3.5.34
+    optionalDependencies:
+      typescript: 6.0.3
+
+  webpack-virtual-modules@0.6.2: {}
+
+  why-is-node-running@2.3.0:
+    dependencies:
+      siginfo: 2.0.0
+      stackback: 0.0.2
+
+  write-file-atomic@5.0.1:
+    dependencies:
+      imurmurhash: 0.1.4
+      signal-exit: 4.1.0
+
+  write-yaml-file@5.0.0:
+    dependencies:
+      js-yaml: 4.1.1
+      write-file-atomic: 5.0.1
+
+  ws@8.20.1: {}
+
+  yocto-queue@1.2.2: {}
diff --git a/serializer/src/main.ts b/serializer/src/main.ts
new file mode 100644
index 0000000..079589f
--- /dev/null
+++ b/serializer/src/main.ts
@@ -0,0 +1,208 @@
+/**
+ * Examples of the @perf/serializer API.
+ *
+ * When the AOT plugin is enabled in vite.config.ts, every `type(...)` /
+ * `oneOf(...)` call below is replaced at build time with a precomputed
+ * codec literal. The runtime never calls `new Function`.
+ */
+
+import {
+  type,
+  oneOf,
+  router,
+  u53,
+  f64,
+  str,
+  list,
+  opt,
+  enumOf,
+  flags,
+  Writer,
+} from '@perf/serializer';
+
+// ─── Tee output to console + 
 if we're in a browser ──────────
+
+const out =
+  typeof document !== 'undefined' ? document.getElementById('out') : null;
+
+function log(...args: unknown[]): void {
+  console.log(...args);
+  if (out) {
+    const text = args
+      .map((a) => {
+        if (typeof a === 'string') return a;
+        if (a instanceof Uint8Array) return `Uint8Array(${a.length})`;
+        try {
+          return JSON.stringify(a);
+        } catch {
+          return String(a);
+        }
+      })
+      .join(' ');
+    out.textContent += text + '\n';
+  }
+}
+
+// ─── Example 1: flat schema ────────────────────────────────────────────────
+//
+// Define a Ticker, infer its TypeScript type from the schema, encode and
+// decode it. This is the 90% use case.
+
+const Ticker = type('Ticker', {
+  symbol: str,
+  last: f64,
+  bid: f64,
+  ask: f64,
+  volume: f64,
+});
+
+type Ticker = typeof Ticker.$infer;
+// → { symbol: string; last: number; bid: number; ask: number; volume: number }
+
+const ticker: Ticker = {
+  symbol: 'BTC-USD',
+  last: 67891.23,
+  bid: 67890.5,
+  ask: 67892.0,
+  volume: 1234567.89,
+};
+
+const tickerBytes = Ticker.encode(ticker);
+const tickerBack = Ticker.decode(tickerBytes);
+
+log('Example 1: Ticker');
+log(`  encoded ${tickerBytes.length} bytes (JSON would be ${JSON.stringify(ticker).length})`);
+log('  decoded:', tickerBack);
+
+// ─── Example 2: nested object + list ───────────────────────────────────────
+//
+// `Level` is itself a codec; it can be passed as a field in another `type()`.
+// The transformer inlines its encode/decode into the parent — no per-element
+// function dispatch.
+
+const Level = type('Level', { p: f64, q: f64 });
+
+const Book = type('Book', {
+  symbol: str,
+  ts: u53,
+  bids: list(Level),
+  asks: list(Level),
+});
+
+const book = {
+  symbol: 'BTC-USD',
+  ts: Date.now(),
+  bids: [
+    { p: 67890.5, q: 0.1 },
+    { p: 67890.0, q: 0.3 },
+    { p: 67889.5, q: 0.5 },
+  ],
+  asks: [
+    { p: 67891.0, q: 0.2 },
+    { p: 67891.5, q: 0.4 },
+  ],
+};
+
+const bookBytes = Book.encode(book);
+log('\nExample 2: OrderBook');
+log(`  encoded ${bookBytes.length} bytes (JSON: ${JSON.stringify(book).length})`);
+log('  decoded.bids[0]:', Book.decode(bookBytes).bids[0]);
+
+// ─── Example 3: enum + bitset + optional ───────────────────────────────────
+//
+// Enums encode as one byte. Bitsets pack up to 32 flags into a u32. Optional
+// fields add one presence byte.
+
+const Order = type('Order', {
+  id: u53,
+  side: enumOf(['buy', 'sell'] as const),
+  qty: f64,
+  price: opt(f64), // market orders have no price
+  flags: flags(['ioc', 'post_only', 'reduce_only'] as const),
+});
+
+const marketOrder = {
+  id: 1,
+  side: 'buy' as const,
+  qty: 0.5,
+  price: undefined,
+  flags: { ioc: true, post_only: false, reduce_only: false },
+};
+
+const limitOrder = {
+  id: 2,
+  side: 'sell' as const,
+  qty: 0.5,
+  price: 67900,
+  flags: { ioc: false, post_only: true, reduce_only: false },
+};
+
+log('\nExample 3: Orders (enum + opt + flags)');
+log(`  market: ${Order.encode(marketOrder).length}b`);
+log(`  limit:  ${Order.encode(limitOrder).length}b`);
+
+// ─── Example 4: discriminated union ────────────────────────────────────────
+//
+// Each variant has its own field map. The discriminator (`kind`) is written as
+// a one-byte tag, then the variant's fields follow.
+
+const Event = oneOf('Event', 'kind', {
+  fill: { price: f64, qty: f64 },
+  cancel: { reason: str },
+  expire: { at: u53 },
+});
+
+const events = [
+  { kind: 'fill', price: 67891.0, qty: 0.5 },
+  { kind: 'cancel', reason: 'user-requested' },
+  { kind: 'expire', at: Date.now() + 60_000 },
+];
+
+log('\nExample 4: Events (union)');
+for (const e of events) {
+  const bytes = Event.encode(e as never);
+  log(`  ${e.kind.padEnd(7)}: ${bytes.length}b`);
+}
+
+// ─── Example 5: pooled writer (hot path) ───────────────────────────────────
+//
+// Reuse one Writer across many encodes. `encodeInto` writes directly into the
+// pooled buffer; `bytes()` returns a zero-copy view. This is the lowest-overhead
+// path — what to use inside a tight WebSocket frame loop.
+
+const w = new Writer(1024);
+
+function sendTicker(t: Ticker, socket: { send(bytes: Uint8Array): void }): void {
+  w.reset();
+  Ticker.encodeInto(t, w);
+  socket.send(w.bytes());
+}
+
+const fakeSocket = {
+  send(bytes: Uint8Array): void {
+    log(`  socket received ${bytes.length} bytes`);
+  },
+};
+
+log('\nExample 5: pooled writer hot path');
+sendTicker(ticker, fakeSocket);
+sendTicker({ ...ticker, last: 67900 }, fakeSocket);
+
+// ─── Example 6: router (framed multi-message protocol) ─────────────────────
+//
+// `router` prepends a 2-byte schema-ID frame on encode and dispatches on it on
+// decode. Use this when one socket carries many message types.
+
+const proto = router(Ticker, Book, Order, Event);
+
+const framedTicker = proto.encode(ticker, Ticker);
+const framedBook = proto.encode(book, Book);
+
+log('\nExample 6: router (framed)');
+log(`  framed ticker: ${framedTicker.length}b (first 2 bytes = schema id)`);
+log(`  framed book:   ${framedBook.length}b`);
+
+const dispatched1 = proto.decode(framedTicker);
+const dispatched2 = proto.decode(framedBook);
+log('  dispatched ticker symbol:', (dispatched1 as Ticker).symbol);
+log('  dispatched book bids[0]:', (dispatched2 as typeof book).bids[0]);
diff --git a/serializer/test/api.test.ts b/serializer/test/api.test.ts
new file mode 100644
index 0000000..b5970fc
--- /dev/null
+++ b/serializer/test/api.test.ts
@@ -0,0 +1,161 @@
+import { test, expect } from 'vitest';
+import {
+  type,
+  oneOf,
+  router,
+  u53,
+  f64,
+  str,
+  bool,
+  list,
+  opt,
+  enumOf,
+  flags,
+  tuple,
+  f64Array,
+  clearRegistry,
+  Writer,
+  Reader,
+} from '../plugin/index.ts';
+
+test('type() — flat schema round-trip', () => {
+  clearRegistry();
+  const Ticker = type('Ticker', {
+    symbol: str,
+    last: f64,
+    volume: f64,
+  });
+
+  type Ticker = typeof Ticker.$infer;
+
+  const v: Ticker = { symbol: 'BTC-USD', last: 100.5, volume: 1234 };
+  const bytes = Ticker.encode(v);
+  expect(Ticker.decode(bytes)).toEqual(v);
+});
+
+test('type() — nested object via inline reference', () => {
+  clearRegistry();
+  const Price = type('Price', { value: f64, scale: u53 });
+  const Order = type('Order', {
+    id: u53,
+    price: Price,
+    qty: f64,
+  });
+
+  const v = { id: 42, price: { value: 100.5, scale: 2 }, qty: 0.5 };
+  expect(Order.decode(Order.encode(v))).toEqual(v);
+});
+
+test('type() — anonymous (no name) still works', () => {
+  clearRegistry();
+  const Anon = type({ x: u53, y: f64 });
+  const v = { x: 1, y: 2.5 };
+  expect(Anon.decode(Anon.encode(v))).toEqual(v);
+});
+
+test('list, opt, enumOf, flags, tuple — combinators', () => {
+  clearRegistry();
+  const T = type('Combo', {
+    tags: list(str),
+    maybe: opt(f64),
+    side: enumOf(['buy', 'sell'] as const),
+    f: flags(['ioc', 'post_only'] as const),
+    point: tuple(f64, f64),
+  });
+
+  const v = {
+    tags: ['a', 'b'],
+    maybe: 3.14,
+    side: 'buy' as const,
+    f: { ioc: true, post_only: false },
+    point: [1, 2] as [number, number],
+  };
+
+  expect(T.decode(T.encode(v))).toEqual(v);
+
+  const v2 = { ...v, maybe: undefined };
+  expect(T.decode(T.encode(v2))).toEqual(v2);
+});
+
+test('type() — typed array field', () => {
+  clearRegistry();
+  const Signal = type('Signal', { name: str, samples: f64Array });
+  const v = { name: 'x', samples: new Float64Array([1, 2, 3, 4]) };
+  const back = Signal.decode(Signal.encode(v));
+  expect(back.name).toBe('x');
+  expect(back.samples).toBeInstanceOf(Float64Array);
+  expect(Array.from(back.samples)).toEqual([1, 2, 3, 4]);
+});
+
+test('oneOf() — discriminated union', () => {
+  clearRegistry();
+  const Event = oneOf('Event', 'kind', {
+    fill: { price: f64, qty: f64 },
+    cancel: { reason: str },
+  });
+
+  const a = { kind: 'fill', price: 100, qty: 0.5 };
+  const b = { kind: 'cancel', reason: 'user' };
+  expect(Event.decode(Event.encode(a as never))).toEqual(a);
+  expect(Event.decode(Event.encode(b as never))).toEqual(b);
+});
+
+test('encodeInto / decodeFrom — pooled writer hot path', () => {
+  clearRegistry();
+  const T = type('Pooled', { x: u53, y: f64 });
+  const w = new Writer(256);
+
+  const v = { x: 42, y: 3.14 };
+
+  w.reset();
+  T.encodeInto(v, w);
+  const bytes = w.bytes();
+  expect(bytes.length).toBeGreaterThan(0);
+
+  const r = new Reader(bytes);
+  expect(T.decodeFrom(r)).toEqual(v);
+});
+
+test('router() — framed multi-type dispatch', () => {
+  clearRegistry();
+  const A = type('A', { x: u53 });
+  const B = type('B', { y: str });
+
+  const proto = router(A, B);
+
+  const bytesA = proto.encode({ x: 7 }, A);
+  const bytesB = proto.encode({ y: 'hi' }, B);
+
+  expect(proto.decode(bytesA)).toEqual({ x: 7 });
+  expect(proto.decode(bytesB)).toEqual({ y: 'hi' });
+});
+
+test('typeof T.$infer — TS inference works at compile time', () => {
+  clearRegistry();
+  const Order = type('OrderInf', {
+    id: u53,
+    price: f64,
+    side: enumOf(['buy', 'sell'] as const),
+    active: bool,
+    tags: list(str),
+  });
+
+  type Order = typeof Order.$infer;
+
+  const v: Order = {
+    id: 1,
+    price: 100,
+    side: 'buy',
+    active: true,
+    tags: ['a'],
+  };
+  expect(Order.decode(Order.encode(v))).toEqual(v);
+});
+
+test('codec id is deterministic by name', () => {
+  clearRegistry();
+  const A = type('Same', { x: u53 });
+  clearRegistry();
+  const B = type('Same', { y: f64 });
+  expect(A.id).toBe(B.id);
+});
diff --git a/serializer/test/bench/aot.bench.ts b/serializer/test/bench/aot.bench.ts
new file mode 100644
index 0000000..179a123
--- /dev/null
+++ b/serializer/test/bench/aot.bench.ts
@@ -0,0 +1,197 @@
+/**
+ * Compile-time (AOT) vs runtime codegen benchmark.
+ *
+ * The AOT codecs are produced by running our transformer on a sample TS file
+ * at bench startup, writing the result to a temp file, and importing it. The
+ * runtime codecs come from the regular `type(...)` runtime path.
+ */
+
+import { bench, beforeAll, afterAll, describe } from 'vitest';
+import { writeFileSync, rmSync, existsSync } from 'node:fs';
+import { join, dirname } from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import { transform } from '../../plugin/compile/transformer.ts';
+import {
+  type as runtimeType,
+  type TypeCodec,
+  u53,
+  f64,
+  str,
+  list,
+  enumOf,
+  flags,
+  clearRegistry,
+  Reader,
+  Writer,
+} from '../../plugin/index.ts';
+
+const HERE = dirname(fileURLToPath(import.meta.url));
+const GEN_FILE = join(HERE, '__aot_codecs.ts');
+
+const AOT_SOURCE = `
+import { type, u53, f64, str, list, enumOf, flags } from '../../plugin/index.ts';
+
+export const Ticker = type('AotTicker', {
+  symbol: str,
+  last: f64,
+  bid: f64,
+  ask: f64,
+  volume: f64,
+});
+
+export const Order = type('AotOrder', {
+  id: u53,
+  account: u53,
+  symbol: str,
+  side: enumOf(['buy', 'sell'] as const),
+  price: f64,
+  qty: f64,
+  filledQty: f64,
+  ts: u53,
+  flags: flags(['ioc', 'post_only', 'reduce_only'] as const),
+});
+
+export const Level = type('AotLevel', { p: f64, q: f64 });
+
+export const Book = type('AotBook', {
+  symbol: str,
+  ts: u53,
+  bids: list(Level),
+  asks: list(Level),
+});
+`;
+
+interface AotCodec {
+  encode: (v: unknown, w?: Writer) => Uint8Array;
+  decode: (b: Uint8Array) => unknown;
+  encodeInto: (v: unknown, w: Writer) => void;
+  decodeFrom: (r: Reader) => unknown;
+  id: number;
+}
+
+let aot: Record;
+let rtTicker: TypeCodec;
+let rtOrder: TypeCodec;
+let rtLevel: TypeCodec;
+let rtBook: TypeCodec;
+
+const ticker = {
+  symbol: 'BTC-USD', last: 67891.23, bid: 67890.5, ask: 67892.0, volume: 1234567.89,
+};
+const order = {
+  id: 9876543210, account: 12345678, symbol: 'BTC-USD',
+  side: 'buy' as const, price: 67500.5, qty: 0.125, filledQty: 0,
+  ts: 1716100000123,
+  flags: { ioc: false, post_only: true, reduce_only: false },
+};
+const book = {
+  symbol: 'BTC-USD',
+  ts: 1716100000123,
+  bids: Array.from({ length: 1000 }, (_, i) => ({ p: 67890 - i * 0.5, q: 0.1 + (i % 100) * 0.01 })),
+  asks: Array.from({ length: 1000 }, (_, i) => ({ p: 67891 + i * 0.5, q: 0.1 + (i % 100) * 0.01 })),
+};
+
+const wT = new Writer(256);
+const wO = new Writer(256);
+const wB = new Writer(64 * 1024);
+
+let tickerAot: Uint8Array;
+let tickerRt: Uint8Array;
+let orderAot: Uint8Array;
+let orderRt: Uint8Array;
+let bookAot: Uint8Array;
+let bookRt: Uint8Array;
+
+beforeAll(async () => {
+  // Build the AOT module on the fly.
+  const transformed = transform(AOT_SOURCE, GEN_FILE, {
+    importPath: '../../plugin/index.ts',
+    packageAliases: ['../../plugin/index.ts'],
+  });
+  writeFileSync(GEN_FILE, transformed.code, 'utf8');
+  const url = `${pathToFileURL(GEN_FILE).href}?t=${Date.now()}`;
+  aot = (await import(/* @vite-ignore */ url)) as Record;
+
+  // Runtime equivalents with non-colliding names.
+  clearRegistry();
+  rtTicker = runtimeType('RtTicker', {
+    symbol: str, last: f64, bid: f64, ask: f64, volume: f64,
+  });
+  rtOrder = runtimeType('RtOrder', {
+    id: u53, account: u53, symbol: str,
+    side: enumOf(['buy', 'sell'] as const),
+    price: f64, qty: f64, filledQty: f64, ts: u53,
+    flags: flags(['ioc', 'post_only', 'reduce_only'] as const),
+  });
+  rtLevel = runtimeType('RtLevel', { p: f64, q: f64 });
+  rtBook = runtimeType('RtBook', {
+    symbol: str, ts: u53, bids: list(rtLevel), asks: list(rtLevel),
+  });
+
+  // Pre-encode for decode benches
+  tickerAot = aot.Ticker!.encode(ticker);
+  wT.reset(); rtTicker.encodeInto(ticker, wT); tickerRt = wT.bytes().slice();
+  orderAot = aot.Order!.encode(order);
+  wO.reset(); rtOrder.encodeInto(order, wO); orderRt = wO.bytes().slice();
+  bookAot = aot.Book!.encode(book);
+  wB.reset(); rtBook.encodeInto(book, wB); bookRt = wB.bytes().slice();
+});
+
+afterAll(() => {
+  if (existsSync(GEN_FILE)) rmSync(GEN_FILE, { force: true });
+});
+
+describe('encode ticker (AOT vs runtime)', () => {
+  bench('AOT (compiled)', () => {
+    wT.reset();
+    aot.Ticker!.encodeInto(ticker, wT);
+  });
+  bench('runtime (new Function)', () => {
+    wT.reset();
+    rtTicker.encodeInto(ticker, wT);
+  });
+});
+
+describe('encode order (AOT vs runtime)', () => {
+  bench('AOT (compiled)', () => {
+    wO.reset();
+    aot.Order!.encodeInto(order, wO);
+  });
+  bench('runtime', () => {
+    wO.reset();
+    rtOrder.encodeInto(order, wO);
+  });
+});
+
+describe('encode book 1000 levels (AOT vs runtime)', () => {
+  bench('AOT (compiled)', () => {
+    wB.reset();
+    aot.Book!.encodeInto(book, wB);
+  });
+  bench('runtime', () => {
+    wB.reset();
+    rtBook.encodeInto(book, wB);
+  });
+});
+
+describe('decode ticker (AOT vs runtime)', () => {
+  bench('AOT (compiled)', () => {
+    const r = new Reader(tickerAot);
+    aot.Ticker!.decodeFrom(r);
+  });
+  bench('runtime', () => {
+    const r = new Reader(tickerRt);
+    rtTicker.decodeFrom(r);
+  });
+});
+
+describe('decode book 1000 levels (AOT vs runtime)', () => {
+  bench('AOT (compiled)', () => {
+    const r = new Reader(bookAot);
+    aot.Book!.decodeFrom(r);
+  });
+  bench('runtime', () => {
+    const r = new Reader(bookRt);
+    rtBook.decodeFrom(r);
+  });
+});
diff --git a/serializer/test/bench/codec.bench.ts b/serializer/test/bench/codec.bench.ts
new file mode 100644
index 0000000..9a201c4
--- /dev/null
+++ b/serializer/test/bench/codec.bench.ts
@@ -0,0 +1,121 @@
+import { bench, describe } from 'vitest';
+import { Reader, Writer, deserialize, serialize } from '../../plugin/index.ts';
+import {
+  buildBook,
+  buildOrder,
+  buildTicker,
+  registerAll,
+} from './payloads.ts';
+
+const codecs = registerAll();
+
+const ticker = buildTicker();
+const order = buildOrder();
+const book = buildBook(1000);
+
+// Pre-allocated pooled Writers (sized generously so we don't measure grow()).
+const wTicker = new Writer(256);
+const wOrder = new Writer(256);
+const wBook = new Writer(64 * 1024);
+
+// Pre-encoded buffers for decode benches.
+const tickerJSON = JSON.stringify(ticker);
+const orderJSON = JSON.stringify(order);
+const bookJSON = JSON.stringify(book);
+
+const tickerBin = serialize(ticker, codecs.ticker);
+const orderBin = serialize(order, codecs.order);
+const bookBin = serialize(book, codecs.book);
+
+// One-time payload-size print on module load so it appears once in bench output.
+// eslint-disable-next-line no-console
+console.log(
+  '\n--- payload sizes ---\n' +
+    `ticker  | json: ${tickerJSON.length}b  bin: ${tickerBin.length}b  (${((tickerBin.length / tickerJSON.length) * 100).toFixed(0)}%)\n` +
+    `order   | json: ${orderJSON.length}b   bin: ${orderBin.length}b   (${((orderBin.length / orderJSON.length) * 100).toFixed(0)}%)\n` +
+    `book    | json: ${bookJSON.length}b    bin: ${bookBin.length}b    (${((bookBin.length / bookJSON.length) * 100).toFixed(0)}%)\n`,
+);
+
+describe('encode ticker (5 fields)', () => {
+  bench('JSON.stringify', () => {
+    JSON.stringify(ticker);
+  });
+  bench('codec.encode (pooled)', () => {
+    wTicker.reset();
+    codecs.ticker.encode(wTicker, ticker);
+  });
+});
+
+describe('encode order (10 fields + bitset)', () => {
+  bench('JSON.stringify', () => {
+    JSON.stringify(order);
+  });
+  bench('codec.encode (pooled)', () => {
+    wOrder.reset();
+    codecs.order.encode(wOrder, order);
+  });
+});
+
+describe('encode book (1000 levels)', () => {
+  bench('JSON.stringify', () => {
+    JSON.stringify(book);
+  });
+  bench('codec.encode (pooled)', () => {
+    wBook.reset();
+    codecs.book.encode(wBook, book);
+  });
+});
+
+describe('decode ticker', () => {
+  bench('JSON.parse', () => {
+    JSON.parse(tickerJSON);
+  });
+  bench('codec.decode', () => {
+    const r = new Reader(tickerBin);
+    r.pos = 2;
+    codecs.ticker.decode(r);
+  });
+});
+
+describe('decode order', () => {
+  bench('JSON.parse', () => {
+    JSON.parse(orderJSON);
+  });
+  bench('codec.decode', () => {
+    const r = new Reader(orderBin);
+    r.pos = 2;
+    codecs.order.decode(r);
+  });
+});
+
+describe('decode book (1000 levels)', () => {
+  bench('JSON.parse', () => {
+    JSON.parse(bookJSON);
+  });
+  bench('codec.decode', () => {
+    const r = new Reader(bookBin);
+    r.pos = 2;
+    codecs.book.decode(r);
+  });
+});
+
+describe('roundtrip ticker', () => {
+  bench('JSON', () => {
+    JSON.parse(JSON.stringify(ticker));
+  });
+  bench('codec (pooled)', () => {
+    wTicker.reset();
+    codecs.ticker.encode(wTicker, ticker);
+    const r = new Reader(wTicker.bytes());
+    codecs.ticker.decode(r);
+  });
+});
+
+describe('serialize+deserialize ticker (with frame)', () => {
+  bench('JSON', () => {
+    JSON.parse(JSON.stringify(ticker));
+  });
+  bench('serialize/deserialize (framed)', () => {
+    deserialize(serialize(ticker, codecs.ticker));
+  });
+});
diff --git a/serializer/test/bench/payloads.ts b/serializer/test/bench/payloads.ts
new file mode 100644
index 0000000..7eab1f8
--- /dev/null
+++ b/serializer/test/bench/payloads.ts
@@ -0,0 +1,118 @@
+import { defineSchema, register, s } from '../../plugin/index.ts';
+import type { Codec } from '../../plugin/index.ts';
+
+export const TickerSchema = defineSchema('BenchTicker', (s) => ({
+  symbol: s.str,
+  last: s.f64,
+  bid: s.f64,
+  ask: s.f64,
+  volume: s.f64,
+}));
+
+export const OrderSchema = defineSchema('BenchOrder', (s) => ({
+  id: s.u53,
+  account: s.u53,
+  symbol: s.str,
+  side: s.enum(['buy', 'sell'] as const),
+  type: s.enum(['limit', 'market', 'stop', 'stop_limit'] as const),
+  price: s.f64,
+  qty: s.f64,
+  filledQty: s.f64,
+  ts: s.u53,
+  flags: s.bitset(['ioc', 'post_only', 'reduce_only'] as const),
+}));
+
+export const LevelSchema = defineSchema('BenchLevel', (s) => ({
+  p: s.f64,
+  q: s.f64,
+}));
+
+export const BookSchema = defineSchema('BenchBook', (s) => ({
+  symbol: s.str,
+  ts: s.u53,
+  bids: s.array(LevelSchema),
+  asks: s.array(LevelSchema),
+}));
+
+export interface Ticker {
+  symbol: string;
+  last: number;
+  bid: number;
+  ask: number;
+  volume: number;
+}
+
+export interface Order {
+  id: number;
+  account: number;
+  symbol: string;
+  side: 'buy' | 'sell';
+  type: 'limit' | 'market' | 'stop' | 'stop_limit';
+  price: number;
+  qty: number;
+  filledQty: number;
+  ts: number;
+  flags: { ioc: boolean; post_only: boolean; reduce_only: boolean };
+}
+
+export interface Level {
+  p: number;
+  q: number;
+}
+
+export interface Book {
+  symbol: string;
+  ts: number;
+  bids: Level[];
+  asks: Level[];
+}
+
+export function buildTicker(): Ticker {
+  return {
+    symbol: 'BTC-USD',
+    last: 67891.23,
+    bid: 67890.5,
+    ask: 67892.0,
+    volume: 1234567.89,
+  };
+}
+
+export function buildOrder(): Order {
+  return {
+    id: 9876543210,
+    account: 12345678,
+    symbol: 'BTC-USD',
+    side: 'buy',
+    type: 'limit',
+    price: 67500.5,
+    qty: 0.125,
+    filledQty: 0,
+    ts: 1716100000123,
+    flags: { ioc: false, post_only: true, reduce_only: false },
+  };
+}
+
+export function buildBook(depth: number): Book {
+  const bids: Level[] = new Array(depth);
+  const asks: Level[] = new Array(depth);
+  for (let i = 0; i < depth; i++) {
+    bids[i] = { p: 67890 - i * 0.5, q: 0.1 + (i % 100) * 0.01 };
+    asks[i] = { p: 67891 + i * 0.5, q: 0.1 + (i % 100) * 0.01 };
+  }
+  return { symbol: 'BTC-USD', ts: 1716100000123, bids, asks };
+}
+
+export interface Codecs {
+  ticker: Codec;
+  order: Codec;
+  level: Codec;
+  book: Codec;
+}
+
+export function registerAll(): Codecs {
+  const ticker = register(TickerSchema);
+  const order = register(OrderSchema);
+  const level = register(LevelSchema);
+  const book = register(BookSchema);
+  return { ticker, order, level, book };
+}
diff --git a/serializer/test/bench/results.md b/serializer/test/bench/results.md
new file mode 100644
index 0000000..c18d6b0
--- /dev/null
+++ b/serializer/test/bench/results.md
@@ -0,0 +1,117 @@
+# Benchmark results
+
+Hardware: Intel Xeon (Icelake) @ 2.46 GHz, Windows Server 2019
+Runtime: Node.js 24.14.0 (x64)
+Tool: mitata
+Date: 2026-05-21
+
+Reproduce: `npm run bench` from the `serializer/` directory. Numbers below use the avg (the p75 column where they diverge).
+
+## Payload sizes
+
+| Workload | JSON bytes | Binary bytes | Binary/JSON ratio |
+|---|---:|---:|---:|
+| Ticker (5 fields) | 82 | 42 | **0.51** |
+| Order (10 fields + bitset) | 203 | 52 | **0.26** |
+| Book snapshot (1000 levels) | 48,577 | 32,020 | **0.66** |
+
+## Encode (lower is better)
+
+| Workload | JSON.stringify | codec.encode (pooled) | Speedup vs JSON |
+|---|---:|---:|---:|
+| Ticker | 598.4 ns | **52.2 ns** | **11.5×** |
+| Order | 1,170 ns | **123.4 ns** | **9.5×** |
+| Book (1000 levels) | 437 µs | **10.2 µs** | **42.9×** |
+
+## Decode (lower is better)
+
+| Workload | JSON.parse | codec.decode | Speedup vs JSON |
+|---|---:|---:|---:|
+| Ticker | 696.3 ns | **311.0 ns** | **2.2×** |
+| Order | 1,440 ns | **360.6 ns** | **4.0×** |
+| Book (1000 levels) | 497 µs | **24–28 µs** (high GC variance) | **17–20×** |
+
+## Roundtrip
+
+| | ns/iter | Note |
+|---|---:|---|
+| `JSON.parse(JSON.stringify(...))` | 1,400 ns | baseline |
+| Pooled codec encode + Reader decode | **418 ns** | **3.35× faster** |
+| Un-pooled `serialize` + `deserialize` (framed) | 2,180 ns | 1.55× slower |
+
+The un-pooled `serialize()` allocates a fresh Writer + DataView + Uint8Array on every call. Hot paths must pool a Writer.
+
+## What changed vs v1 baseline
+
+The v1 codec used method-call style for every operation: every `w.f64(v)` was a method dispatch with internal property reads on `this.buf`, `this.view`, `this.pos`. The optimized codec restructures the generated functions around four V8-friendly patterns:
+
+| # | Optimization | Effect |
+|---|---|---|
+| 1 | Lift `pos`, `buf`, `view` to function-local `let/const` at start; sync `w.pos = pos` at end | Replaces N×3 property loads with N register reads |
+| 2 | Inline all bounded-size ops (`u8`–`f64`, `bool`, varints, `enum`, `bitset`) using the lifted locals | Eliminates the method-call cost per primitive |
+| 3 | Pre-`ensure` for the bounded prefix of each schema in a single bounds check | One growth check per ~10 fields instead of one per field |
+| 4 | Inline nested objects/arrays/unions/tuples — no per-element function dispatch | Tight inner loops for array (e.g., order book levels) |
+| 5 | Closure-captured frozen map for `enum` with ≥4 values; ternary chain for 2–3 | Avoids string-switch overhead |
+| 6 | For array elements that are themselves bounded, pre-`ensure(L * elementMax)` once outside the loop, then run a loop with no per-iteration ensure | Order-book encode goes from method-per-level to inline-per-level |
+
+Unbounded leaves (`str`, `bytes`, `typedArray`, `ref`, `codec`) still go through the Writer/Reader methods, with a small sync/refetch dance around the call.
+
+## Before / after (v1 baseline → v2 optimized, both avg ns)
+
+| Workload | v1 baseline | v2 optimized | Improvement |
+|---|---:|---:|---:|
+| Ticker encode | 77.4 ns | **52.2 ns** | **1.48×** |
+| Order encode | 130.4 ns | **123.4 ns** | 1.06× |
+| Book encode | 27.9 µs | **10.2 µs** | **2.73×** |
+| Ticker decode | 308.4 ns | 311.0 ns | ~same |
+| Order decode | 368.3 ns | 360.6 ns | ~same |
+| Book decode | 26.1 µs | 24.4 µs (p75) | 1.07× |
+
+The decode side gains less than encode because Node's `JSON.parse` was already not the bottleneck — most of the decode time goes to allocating the result object and the string for `symbol`/`reason` fields, which the codec also has to do.
+
+The book encode at **2.7× faster than v1 baseline (43× faster than JSON.stringify)** is the headline number: inlining the per-level encoder into the outer loop turned 1000 function calls per snapshot into 1000 inline `view.setFloat64(pos, ...)` pairs sharing one `ensure()`.
+
+## What didn't pan out
+
+We tried `String.fromCharCode.apply(null, buf.subarray(start, end))` for ASCII strings in the 8–64 char range. On Node 24 it was consistently slower than the simple `s += String.fromCharCode(buf[i])` loop for the short strings dominating exchange payloads — the variadic-args wrapper has its own overhead. Reverted.
+
+## Generated source — example
+
+For the Ticker schema (after optimization), the encoder body produced by codegen is:
+
+```js
+function encode_BenchTicker(w, o) {
+  let pos = w.pos;
+  let buf = w.buf;
+  let view = w.view;
+
+  if (pos + 33 > buf.byteLength) {
+    w.pos = pos; w.grow(33); buf = w.buf; view = w.view;
+  }
+  // varu53 symbol-length and 4 × f64 are bounded, but the str body itself isn't:
+  // (the str field flushes the bounded prefix, calls w.str, then refetches)
+
+  w.pos = pos; w.str(o["symbol"]); pos = w.pos; buf = w.buf; view = w.view;
+
+  if (pos + 32 > buf.byteLength) {
+    w.pos = pos; w.grow(32); buf = w.buf; view = w.view;
+  }
+  view.setFloat64(pos, o["last"], true);   pos += 8;
+  view.setFloat64(pos, o["bid"], true);    pos += 8;
+  view.setFloat64(pos, o["ask"], true);    pos += 8;
+  view.setFloat64(pos, o["volume"], true); pos += 8;
+
+  w.pos = pos;
+}
+```
+
+No `this.` indirections, no method dispatch for the floats, one ensure for the 4-float run. The result is **52 ns per Ticker encode**, ~12× faster than `JSON.stringify`.
+
+## Acceptance bar (from plan)
+
+| Target | Actual | Status |
+|---|---|---|
+| Encode ≥ 3× faster than JSON.stringify on medium-order workload | 9.5× | exceeded |
+| Decode ≥ 5× faster than JSON.parse on order-book workload | 17–20× | exceeded |
+| Payload ≤ 60% of JSON byte length on numeric-heavy data | 26% (Order) / 66% (Book) | partial (Book is f64-dense, little to compress) |
+| Zero deopt events on hot benchmark loop | one-time OSR transition only | acceptable |
diff --git a/serializer/test/contract.test.ts b/serializer/test/contract.test.ts
new file mode 100644
index 0000000..1fc1534
--- /dev/null
+++ b/serializer/test/contract.test.ts
@@ -0,0 +1,79 @@
+import { test, expect } from 'vitest';
+import {
+  clearRegistry,
+  defineSchema,
+  deserialize,
+  registerClass,
+  Serializable,
+  serialize,
+} from '../plugin/index.ts';
+
+test('class with [Serializable] static schema registers and round-trips', () => {
+  clearRegistry();
+
+  class Order {
+    id!: number;
+    price!: number;
+    qty!: number;
+    side!: 'buy' | 'sell';
+
+    static [Serializable] = defineSchema('OrderClass', (s) => ({
+      id: s.u53,
+      price: s.f64,
+      qty: s.f64,
+      side: s.enum(['buy', 'sell'] as const),
+    }));
+  }
+
+  const codec = registerClass(Order);
+
+  const v = { id: 42, price: 100.5, qty: 1.5, side: 'buy' as const };
+  const bytes = serialize(v, codec);
+  const decoded = deserialize(bytes);
+  expect(decoded).toEqual(v);
+});
+
+test('registerClass caches by constructor', () => {
+  clearRegistry();
+
+  class A {
+    static [Serializable] = defineSchema('AClass', (s) => ({ x: s.u8 }));
+  }
+  const c1 = registerClass(A);
+  const c2 = registerClass(A);
+  expect(c1).toBe(c2);
+});
+
+test('registerClass throws for class missing [Serializable]', () => {
+  clearRegistry();
+
+  class B {}
+
+  expect(() => registerClass(B)).toThrow(/\[Serializable\] schema/);
+});
+
+test('Symbol.serializable is shared across module boundaries via Symbol.for', () => {
+  const looked = Symbol.for('@perf/serializable');
+  expect(looked).toBe(Serializable);
+});
+
+test('codec.id is deterministic for the schema name', () => {
+  clearRegistry();
+  const A = defineSchema('SameName', (s) => ({ x: s.u8 }));
+
+  clearRegistry();
+  const codecA = registerClass(
+    class extends Object {
+      static [Serializable] = A;
+    },
+  );
+
+  clearRegistry();
+  const codecB = registerClass(
+    class extends Object {
+      static [Serializable] = A;
+    },
+  );
+
+  expect(codecA.id).toBe(codecB.id);
+});
diff --git a/serializer/test/fuzz.test.ts b/serializer/test/fuzz.test.ts
new file mode 100644
index 0000000..469f43e
--- /dev/null
+++ b/serializer/test/fuzz.test.ts
@@ -0,0 +1,122 @@
+import { test, expect } from 'vitest';
+import {
+  clearRegistry,
+  defineSchema,
+  deserialize,
+  register,
+  s,
+  serialize,
+} from '../plugin/index.ts';
+
+function rng(seed: number): () => number {
+  let a = seed >>> 0;
+  return () => {
+    a = (a + 0x6d2b79f5) >>> 0;
+    let t = a;
+    t = Math.imul(t ^ (t >>> 15), t | 1);
+    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
+    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
+  };
+}
+
+const r = rng(0xc0ffee);
+
+function randFloat(): number {
+  const bucket = Math.floor(r() * 6);
+  switch (bucket) {
+    case 0: return 0;
+    case 1: return r() * 100;
+    case 2: return r() * 1e10;
+    case 3: return -r() * 100;
+    case 4: return (r() - 0.5) * 1e-6;
+    default: return r() * 1000;
+  }
+}
+
+function randInt(maxBits = 32): number {
+  const v = Math.floor(r() * 2 ** maxBits);
+  return v >>> 0;
+}
+
+function randString(): string {
+  const len = Math.floor(r() * 30);
+  let s = '';
+  for (let i = 0; i < len; i++) s += String.fromCharCode(32 + Math.floor(r() * 95));
+  return s;
+}
+
+test('fuzz: 2000 random ticker round-trips', () => {
+  clearRegistry();
+  const Ticker = defineSchema('FuzzTicker', (s) => ({
+    symbol: s.str,
+    last: s.f64,
+    volume: s.f64,
+    count: s.u32,
+    asks: s.array(s.f64),
+  }));
+  const codec = register(Ticker);
+
+  for (let i = 0; i < 2000; i++) {
+    const v = {
+      symbol: randString(),
+      last: randFloat(),
+      volume: randFloat(),
+      count: randInt(32),
+      asks: Array.from({ length: Math.floor(r() * 10) }, randFloat),
+    };
+    expect(deserialize(serialize(v, codec)), `iteration ${i}`).toEqual(v);
+  }
+});
+
+test('fuzz: 1000 random nested orders', () => {
+  clearRegistry();
+  const Price = defineSchema('FuzzPrice', (s) => ({ value: s.f64, scale: s.u8 }));
+  register(Price);
+  const Order = defineSchema('FuzzOrder', (s) => ({
+    id: s.u53,
+    symbol: s.str,
+    price: Price,
+    qty: s.f64,
+    side: s.enum(['buy', 'sell'] as const),
+    tags: s.array(s.str),
+    flags: s.bitset(['ioc', 'post_only', 'reduce_only'] as const),
+  }));
+  const codec = register(Order);
+
+  for (let i = 0; i < 1000; i++) {
+    const v = {
+      id: Math.floor(r() * 2 ** 40),
+      symbol: randString(),
+      price: { value: randFloat(), scale: randInt(8) & 0xff },
+      qty: randFloat(),
+      side: (r() < 0.5 ? 'buy' : 'sell') as 'buy' | 'sell',
+      tags: Array.from({ length: Math.floor(r() * 5) }, randString),
+      flags: {
+        ioc: r() < 0.5,
+        post_only: r() < 0.5,
+        reduce_only: r() < 0.5,
+      },
+    };
+    expect(deserialize(serialize(v, codec)), `iteration ${i}`).toEqual(v);
+  }
+});
+
+test('fuzz: 500 random unions', () => {
+  clearRegistry();
+  const Event = s.union('FuzzEvent', 'kind', {
+    fill: { price: s.f64, qty: s.f64 },
+    cancel: { reason: s.str },
+    expire: { at: s.u53 },
+  });
+  const codec = register(Event);
+
+  for (let i = 0; i < 500; i++) {
+    const which = Math.floor(r() * 3);
+    let v: unknown;
+    if (which === 0) v = { kind: 'fill', price: randFloat(), qty: randFloat() };
+    else if (which === 1) v = { kind: 'cancel', reason: randString() };
+    else v = { kind: 'expire', at: Math.floor(r() * 2 ** 40) };
+
+    expect(deserialize(serialize(v, codec)), `iteration ${i}`).toEqual(v);
+  }
+});
diff --git a/serializer/test/primitives.test.ts b/serializer/test/primitives.test.ts
new file mode 100644
index 0000000..ee254b0
--- /dev/null
+++ b/serializer/test/primitives.test.ts
@@ -0,0 +1,175 @@
+import { test, expect } from 'vitest';
+import { Reader, Writer } from '../plugin/io.ts';
+
+function roundtrip(write: (w: Writer) => void, read: (r: Reader) => T): T {
+  const w = new Writer(16);
+  write(w);
+  return read(new Reader(w.bytes()));
+}
+
+test('u8/u16/u32 round-trip with boundary values', () => {
+  for (const v of [0, 1, 127, 128, 255]) {
+    expect(roundtrip((w) => w.u8(v), (r) => r.u8())).toBe(v);
+  }
+  for (const v of [0, 1, 0xff, 0x100, 0xffff]) {
+    expect(roundtrip((w) => w.u16(v), (r) => r.u16())).toBe(v);
+  }
+  for (const v of [0, 1, 0xffff, 0x10000, 0xffffffff]) {
+    expect(roundtrip((w) => w.u32(v), (r) => r.u32())).toBe(v);
+  }
+});
+
+test('i16/i32 signed round-trip including negatives', () => {
+  for (const v of [-32768, -1, 0, 1, 32767]) {
+    expect(roundtrip((w) => w.i16(v), (r) => r.i16())).toBe(v);
+  }
+  for (const v of [-2147483648, -1, 0, 1, 2147483647]) {
+    expect(roundtrip((w) => w.i32(v), (r) => r.i32())).toBe(v);
+  }
+});
+
+test('f32/f64 round-trip including special values', () => {
+  for (const v of [0, -0, 1, -1, 3.14159, Infinity, -Infinity]) {
+    expect(roundtrip((w) => w.f64(v), (r) => r.f64())).toBe(v);
+  }
+  expect(Number.isNaN(roundtrip((w) => w.f64(NaN), (r) => r.f64()))).toBe(true);
+  for (const v of [0, 1, -1, 0.5, -0.5, 2.0, 1024, -1024, 0.125]) {
+    expect(roundtrip((w) => w.f32(v), (r) => r.f32())).toBe(v);
+  }
+});
+
+test('varu32 LEB128 round-trip including 5-byte values', () => {
+  const cases = [0, 1, 127, 128, 16383, 16384, 0x1fffff, 0x10000000, 0xffffffff];
+  for (const v of cases) {
+    expect(roundtrip((w) => w.varu32(v), (r) => r.varu32())).toBe(v);
+  }
+});
+
+test('varu32 byte lengths follow LEB128 spec', () => {
+  const sizes: Array<[number, number]> = [
+    [0, 1],
+    [127, 1],
+    [128, 2],
+    [16383, 2],
+    [16384, 3],
+    [0x1fffff, 3],
+    [0x200000, 4],
+    [0xfffffff, 4],
+    [0x10000000, 5],
+  ];
+  for (const [v, expectedSize] of sizes) {
+    const w = new Writer(16);
+    w.varu32(v);
+    expect(w.pos, `varu32(${v}) should be ${expectedSize} bytes`).toBe(expectedSize);
+  }
+});
+
+test('vari32 zigzag round-trip', () => {
+  const cases = [0, -1, 1, -2, 2, -64, 63, -8192, 8191, -2147483648, 2147483647];
+  for (const v of cases) {
+    expect(roundtrip((w) => w.vari32(v), (r) => r.vari32())).toBe(v);
+  }
+});
+
+test('varu53 round-trip up to 2^53', () => {
+  const cases = [
+    0,
+    1,
+    127,
+    128,
+    2 ** 16,
+    2 ** 32 - 1,
+    2 ** 32,
+    2 ** 40,
+    Number.MAX_SAFE_INTEGER,
+  ];
+  for (const v of cases) {
+    expect(roundtrip((w) => w.varu53(v), (r) => r.varu53())).toBe(v);
+  }
+});
+
+test('vari53 round-trip', () => {
+  const cases = [
+    0,
+    -1,
+    1,
+    -(2 ** 30),
+    2 ** 30,
+    Number.MIN_SAFE_INTEGER,
+    Number.MAX_SAFE_INTEGER,
+  ];
+  for (const v of cases) {
+    expect(roundtrip((w) => w.vari53(v), (r) => r.vari53())).toBe(v);
+  }
+});
+
+test('varbu/varbi bigint round-trip', () => {
+  const u: bigint[] = [0n, 1n, 127n, 128n, 1n << 32n, 1n << 63n, (1n << 64n) - 1n];
+  for (const v of u) {
+    expect(roundtrip((w) => w.varbu(v), (r) => r.varbu())).toBe(v);
+  }
+  const s: bigint[] = [0n, -1n, 1n, -(1n << 32n), 1n << 32n, -(1n << 63n), (1n << 63n) - 1n];
+  for (const v of s) {
+    expect(roundtrip((w) => w.varbi(v), (r) => r.varbi())).toBe(v);
+  }
+});
+
+test('str round-trip ASCII short and long', () => {
+  for (const s of ['', 'a', 'hello', 'BTC-USD', 'abcdefghijklmnopqrstuvwxyz']) {
+    expect(roundtrip((w) => w.str(s), (r) => r.str())).toBe(s);
+  }
+  const long = 'x'.repeat(200);
+  expect(roundtrip((w) => w.str(long), (r) => r.str())).toBe(long);
+});
+
+test('str round-trip non-ASCII', () => {
+  for (const s of ['héllo', 'café', '日本語', '🚀', 'mix αβγ 漢字 🎉']) {
+    expect(roundtrip((w) => w.str(s), (r) => r.str())).toBe(s);
+  }
+});
+
+test('bytes round-trip', () => {
+  const data = new Uint8Array([1, 2, 3, 4, 255, 0, 128]);
+  const result = roundtrip(
+    (w) => w.bytesPrefixed(data),
+    (r) => r.bytesPrefixed(),
+  );
+  expect(Array.from(result)).toEqual(Array.from(data));
+});
+
+test('Writer grows beyond initial capacity', () => {
+  const w = new Writer(4);
+  for (let i = 0; i < 1000; i++) w.u8(i & 0xff);
+  expect(w.pos).toBe(1000);
+  expect(w.buf.byteLength).toBeGreaterThanOrEqual(1000);
+});
+
+test('Writer reset reuses buffer', () => {
+  const w = new Writer(16);
+  w.u32(42);
+  const cap1 = w.buf.byteLength;
+  w.reset();
+  expect(w.pos).toBe(0);
+  w.u32(99);
+  expect(w.buf.byteLength).toBe(cap1);
+});
+
+test('multi-write/multi-read interleaved', () => {
+  const w = new Writer(16);
+  w.u8(1);
+  w.f64(3.14);
+  w.str('hi');
+  w.varu53(42);
+
+  const r = new Reader(w.bytes());
+  expect(r.u8()).toBe(1);
+  expect(r.f64()).toBe(3.14);
+  expect(r.str()).toBe('hi');
+  expect(r.varu53()).toBe(42);
+});
+
+test('bool round-trip', () => {
+  for (const v of [true, false]) {
+    expect(roundtrip((w) => w.bool(v), (r) => r.bool())).toBe(v);
+  }
+});
diff --git a/serializer/test/schemas.test.ts b/serializer/test/schemas.test.ts
new file mode 100644
index 0000000..0b885e3
--- /dev/null
+++ b/serializer/test/schemas.test.ts
@@ -0,0 +1,247 @@
+import { test, expect } from 'vitest';
+import {
+  clearRegistry,
+  defineSchema,
+  deserialize,
+  register,
+  s,
+  serialize,
+} from '../plugin/index.ts';
+
+function fresh() {
+  clearRegistry();
+}
+
+test('flat object with mixed primitives', () => {
+  fresh();
+  const Ticker = defineSchema('Ticker', (s) => ({
+    symbol: s.str,
+    last: s.f64,
+    volume: s.f64,
+    count: s.u32,
+  }));
+  const codec = register(Ticker);
+
+  const value = { symbol: 'BTC-USD', last: 45123.45, volume: 1234.5678, count: 99999 };
+  const bytes = serialize(value, codec);
+  const decoded = deserialize(bytes);
+
+  expect(decoded).toEqual(value);
+});
+
+test('array of primitives', () => {
+  fresh();
+  const Tags = defineSchema('Tags', (s) => ({
+    items: s.array(s.str),
+    counts: s.array(s.u32),
+  }));
+  const codec = register(Tags);
+
+  const v = { items: ['a', 'b', 'hello'], counts: [1, 2, 3, 4, 5] };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('nested object via inline ObjectSchema', () => {
+  fresh();
+  const Price = defineSchema('Price', (s) => ({ value: s.f64, scale: s.u8 }));
+  const Order = defineSchema('Order', (s) => ({
+    id: s.u53,
+    price: Price,
+    qty: s.f64,
+  }));
+  register(Price);
+  const codec = register(Order);
+
+  const v = { id: 12345, price: { value: 100.5, scale: 2 }, qty: 1.5 };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('optional fields', () => {
+  fresh();
+  const Maybe = defineSchema('Maybe', (s) => ({
+    a: s.optional(s.str),
+    b: s.optional(s.f64),
+  }));
+  const codec = register(Maybe);
+
+  expect(deserialize(serialize({ a: 'hi', b: 3.14 }, codec))).toEqual({
+    a: 'hi',
+    b: 3.14,
+  });
+  expect(deserialize(serialize({ a: undefined, b: 1 }, codec))).toEqual({
+    a: undefined,
+    b: 1,
+  });
+  expect(deserialize(serialize({ a: undefined, b: undefined }, codec))).toEqual({
+    a: undefined,
+    b: undefined,
+  });
+});
+
+test('enum field', () => {
+  fresh();
+  const Side = defineSchema('SidedOrder', (s) => ({
+    side: s.enum(['buy', 'sell'] as const),
+    qty: s.f64,
+  }));
+  const codec = register(Side);
+
+  for (const side of ['buy', 'sell'] as const) {
+    const v = { side, qty: 1 };
+    expect(deserialize(serialize(v, codec))).toEqual(v);
+  }
+});
+
+test('bitset field (≤8 flags)', () => {
+  fresh();
+  const Flags = defineSchema('Flags', (s) => ({
+    flags: s.bitset(['ioc', 'post_only', 'reduce_only'] as const),
+  }));
+  const codec = register(Flags);
+
+  const v = { flags: { ioc: true, post_only: false, reduce_only: true } };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('bitset field (>32 flags uses bigint)', () => {
+  fresh();
+  const flagNames = Array.from({ length: 40 }, (_, i) => `f${i}`) as readonly string[];
+  const Flags = defineSchema('FlagsBig', (s) => ({
+    flags: s.bitset(flagNames as readonly [string, ...string[]]),
+  }));
+  const codec = register(Flags);
+
+  const flags: Record = {};
+  for (let i = 0; i < 40; i++) flags[`f${i}`] = i % 3 === 0;
+  const v = { flags };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('tuple field', () => {
+  fresh();
+  const Point = defineSchema('Point3D', (s) => ({
+    name: s.str,
+    coord: s.tuple(s.f64, s.f64, s.f64),
+  }));
+  const codec = register(Point);
+
+  const v = { name: 'p', coord: [1.5, 2.5, 3.5] };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('array of nested objects', () => {
+  fresh();
+  const Level = defineSchema('Level', (s) => ({ price: s.f64, qty: s.f64 }));
+  register(Level);
+  const Book = defineSchema('Book', (s) => ({
+    bids: s.array(Level),
+    asks: s.array(Level),
+  }));
+  const codec = register(Book);
+
+  const v = {
+    bids: [{ price: 100, qty: 1 }, { price: 99, qty: 2 }],
+    asks: [{ price: 101, qty: 0.5 }, { price: 102, qty: 1.5 }, { price: 103, qty: 0.1 }],
+  };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('union with discriminator', () => {
+  fresh();
+  const Event = s.union('Event', 'kind', {
+    fill: { price: s.f64, qty: s.f64 },
+    cancel: { reason: s.str },
+    expire: { at: s.u53 },
+  });
+  const codec = register(Event);
+
+  const samples = [
+    { kind: 'fill' as const, price: 100, qty: 0.5 },
+    { kind: 'cancel' as const, reason: 'user' },
+    { kind: 'expire' as const, at: 1700000000 },
+  ];
+  for (const v of samples) {
+    expect(deserialize(serialize(v, codec))).toEqual(v);
+  }
+});
+
+test('typed array (f64Array) round-trip', () => {
+  fresh();
+  const Signal = defineSchema('Signal', (s) => ({
+    name: s.str,
+    samples: s.f64Array,
+  }));
+  const codec = register(Signal);
+
+  const samples = new Float64Array([1.1, 2.2, 3.3, 4.4, 5.5]);
+  const v = { name: 'sig', samples };
+  const decoded = deserialize(serialize(v, codec));
+  expect(decoded.name).toBe('sig');
+  expect(decoded.samples).toBeInstanceOf(Float64Array);
+  expect(decoded.samples.length).toBe(5);
+  for (let i = 0; i < 5; i++) expect(decoded.samples[i]).toBe(samples[i]);
+});
+
+test('bigint u64/i64 round-trip', () => {
+  fresh();
+  const Big = defineSchema('Big', (s) => ({
+    u: s.u64,
+    i: s.i64,
+  }));
+  const codec = register(Big);
+  const v = { u: 1n << 50n, i: -(1n << 50n) };
+  expect(deserialize(serialize(v, codec))).toEqual(v);
+});
+
+test('bytes field', () => {
+  fresh();
+  const Blob = defineSchema('Blob', (s) => ({
+    data: s.bytes,
+  }));
+  const codec = register(Blob);
+  const data = new Uint8Array([0, 1, 2, 3, 254, 255]);
+  const decoded = deserialize<{ data: Uint8Array }>(serialize({ data }, codec));
+  expect(Array.from(decoded.data)).toEqual(Array.from(data));
+});
+
+test('serialize includes 2-byte schema ID frame', () => {
+  fresh();
+  const Sch = defineSchema('Sch', (s) => ({ x: s.u8 }));
+  const codec = register(Sch);
+  const bytes = serialize({ x: 7 }, codec);
+  expect(bytes.length).toBeGreaterThanOrEqual(3);
+  const id = bytes[0]! | (bytes[1]! << 8);
+  expect(id).toBe(codec.id);
+});
+
+test('large nested order-book payload', () => {
+  fresh();
+  const Level = defineSchema('LvlBig', (s) => ({ p: s.f64, q: s.f64 }));
+  register(Level);
+  const Snap = defineSchema('Snap', (s) => ({
+    symbol: s.str,
+    ts: s.u53,
+    bids: s.array(Level),
+    asks: s.array(Level),
+  }));
+  const codec = register(Snap);
+
+  const bids = Array.from({ length: 1000 }, (_, i) => ({ p: 100 - i * 0.01, q: 1 + i * 0.001 }));
+  const asks = Array.from({ length: 1000 }, (_, i) => ({ p: 100 + i * 0.01, q: 1 + i * 0.001 }));
+  const v = { symbol: 'BTC-USD', ts: 1700000000123, bids, asks };
+
+  const bytes = serialize(v, codec);
+  const decoded = deserialize(bytes);
+  expect(decoded.symbol).toBe(v.symbol);
+  expect(decoded.ts).toBe(v.ts);
+  expect(decoded.bids.length).toBe(1000);
+  expect(decoded.asks.length).toBe(1000);
+  expect(decoded.bids[0]).toEqual(v.bids[0]);
+  expect(decoded.asks[999]).toEqual(v.asks[999]);
+});
+
+test('deserialize unknown schema ID throws', () => {
+  fresh();
+  const bytes = new Uint8Array([0xff, 0xff, 0]);
+  expect(() => deserialize(bytes)).toThrow(/Unknown schema ID/);
+});
diff --git a/serializer/test/shapes.test.ts b/serializer/test/shapes.test.ts
new file mode 100644
index 0000000..706e0a3
--- /dev/null
+++ b/serializer/test/shapes.test.ts
@@ -0,0 +1,105 @@
+import { test, expect } from 'vitest';
+import {
+  clearRegistry,
+  defineSchema,
+  deserialize,
+  register,
+  s,
+  serialize,
+} from '../plugin/index.ts';
+
+/**
+ * Decoded objects must share key order with the schema field order. Same
+ * key order across instances is V8's strongest signal of a shared hidden
+ * class, which is what the codec's single-object-literal pattern ensures.
+ */
+test('decoded objects share key order matching schema field order', () => {
+  clearRegistry();
+  const Order = defineSchema('ShapeOrder', (s) => ({
+    id: s.u53,
+    price: s.f64,
+    qty: s.f64,
+    side: s.enum(['buy', 'sell'] as const),
+    tags: s.array(s.str),
+  }));
+  const codec = register(Order);
+
+  const expectedOrder = ['id', 'price', 'qty', 'side', 'tags'];
+
+  const decoded1 = deserialize>(
+    serialize({ id: 1, price: 100, qty: 0.5, side: 'buy', tags: ['a'] }, codec),
+  );
+  const decoded2 = deserialize>(
+    serialize({ id: 999, price: 1e10, qty: 0, side: 'sell', tags: [] }, codec),
+  );
+  const decoded3 = deserialize>(
+    serialize({ id: 2 ** 40, price: -1, qty: 1234, side: 'buy', tags: ['x', 'y', 'z'] }, codec),
+  );
+
+  expect(Object.keys(decoded1)).toEqual(expectedOrder);
+  expect(Object.keys(decoded2)).toEqual(expectedOrder);
+  expect(Object.keys(decoded3)).toEqual(expectedOrder);
+});
+
+test('decoded value types are consistent across instances', () => {
+  clearRegistry();
+  const T = defineSchema('Types', (s) => ({
+    a: s.u32,
+    b: s.f64,
+    c: s.str,
+    d: s.bool,
+  }));
+  const codec = register(T);
+
+  const types = (o: Record) =>
+    Object.entries(o).map(([k, v]) => [k, typeof v]);
+
+  const a = deserialize>(
+    serialize({ a: 1, b: 1.5, c: 'a', d: true }, codec),
+  );
+  const b = deserialize>(
+    serialize({ a: 0, b: 0, c: '', d: false }, codec),
+  );
+  expect(types(a)).toEqual(types(b));
+  expect(types(a)).toEqual([
+    ['a', 'number'],
+    ['b', 'number'],
+    ['c', 'string'],
+    ['d', 'boolean'],
+  ]);
+});
+
+test('nested object key order is stable', () => {
+  clearRegistry();
+  const Price = defineSchema('SPrice', (s) => ({ value: s.f64, scale: s.u8 }));
+  register(Price);
+  const Order = defineSchema('SOrder', (s) => ({
+    id: s.u53,
+    price: Price,
+    qty: s.f64,
+  }));
+  const codec = register(Order);
+
+  const v = { id: 1, price: { value: 100, scale: 2 }, qty: 1 };
+  const d1 = deserialize>(serialize(v, codec));
+  const d2 = deserialize>(serialize({ ...v, id: 99 }, codec));
+
+  expect(Object.keys(d1)).toEqual(['id', 'price', 'qty']);
+  expect(Object.keys(d2)).toEqual(['id', 'price', 'qty']);
+  expect(Object.keys(d1.price as Record)).toEqual(['value', 'scale']);
+  expect(Object.keys(d2.price as Record)).toEqual(['value', 'scale']);
+});
+
+test('union decoded objects place discriminator first', () => {
+  clearRegistry();
+  const Event = s.union('SEvent', 'kind', {
+    a: { x: s.u32 },
+    b: { y: s.f64 },
+  });
+  const codec = register(Event);
+
+  const ea = deserialize>(serialize({ kind: 'a', x: 1 }, codec));
+  const eb = deserialize>(serialize({ kind: 'b', y: 2.5 }, codec));
+  expect(Object.keys(ea)[0]).toBe('kind');
+  expect(Object.keys(eb)[0]).toBe('kind');
+});
diff --git a/serializer/test/transformer.test.ts b/serializer/test/transformer.test.ts
new file mode 100644
index 0000000..77020e1
--- /dev/null
+++ b/serializer/test/transformer.test.ts
@@ -0,0 +1,169 @@
+import { test, expect, afterAll } from 'vitest';
+import { writeFileSync, rmSync, existsSync } from 'node:fs';
+import { join, dirname } from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import { transform } from '../plugin/compile/transformer.ts';
+
+const HERE = dirname(fileURLToPath(import.meta.url));
+// Write gen files directly in the test dir so relative imports `../src/...`
+// resolve to serializer/src/index.ts.
+const GEN_DIR = HERE;
+
+let counter = 0;
+async function transformAndImport(source: string): Promise> {
+  const id = ++counter;
+  const file = join(GEN_DIR, `__gen_${id}.ts`);
+  const result = transform(source, file, {
+    importPath: '../plugin/index.ts',
+    packageAliases: ['../plugin/index.ts'],
+  });
+  writeFileSync(file, result.code, 'utf8');
+  // Use file URL + @vite-ignore so vite passes through to native dynamic import.
+  const url = `${pathToFileURL(file).href}?t=${Date.now()}`;
+  const mod = await import(/* @vite-ignore */ url);
+  return mod as Record;
+}
+
+test('transformer: flat type round-trip', async () => {
+  const src = `
+import { type, u53, f64, str } from '../plugin/index.ts';
+
+export const Ticker = type('TxTicker', {
+  symbol: str,
+  last: f64,
+  volume: f64,
+});
+`;
+  const mod = await transformAndImport(src);
+  const Ticker = mod.Ticker as { encode: (v: unknown) => Uint8Array; decode: (b: Uint8Array) => unknown };
+  const v = { symbol: 'BTC-USD', last: 100.5, volume: 1234.5 };
+  expect(Ticker.decode(Ticker.encode(v))).toEqual(v);
+});
+
+test('transformer: nested object via local reference', async () => {
+  const src = `
+import { type, u53, f64 } from '../plugin/index.ts';
+
+export const Price = type('TxPrice', { value: f64, scale: u53 });
+export const Order = type('TxOrder', { id: u53, price: Price, qty: f64 });
+`;
+  const mod = await transformAndImport(src);
+  const Order = mod.Order as { encode: (v: unknown) => Uint8Array; decode: (b: Uint8Array) => unknown };
+  const v = { id: 42, price: { value: 100.5, scale: 2 }, qty: 0.5 };
+  expect(Order.decode(Order.encode(v))).toEqual(v);
+});
+
+test('transformer: combinators (list, opt, enumOf, flags, tuple)', async () => {
+  const src = `
+import { type, u53, f64, str, list, opt, enumOf, flags, tuple } from '../plugin/index.ts';
+
+export const Combo = type('TxCombo', {
+  tags: list(str),
+  maybe: opt(f64),
+  side: enumOf(['buy', 'sell'] as const),
+  f: flags(['ioc', 'post_only'] as const),
+  point: tuple(f64, f64),
+});
+`;
+  const mod = await transformAndImport(src);
+  const Combo = mod.Combo as { encode: (v: unknown) => Uint8Array; decode: (b: Uint8Array) => unknown };
+  const v = {
+    tags: ['a', 'b'],
+    maybe: 3.14,
+    side: 'buy',
+    f: { ioc: true, post_only: false },
+    point: [1, 2],
+  };
+  expect(Combo.decode(Combo.encode(v))).toEqual(v);
+});
+
+test('transformer: anonymous (no name) — uses const name as schema name', async () => {
+  const src = `
+import { type, u53, f64 } from '../plugin/index.ts';
+
+export const TxAnon = type({ x: u53, y: f64 });
+`;
+  const mod = await transformAndImport(src);
+  const T = mod.TxAnon as {
+    encode: (v: unknown) => Uint8Array;
+    decode: (b: Uint8Array) => unknown;
+    id: number;
+    name: string;
+  };
+  expect(T.name).toBe('TxAnon');
+  const v = { x: 1, y: 2.5 };
+  expect(T.decode(T.encode(v))).toEqual(v);
+});
+
+test('transformer: array of nested objects (the OrderBook hot path)', async () => {
+  const src = `
+import { type, u53, f64, str, list } from '../plugin/index.ts';
+
+export const Level = type('TxLevel', { p: f64, q: f64 });
+export const Book = type('TxBook', {
+  symbol: str,
+  ts: u53,
+  bids: list(Level),
+  asks: list(Level),
+});
+`;
+  const mod = await transformAndImport(src);
+  const Book = mod.Book as { encode: (v: unknown) => Uint8Array; decode: (b: Uint8Array) => unknown };
+  const v = {
+    symbol: 'BTC-USD',
+    ts: 1700000000000,
+    bids: Array.from({ length: 100 }, (_, i) => ({ p: 100 - i * 0.1, q: 0.5 + i * 0.01 })),
+    asks: Array.from({ length: 100 }, (_, i) => ({ p: 100 + i * 0.1, q: 0.5 + i * 0.01 })),
+  };
+  const decoded = Book.decode(Book.encode(v)) as typeof v;
+  expect(decoded.symbol).toBe(v.symbol);
+  expect(decoded.ts).toBe(v.ts);
+  expect(decoded.bids.length).toBe(100);
+  expect(decoded.bids[0]).toEqual(v.bids[0]);
+  expect(decoded.asks[99]).toEqual(v.asks[99]);
+});
+
+test('transformer: file without type() imports — unchanged', () => {
+  const src = `
+import { foo } from 'somewhere';
+const x = foo();
+`;
+  const result = transform(src, 'test.ts', { importPath: '../plugin/index.ts' });
+  expect(result.transformedCount).toBe(0);
+  expect(result.code).toBe(src);
+});
+
+test('transformer: file with type import but no calls — adds nothing', () => {
+  const src = `
+import { type, u53 } from '../plugin/index.ts';
+// no type() call here
+const x = 1;
+`;
+  const result = transform(src, 'test.ts', {
+    importPath: '../plugin/index.ts',
+    packageAliases: ['../plugin/index.ts'],
+  });
+  expect(result.transformedCount).toBe(0);
+});
+
+test('transformer: replaces call with IIFE (smoke check on output)', () => {
+  const src = `
+import { type, u53, f64 } from '../plugin/index.ts';
+export const T = type('TxSmoke', { x: u53, y: f64 });
+`;
+  const result = transform(src, 'test.ts', {
+    importPath: '../plugin/index.ts',
+    packageAliases: ['../plugin/index.ts'],
+  });
+  expect(result.transformedCount).toBe(1);
+  expect(result.code).toContain('function encode_TxSmoke');
+  expect(result.code).toContain('function decode_TxSmoke');
+  expect(result.code).not.toContain("type('TxSmoke'");
+});
+
+afterAll(() => {
+  for (let i = 1; i <= counter; i++) {
+    const file = join(GEN_DIR, `__gen_${i}.ts`);
+    if (existsSync(file)) rmSync(file, { force: true });
+  }
+});
diff --git a/serializer/tsconfig.json b/serializer/tsconfig.json
new file mode 100644
index 0000000..0bfb2e5
--- /dev/null
+++ b/serializer/tsconfig.json
@@ -0,0 +1,20 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "moduleResolution": "bundler",
+    "lib": ["ESNext"],
+    "strict": true,
+    "noImplicitAny": true,
+    "noImplicitOverride": true,
+    "noUncheckedIndexedAccess": true,
+    "exactOptionalPropertyTypes": false,
+    "isolatedModules": true,
+    "allowImportingTsExtensions": true,
+    "noEmit": true,
+    "skipLibCheck": true,
+    "resolveJsonModule": true,
+    "verbatimModuleSyntax": false
+  },
+  "include": ["plugin/**/*.ts", "src/**/*.ts", "test/**/*.ts", "vite.config.ts"]
+}
diff --git a/serializer/vite.config.ts b/serializer/vite.config.ts
new file mode 100644
index 0000000..459e29a
--- /dev/null
+++ b/serializer/vite.config.ts
@@ -0,0 +1,35 @@
+import { defineConfig } from 'vite';
+import { fileURLToPath } from 'node:url';
+import { DevTools } from '@vitejs/devtools';
+import { serializerCodegen } from './plugin/compile/vite.ts';
+
+/**
+ * Vite config for the example app at `src/main.ts`.
+ *
+ * Two things wired up:
+ *
+ * 1. **Path alias** — `@perf/serializer` resolves to `./plugin/index.ts`.
+ *    The example code (and any consumer) writes `from '@perf/serializer'`
+ *    without ever touching relative paths.
+ *
+ * 2. **Compile-only AOT plugin** — every `type(...)` and `oneOf(...)` call
+ *    found in the source is replaced at build time with an inline codec
+ *    literal. The runtime never calls `new Function`. CSP-safe, tree-shakeable,
+ *    no first-call warmup.
+ */
+export default defineConfig({
+  devtools: {
+    enabled: true,
+  },
+  build: {
+    rolldownOptions: {
+      devtools: {}, // enable devtools mode
+    },
+  },
+  resolve: {
+    alias: {
+      '@perf/serializer': fileURLToPath(new URL('./plugin/index.ts', import.meta.url)),
+    },
+  },
+  plugins: [serializerCodegen(), DevTools()],
+});
diff --git a/serializer/vitest.config.ts b/serializer/vitest.config.ts
new file mode 100644
index 0000000..adf1f5f
--- /dev/null
+++ b/serializer/vitest.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  // Standalone config — don't search upward for vite.config in parent dirs.
+  configFile: false,
+  test: {
+    include: ['test/**/*.test.ts'],
+    environment: 'node',
+    globals: false,
+    benchmark: {
+      include: ['test/**/*.bench.ts'],
+    },
+  },
+});