API reference / @evolu/common / Type

Type

🧩 Type-safe runtime types

Evolu Type is like a type guard that returns typed errors (via Result) instead of throwing. We either get a safely typed value or a precise, composable error value telling us exactly why validation failed.

Evolu Type supports Standard Schema for interoperability with 40+ validation-compatible tools and frameworks.

Why another validation library?

  • Result-based error handling – no exceptions for normal control flow.
  • Typed errors with decoupled formatters – validation logic β‰  user messages.
  • Consistent constraints via Brand – every constraint becomes part of the type.
  • No user-land chaining DSL – designed with the upcoming ES pipe operator in mind.
  • Selective validation – parent validations are skipped when already proved by typing.
  • Simple, top-down implementation – readable source code from top to bottom with no hidden magic; just plain functions and composition.

Base Types Quick Start

// Validate unknown values
const value: unknown = "hello";
const stringResult = String.fromUnknown(value);
if (!stringResult.ok) {
  // console.error(formatStringError(stringResult.error));
  return stringResult; // inside a function returning Result<string, _>
}
// Safe branch: value is now string
const upper = stringResult.value.toUpperCase();

// Type guard style
if (String.is(value)) {
  // narrowed to string
}

// Composing: arrays & objects
const Numbers = array(Number); // ReadonlyArray<number>
const Point = object({ x: Number, y: Number });

Numbers.from([1, 2, 3]); // ok
Point.from({ x: 1, y: 2 }); // ok
Point.from({ x: 1, y: "2" }); // err -> nested Number error

Branding Basics

Branding adds semantic meaning & constraints while preserving the runtime shape:

const CurrencyCode = brand("CurrencyCode", String, (value) =>
  /^[A-Z]{3}$/.test(value)
    ? ok(value)
    : err<CurrencyCodeError>({ type: "CurrencyCode", value }),
);
type CurrencyCode = typeof CurrencyCode.Type; // string & Brand<"CurrencyCode">

interface CurrencyCodeError extends TypeError<"CurrencyCode"> {}

const formatCurrencyCodeError = createTypeErrorFormatter<CurrencyCodeError>(
  (error) => `Invalid currency code: ${error.value}`,
);

const r = CurrencyCode.from("USD"); // ok("USD")
const e = CurrencyCode.from("usd"); // err(...)

See also reusable brand factories like minLength, maxLength, trimmed, positive, between, etc.

Objects & Optional Fields

const User = object({
  name: NonEmptyTrimmedString100,
  age: optional(PositiveInt),
});
type User = typeof User.Type;

User.from({ name: "Alice" }); // ok
User.from({ name: "Alice", age: -1 }); // err(PositiveInt)

Deriving JSON String Types

const Person = object({
  name: NonEmptyString50,
  // Did you know that JSON.stringify converts NaN (a number) into null?
  // To prevent this, use FiniteNumber.
  age: FiniteNumber,
});
type Person = typeof Person.Type;

const [PersonJson, personToPersonJson, personJsonToPerson] = json(
  Person,
  "PersonJson",
);
// string & Brand<"PersonJson">
type PersonJson = typeof PersonJson.Type;

const person = Person.orThrow({
  name: "Alice",
  age: 30,
});

const personJson = personToPersonJson(person);
expect(personJsonToPerson(personJson)).toEqual(person);

Error Formatting

Evolu separates validation logic from human-readable messages. There are two layers:

  1. Per-type formatters (e.g. formatStringError) – simple, focused, already used earlier in the quick start example.
  2. A unified formatter via createFormatTypeError – composes all built-in and custom errors (including nested composite types) and lets us override selected messages.

1. Per-Type Formatter (recap)

const r = String.fromUnknown(42);
if (!r.ok) console.error(formatStringError(r.error));

2. Unified Formatter with Overrides

// Override only what we care about; fall back to built-ins for the rest.
const formatTypeError = createFormatTypeError((error) => {
  if (error.type === "MinLength") return `Min length is ${error.min}`;
});

const User = object({ name: NonEmptyTrimmedString100 });
const resultUser = User.from({ name: "" });
if (!resultUser.ok) console.error(formatTypeError(resultUser.error));

const badPoint = object({ x: Number, y: Number }).from({
  x: 1,
  y: "foo",
});
if (!badPoint.ok) console.error(formatTypeError(badPoint.error));

The unified formatter walks nested structures (object / array / record / tuple / union) and applies overrides only where specified, greatly reducing boilerplate when formatting complex validation errors.

Tip

If necessary, write globalThis.String instead of String to avoid naming clashes with native types.

Design Decision: No Bidirectional Transformations

Evolu Type intentionally does not support bidirectional transformations. It previously did, but supporting that while keeping typed error fidelity added complexity that hurt readability & reliability. Most persistence pipelines (e.g. SQLite) already require explicit mapping of query results, so implicit reverse transforms would not buy much. We may revisit this if we can design a minimal, 100% safe API that preserves simplicity.

Base Factories

FunctionDescription
arrayArray of a specific Type.
baseBase Type.
brandBranded Type.
instanceOfinstanceof Type.
literalLiteral Type.
nullishOrunion(undefined, null, T) Type.
nullOrunion(null, T) Type.
objectObject Type.
recordRecord of a key Type and value Type.
recursiveRecursive Type.
tupleTuple Type.
undefinedOrunion(undefined, T) Type.
unionUnion Type.

Base Types

VariableDescription
BigInt-
Boolean-
DateJavaScript Date.
Function-
JsonValueJSON-compatible value: string, FiniteNumber, boolean, null, JsonArray, or JsonObject.
Null-
Number-
String-
Uint8Array-
Undefined-
Unknown-

String

NameDescription
Base64UrlBase64Url without padding.
CurrencyCodeA three-letter ISO 4217 currency code (e.g., USD, EUR).
DateIsoISO 8601 date-time string.
IdGlobally unique identifier.
Int64StringStringified Int64.
JsonJSON-string Type.
lengthExact length.
maxLengthMaximum length.
minLengthMinimum length.
MnemonicThe mnemonic, also known as a "seed phrase," is a set of 12 words in a specific order chosen from a predefined list (BIP39). It provides a human-readable way to store a private key securely. The mnemonic is generated safely on the user's device using cryptographically secure random number generation, ensuring it remains private and unique.
NonEmptyString-
NonEmptyString100-
NonEmptyString1000-
NonEmptyTrimmedString-
NonEmptyTrimmedString100-
NonEmptyTrimmedString1000-
regexString matching a regular expression.
SimpleNameSimple alphanumeric string for naming in file systems, URLs, and identifiers.
SimplePasswordTrimmed string between 8 and 64 characters, branded as SimplePassword.
String-
String100-
String1000-
trimmedTrimmed string.
TrimmedStringTrimmed string
TrimmedString100-
TrimmedString1000-
UrlSafeStringURL-safe string.
createIdFromStringCreates an Id from a string using SHA-256.
idCreates a branded Id Type for a table's primary key.

Number

VariableDescription
betweenNumber within a range, inclusive.
finiteFinite number.
FiniteNumberFinite number.
greaterThanNumber greater than a specified value.
greaterThanOrEqualToNumber β‰₯ a specified value.
intInteger within the safe range of JavaScript numbers.
IntInteger within the safe range of JavaScript numbers.
Int6464-bit signed integer.
lessThanNumber less than a specified value.
lessThanOrEqualToNumber ≀ a specified value.
multipleOfNumber that is a multiple of a divisor.
negativeNegative number.
NegativeInt-
NegativeNumber-
nonNaNNumber that is not NaN.
NonNaNNumber-
nonNegativeNon-negative number.
NonNegativeInt-
NonNegativeNumber-
nonPositiveNon-positive number.
NonPositiveInt-
NonPositiveNumber-
positivePositive number.
PositiveInt-
PositiveNumber-

Array

NameDescription
JsonArrayJSON-compatible array of JsonValue elements.
lengthExact length.
maxLengthMaximum length.
minLengthMinimum length.
arrayArray of a specific Type.

Object

NameDescription
JsonObjectJSON-compatible object with string keys and JsonValue values.
nullableToOptionalConverts each β€œnullable” property (a union that includes Null) into an optional property. This means consumers can omit the property entirely, or set it to null, or set it to the non-null member of the union.
objectObject Type.
omitCreate a new object Type by omitting some keys.
partialCreates a partial object type where all properties are optional.
recordRecord of a key Type and value Type.

Utilities

NameDescription
BrandFactoryHelper type for Type Factory that creates a branded Type.
InferErrorExtracts the specific error type from a Type.
InferErrorsExtracts all error types (Error / ParentError) from a Type.
InferInputExtracts the input type from a Type.
InferNameExtracts the name from a Type.
InferParentExtracts the parent type from a Type.
InferParentErrorExtracts the parent error type from a Type.
InferTypeExtracts the type from a Type.
MergeObjectTypeErrorsMerge Error and ParentError into one ObjectError so tooltips and error messages are easier to read.
TypeErrorsUnion of all TypeErrors defined in the Type.ts file, including base type errors (e.g., StringError, NumberError), composite type errors (ArrayError, ObjectError), and optionally, user-defined extra errors.
TypeNameUnique identifier for a Type.
createBaseTypeErrorFormatterCreates a formatter function for a base TypeError.
createFormatTypeErrorFormats Evolu Type errors into user-friendly messages.
createTypeErrorFormatterCreates a formatter function for TypeError.
isTypeChecks if the given value is an Type.
typeErrorToStandardSchemaIssuesConverts an Evolu TypeError to Standard Schema V1 issues format.

Other

NameDescription
StandardSchemaV1-
ArrayError-
ArrayTypeArrayType extends Type with an additional element property for reflection.
Base64UrlError-
BetweenError-
BigIntError-
BooleanError-
BrandTypeThe Standard Schema interface.
BrandWithoutRefineError-
CurrencyCodeError-
DateIsoError-
EvoluTypeError-
FiniteError-
FunctionError-
GreaterThanError-
GreaterThanOrEqualToError-
IdError-
InstanceOfError-
InstanceOfTypeThe Standard Schema interface.
Int64Error-
Int64StringError-
IntError-
JsonError-
JsonObject-
JsonObjectInput-
LengthError-
LessThanError-
LessThanOrEqualToError-
LiteralError-
LiteralTypeThe Standard Schema interface.
MaxLengthError-
MinLengthError-
MnemonicError-
MultipleOfError-
NegativeError-
NonNaNError-
NonNegativeError-
NonPositiveError-
NullError-
NumberError-
ObjectError-
ObjectTypeObjectType extends Type with an additional props property for reflection.
ObjectWithRecordError-
ObjectWithRecordTypeObjectWithRecordType extends Type with additional props and record properties for reflection.
OptionalTypeThe Standard Schema interface.
PositiveError-
RecordError-
RecordTypeRecordType extends Type with additional key and value properties for reflection.
RecursiveTypeThe Standard Schema interface.
RegexError-
SimpleNameError-
StandardSchemaV1The Standard Schema interface.
StringError-
TableIdThe Standard Schema interface.
TableIdError-
TrimmedError-
TupleError-
TupleTypeTupleType extends Type with an additional elements property for reflection.
TypeThe Standard Schema interface.
TypeError-
TypeErrorWithReason-
Uint8ArrayError-
UndefinedError-
UnionError-
UnionTypeUnionType extends Type with an additional members property for reflection.
ValidMutationSizeError-
AnyType-
Base64Url-
CurrencyCode-
DateIso-
FiniteNumber-
Id-
IdBytes-
Int-
Int64-
Int64String-
IsUnionWithNull-
Json-
JsonArray-
JsonArrayInput-
JsonValue-
JsonValueError-
JsonValueInput-
Mnemonic-
NegativeInt-
NegativeNumber-
NonEmptyString-
NonEmptyString100-
NonEmptyString1000-
NonEmptyTrimmedString-
NonEmptyTrimmedString100-
NonEmptyTrimmedString1000-
NonNaNNumber-
NonNegativeInt-
NonNegativeNumber-
NonPositiveInt-
NonPositiveNumber-
NullableToOptionalProps-
NullTypeInMembers-
PositiveInt-
PositiveNumber-
SimpleName-
SimplePassword-
SimplePasswordError-
String100-
String1000-
TransformNullable-
TrimmedString-
TrimmedString100-
TrimmedString1000-
TypeErrorFormatter-
UrlSafeString-
UrlSafeStringError-
ValidMutationSize-
base64UrlToUint8ArrayDecodes a Base64Url string to a Uint8Array.
EvoluTypeValidates that an unknown value is an Evolu Type (i.e., satisfies AnyType).
formatBase64UrlError-
formatBetweenError-
formatBigIntError-
formatBooleanError-
formatCurrencyCodeError-
formatDateIsoError-
formatFiniteError-
formatFunctionError-
formatGreaterThanError-
formatGreaterThanOrEqualToError-
formatIdError-
formatInstanceOfError-
formatInt64Error-
formatInt64StringError-
formatIntError-
formatIsTypeError-
formatJsonError-
formatLengthError-
formatLessThanError-
formatLessThanOrEqualToError-
formatLiteralError-
formatMaxLengthError-
formatMinLengthError-
formatMnemonicError-
formatMultipleOfError-
formatNegativeError-
formatNonNaNError-
formatNonNegativeError-
formatNonPositiveError-
formatNullError-
formatNumberError-
formatPositiveError-
formatRegexError-
formatStringError-
formatTableIdError-
formatTrimmedError-
formatUint8ArrayError-
formatUndefinedError-
formatValidMutationSizeError-
IdBytesBinary representation of an Id.
idBytesTypeValueLength-
maxMutationSize-
maxPositiveIntMaximum safe positive integer value for practically infinite operations.
uint8ArrayToBase64UrlEncodes a Uint8Array to a Base64Url string.
createIdCreates an Id.
dateIsoToDate-
dateToDateIso-
formatArrayError-
formatObjectError-
formatObjectWithRecordError-
formatRecordError-
formatSimplePasswordError-
formatTupleError-
formatUnionError-
idBytesToId-
idToIdBytes-
isOptionalTypeDetermines if a given type is an OptionalType.
isUnionType-
jsonCreates a branded JSON string Type and type-safe conversion functions for a given Type.
jsonToJsonValue-
jsonValueToJson-
optionalOptional Type.
parseJson-
trim-
validMutationSizeEvolu has to limit the maximum mutation size. Otherwise, sync couldn't use the maxProtocolMessageRangesSize. The max size is 640KB in bytes, measured via MessagePack. Evolu Protocol DbChange will be smaller thanks to various optimizations.

Was this page helpful?