alepha@docs:~/docs/framework/guides/core$
cat 6-types.md | pretty
4 min read
Last commit:

#Types

Alepha provides schema validation through the z singleton from "alepha". It wraps Zod 4 with opinionated defaults: z.text() strings carry length limits and trimming, format types are tagged for the ORM and OpenAPI, and objects strip unknown keys.

Import z from alepha, not from zod - a schema built with the raw library carries none of those defaults.

#Basic Usage

typescript
1import { z } from "alepha";2 3const userSchema = z.object({4  id: z.uuid(),5  email: z.email(),6  name: z.text(),7  age: z.integer().optional(),8});

z returns plain Zod schemas, so anything that accepts a Zod type accepts them - .parse(), .safeParse(), .optional(), and the rest of the Zod surface all work as usual.

#Strings

#z.string() vs z.text()

z.string() creates a raw string with no length limit. Use it for internal values where length is irrelevant.

z.text() adds length limits and text processing. Use it for user input, database fields, and API schemas.

typescript
1z.string(); // no limits2z.text(); // max 255 chars, trimmed3z.text({ size: "short" }); // max 64 chars, trimmed4z.text({ size: "long" }); // max 1024 chars, trimmed5z.text({ size: "rich" }); // max 65535 chars, trimmed

z.text() trims whitespace by default. You can control this and enable lowercase conversion:

typescript
1z.text({ trim: false }); // no trimming2z.text({ trim: true, lowercase: true }); // trim + lowercase

#Text Presets

Shorthand methods for common text sizes:

typescript
1z.shortText(); // same as z.text({ size: "short" })  - 64 chars2z.longText(); // same as z.text({ size: "long" })   - 1024 chars3z.richText(); // same as z.text({ size: "rich" })   - 65535 chars

#Length Limits

The size presets cap at 64 (short), 255 (regular), 1024 (long), and 65535 (rich) characters. An explicit maxLength overrides the preset cap:

typescript
1z.text({ maxLength: 1_000_000 }); // overrides the 255 default

#Numbers

typescript
1z.number(); // any number2z.integer(); // integer (no fractional part)3z.int32(); // integer clamped to signed 32-bit range (-2147483648 to 2147483647)4z.int64(); // JS-safe integer (-9007199254740991 to 9007199254740991)

z.int64() is NOT a true 64-bit integer. JavaScript cannot represent all int64 values. For true int64, use z.bigint() which stores values as strings.

Chain zod's native number checks for bounds:

typescript
1z.integer().min(0).max(100);2z.number().gt(0);

#Objects

Objects silently strip unknown keys (standard Zod behavior):

typescript
1const schema = z.object({2  name: z.text(),3  email: z.email(),4});5// { name: "alice", email: "a@b.c", extra: true } → { name: "alice", email: "a@b.c" }

Use zod's .strict() if you want extra keys to be rejected instead.

#Arrays

Arrays are unbounded by default. Add explicit bounds where the input is untrusted:

typescript
1z.array(z.string()); // no cap2z.array(z.string()).max(50); // max 50 items3z.array(z.string()).min(1); // at least 1 item

#Modifiers

#Optional and Nullable

typescript
1z.string().optional(); // string | undefined2z.string().nullable(); // string | null

These can be combined:

typescript
1z.string().nullable().optional(); // string | null | undefined

#Partial, Pick, Omit

typescript
1const user = z.object({2  id: z.uuid(),3  name: z.text(),4  email: z.email(),5});6 7user.partial(); // all fields optional8user.pick({ id: true, name: true }); // only id and name9user.omit({ id: true }); // name and email only

#Extend

Add properties to an existing schema:

typescript
1const baseUser = z.object({2  id: z.uuid(),3  name: z.text(),4});5 6const admin = baseUser.extend({7  role: z.const("admin"),8  permissions: z.array(z.text()),9});

To merge multiple base schemas, spread their .shape into a new object:

typescript
1z.object({ ...baseUser.shape, ...timestamped.shape, extra: z.text() });

#Format Types

#Bigint

String-encoded arbitrary-precision integer:

typescript
1z.bigint(); // validates "123456789", "-42", etc.

Values are represented as strings to avoid JavaScript number limitations.

#UUID

typescript
1z.uuid(); // validates UUID format (e.g. "550e8400-e29b-41d4-a716-446655440000")

#URL

typescript
1z.url(); // validates URL format

#File and Stream

typescript
1z.file(); // file-like object (browser File API compatible)2z.file({ maxBytes: 1_048_576 }); // caps what the multipart parser accepts for this route3z.stream(); // experimental streaming type4z.stream({ maxBytes: 1_048_576 }); // same cap, applied to the streamed part

maxBytes is runtime-enforced - the multipart parser reads it and refuses larger uploads with a 413. Careful with the neighbouring $storage({ maxSize }), which is declared in megabytes; mixing the two units up is silent in both directions.

#Domain Types

#Email

typescript
1z.email(); // validates email format (no trimming or lowercasing - whitespace is rejected)

#Phone (E.164)

typescript
1z.e164(); // validates E.164 format, e.g. "+1234567890"

#Language Tag (BCP 47)

typescript
1z.bcp47(); // validates BCP 47 tags, e.g. "en", "en-US", "fr-CA"

#Date and Time

typescript
1z.datetime(); // ISO 8601 date-time, e.g. "2026-01-15T10:30:00Z"2z.date(); // ISO 8601 date, e.g. "2026-01-15"3z.time(); // ISO 8601 time, e.g. "10:30:00"4z.duration(); // string tagged with the ISO 8601 duration format (not runtime-validated)

#Enums

String enums with built-in validation:

typescript
1z.enum(["ACTIVE", "INACTIVE", "BANNED"]);2// validates that the value is one of the listed strings

#Other Types

typescript
 1z.const("value")    // literal value 2z.boolean()         // boolean 3z.null()            // null 4z.any()             // any (no validation) 5z.void()            // void 6z.undefined()       // undefined 7z.union([...])      // union of schemas 8z.tuple([...])      // fixed-length array 9z.record(k, v)      // Record<K, V>10z.json()            // Record<string, any> - convenience for JSON blobs

#Validation

Alepha validates data through alepha.codec.validate(). This is the same validation used internally by $action, $env, and other primitives.

typescript
 1import { Alepha, z } from "alepha"; 2  3const alepha = Alepha.create(); 4const schema = z.object({ 5  name: z.text(), 6  email: z.email(), 7}); 8  9const result = alepha.codec.validate(schema, {10  name: "  Alice  ",11  email: "alice@example.com",12});13// result: { name: "Alice", email: "alice@example.com" }

Validation is a thin wrapper over schema.safeParse - everything beyond type checking lives in the schema itself:

  • Trimming: strings created with z.text() are trimmed by default (trim: false opts out).
  • Lowercase: strings created with z.text({ lowercase: true }) are lowercased.
  • Unknown keys: objects strip keys not declared in the schema.
  • Defaults: .default(...) values are applied.

There is no extra coercion layer: null in a non-nullable field is a validation error, and a non-array value passed to an array schema is rejected, not wrapped.

If validation fails, a SchemaValidationError is thrown with details about the first failing constraint.

#Encoding

alepha.codec.encode() validates data and serializes it to a target format:

typescript
 1const schema = z.object({ 2  id: z.uuid(), 3  name: z.text(), 4}); 5  6// Validate and return the cleaned object (default) 7const obj = alepha.codec.encode(schema, data); 8  9// Validate and serialize to JSON string10const json = alepha.codec.encode(schema, data, { as: "string" });11 12// Validate and serialize to binary (for protobuf, msgpack, etc.)13const bytes = alepha.codec.encode(schema, data, { as: "binary" });

You can skip validation with validation: false:

typescript
1alepha.codec.encode(schema, data, { validation: false, as: "string" });

#Codec Formats

The default codec is "json". Additional codecs like Protobuf can be registered:

typescript
1alepha.codec.register({2  name: "protobuf",3  codec: myProtobufCodec,4});5 6alepha.codec.encode(schema, data, { encoder: "protobuf", as: "binary" });

#Decoding

alepha.codec.decode() deserializes data and validates it against a schema:

typescript
1const result = alepha.codec.decode(schema, jsonString);2// result is validated and typed as Infer<typeof schema>

Specify a codec if the data isn't standard JSON:

typescript
1const result = alepha.codec.decode(schema, binaryData, { encoder: "protobuf" });

Validation runs automatically after decoding. Disable it with validation: false.

#Accessing Zod Directly

Schemas built by z are ordinary Zod schemas, so for anything z does not wrap, the fluent API works on them directly:

typescript
1schemaA.and(schemaB);

Do not import the zod package directly for this - a second zod copy makes schemas structurally incompatible with every Alepha primitive. Stay on the instances z hands you.