Skip to content

SX-3/gate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Gate

npm version npm downloads bundle size Standard Schema v1

Gate β€” a maximally performant TypeScript schema validation and parsing library. Compiles schemas into pure JavaScript on the fly β€” zero runtime interpretation.


Installation

npm install @sx3/gate
bun add @sx3/gate
pnpm add @sx3/gate

Quick Start

import { array, check, email, max, min, number, object, parse, pipe, string, validate } from '@sx3/gate';

const UserSchema = object({
  name: pipe(string, min(2), max(50)),
  email: pipe(string, email),
  age: pipe(number, min(0), max(120)),
  tags: array(string),
});

const data = parse(UserSchema)({
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
  tags: ['admin'],
});

const result = validate(UserSchema)(input); // Return StandardSchema Result

if (check(UserSchema)(input)) { /* ok */ }

Validation Modes

Three validation modes. They differ in speed and error detail.

parse β€” throw

import { parse } from '@sx3/gate';

try { parse(User)(input); }
catch (error) { /* GateError */ }

On error β€” throw. Ideal for APIs: invalid input β†’ immediate 400.

validate β€” result

import { validate } from '@sx3/gate';

const result = validate(User)(input);
if (result.issues) { /* all errors */ }
else { result.value; }

Returns all errors at once, never throws. Perfect for forms.

check β€” boolean

import { check } from '@sx3/gate';

if (check(User)(input)) { /* valid */ }

No objects, no throw β€” just true/false. The fastest mode. Ideal for guards and filters.

Mode Speed Error info
parse β˜…β˜…β˜† message + path (first error)
validate β˜…β˜†β˜† message + path (all errors)
check β˜…β˜…β˜… none (boolean only)

On the first call to parse(schema) / validate(schema) / check(schema), the schema is compiled via new Function and cached. Subsequent calls are just a direct function invocation β€” zero overhead on schema interpretation.

Error Messages

Every type and constraint accepts a custom message as its last argument. The message can be a string or a function (ctx) => string.

Messages are evaluated at compile time and baked directly into the generated function as a string literal. No runtime string interpolation, no extra allocations on every validation call. If you pass 'Must be a string', the compiled code contains throw new E("Must be a string", ...). If you use a function like ({ n }) => \Min ${n}`, the function runs once during compilation and the resulting string "Min 3"` is inlined. The function itself is never called at validation time.

On types

string('Must be a string');
number('Expected a number');
boolean('Must be a boolean');
literal(42, 'Must be exactly 42');
array(string, 'Must be an array of strings');
object({ name: string }, 'Must be an object');

On constraints

pipe(string, min(3, 'At least 3 characters'));
pipe(string, max(100, 'Too long'));
pipe(string, length(10, 'Exactly 10 characters'));
pipe(string, email('Invalid email'));
pipe(number, clamp(0, 100, 'Out of range'));

Function-style message

pipe(string, min(3, ({ n }) => `At least ${n} characters`));
pipe(number, clamp(0, 100, ({ min, max }) => `Must be ${min}..${max}`));

refine β€” custom check

import { refine } from '@sx3/gate';

// predicate function
pipe(number, refine(n => n % 2 === 0, 'Must be even'));

// inline condition ($ β†’ variable name) / ! DANGER: don't try make runtime string literal (RCE risk)
pipe(number, refine('$ % 2 === 0', 'Must be even'));

Types

import {
  array,
  bigint,
  boolean,
  instance,
  int,
  int8,
  int16,
  int32,
  int64,
  literal,
  never,
  number,
  object,
  record,
  string,
  tuple,
  uint8,
  uint16,
  uint32,
  uint64,
  union,
  unknown,
} from '@sx3/gate';

Primitives

string; // typeof === "string"
number; // typeof === "number"
boolean; // typeof === "boolean"
bigint; // typeof === "bigint"
unknown; // passes anything
never; // rejects everything
literal(42); // === 42

Objects and collections

object({ name: string, age: number }); // { name: string; age: number }
array(string); // string[]
tuple([string, number]); // [string, number]
record(string, number); // Record<string, number>
union([string, number]); // string | number
instance(Date); // instanceof Date

Modifiers

import { nullable, nullish, optional } from '@sx3/gate';

nullable(string); // string | null
optional(string); // string | undefined
nullish(string); // string | null | undefined

Constraints

Applied via pipe. For strings/arrays they check .length, for numbers they check the value.

min(string, 3); // .length >= 3
max(string, 100); // .length <= 100
clamp(string, 0, 100); // .length >= 0 && .length <= 100

min(number, 0); // >= 0
max(number, 100); // <= 100
clamp(number, 0, 100); // >= 0 && <= 100

length(string, 10); // .length === 10

email; // email (regex)
uuid; // UUID v4
url; // http(s)://...
cuid; // CUID
datetime; // ISO 8601 UTC
trim(string); // whitespace trim

pattern β€” arbitrary regex

pattern is a standalone string schema with a regex check, not a constraint:

import { pattern } from '@sx3/gate';

const HexColor = pattern(/^#[0-9a-f]{6}$/i);
const Digits = pattern(/^\d+$/, 'Digits only');

Composition

pipe β€” chaining

Left-to-right: each function takes a schema and returns a schema.

import { max, min, number, pipe, string, to } from '@sx3/gate';

const Username = pipe(string, min(3), max(20));
const NumericId = pipe(string, to(number)); // string β†’ number

merge β€” merging objects

import { merge, object } from '@sx3/gate';

const A = object({ id: number });
const B = object({ name: string });
const C = object({ age: number });
const ABC = merge(A, B, C); // { id: number; name: string; age: number }

strict β€” disallow extra keys

import { object, strict } from '@sx3/gate';

const StrictUser = strict(object({ id: number, name: string }), true); // strict(schema, deep)

parse(StrictUser)({ id: 1, name: 'a', extra: true });
// β†’ GateError: Unexpected key "extra"

Transformations

to β€” type coercion

Compiles to inline coercion operations β€” no function calls at runtime.

import { boolean, number, object, pipe, string, to } from '@sx3/gate';

// string ↔ number
to(string, number); // "42" β†’ 42
to(number, string); // 42 β†’ "42"

// string ↔ boolean
to(string, boolean); // "true" β†’ true, "false" β†’ false
to(boolean, string); // true β†’ "true"

// string ↔ bigint
to(string, bigint); // "9007199254740991" β†’ 9007199254740991n
to(bigint, string); // 9007199254740991n β†’ "9007199254740991"

// string ↔ object (JSON.parse / JSON.stringify)
to(string, object({})); // '{"a":1}' β†’ { a: 1 }   β€” uses JSON.parse
to(object({}), string); //  { a: 1 } β†’ '{"a":1}'   β€” uses JSON.stringify
to(string, array(number)); // '[1,2]' β†’ [1, 2]      β€” JSON.parse
to(array(number), string); //  [1,2] β†’ '[1,2]'       β€” JSON.stringify

Coercion only works between compatible type pairs. Unsupported combinations throw at schema compilation time.

transform β€” custom transformation

import { string, transform } from '@sx3/gate';

transform(string, s => s.toUpperCase())('hello'); // "HELLO"
transform(string, JSON.parse); // equivalent to to(object({}))

Type Inference

import type { Output } from '@sx3/gate';
import { number, object, string } from '@sx3/gate';

const UserSchema = object({ id: number, name: string });
type User = Output<typeof UserSchema>;
// { id: number; name: string }

Standard Schema v1

Works with tRPC, Hono, TanStack Form out of the box:

const schema = object({ name: string });
const result = await schema['~standard'].validate(input);

By default ~standard.validate uses parse mode. Switch it:

import { settings } from '@sx3/gate';

settings({ standardMode: 'validate' }); // use validate
settings({ standardMode: 'check' }); // use check

Settings

import { settings } from '@sx3/gate';

settings({
  strict: true, // all object() are strict by default
  checkNaN: false, // don't check NaN for number()
  standardMode: 'parse', // 'parse' | 'validate' | 'check'
});

Highlights

  • ⚑ JIT compilation β€” new Function + schema-level cache, second call is pure JS
  • 🎯 Full type inference β€” Output<typeof schema>, no manual generics
  • πŸ“¦ Tree-shakeable ESM β€” take only what you use
  • 🚫 Zero dependencies β€” no runtime dependencies
  • πŸ”— Standard Schema v1 β€” tRPC, Hono, TanStack Form

License

MIT Β© SX3