HighX
Server

Validation

Argument and return schemas, checked at runtime, in production.

Every function declares an args schema and a returns schema. Both are checked at runtime — not only in development, and not only in tests.

export const saveNote = mutation({
  name: "notes.save",
  args: noteArgs,      // checked before the handler runs
  returns: noteId,     // checked before the value crosses back
  auth: can("notes:write"),
  handler: (ctx, args) => ctx.db.table("notes").insert(args),
});

Validating arguments is obvious — they come from a browser. Validating the return is the less obvious half: it is the boundary where a storage row becomes a public API value. Without it, adding a column to a table silently starts publishing it.

Builders

A Schema<T> is anything with a parse that throws on bad input, so you can write your own or wrap a library.

schema()

The primitive:

import { schema } from "@highx/core";

const noteId = schema((input: unknown): string => {
  if (typeof input !== "string" || input.length === 0) throw new Error("Expected a note ID");
  return input;
});

stringSchema()

import { stringSchema } from "@highx/core";

const title = stringSchema({ minLength: 1, maxLength: 120 });
const body = stringSchema({ maxLength: 10_000 });

Put a maxLength on anything a client sends. An unbounded string is an unbounded row, and rows live in a Durable Object with a storage limit.

objectSchema()

For argument objects. Each field is narrowed explicitly:

import { objectSchema, stringSchema } from "@highx/core";

interface NoteInput extends Record<string, unknown> {
  readonly title: string;
  readonly color: "amber" | "blue" | "rose";
}

const noteArgs = objectSchema<NoteInput>((input) => {
  const color = input["color"];
  if (color !== "amber" && color !== "blue" && color !== "rose") {
    throw new Error("Invalid note color");
  }
  return {
    title: stringSchema({ minLength: 1, maxLength: 120 }).parse(input["title"]),
    color,
  };
});

Fields the callback does not return are dropped. A client sending { title, color, isAdmin: true } gets { title, color } in the handler.

Using Zod

import { z } from "zod";

const noteArgs = z.object({ title: z.string().min(1).max(120) });

Every args, returns, input, output, row, and workflow result contract accepts a native Zod schema directly. HighX calls parse/safeParse at every boundary, including storage replay. The legacy schema() helper is only a Zod-backed migration bridge for older internal validators.

Validation is not authorization

Validation answers "is this well-formed?", never "is this allowed?". A well-formed tenant ID from an attacker is still an attacker's tenant ID — authorization belongs in auth and ctx.identity.

Failures

A schema failure becomes an invalid_argument error with a safe message. What your throw said is logged server-side; the client is told what was wrong with the request, never what the server looks like inside.

On this page