Defjs
Core

struct

Facade object for building protocol-boundary structs and parsing values with its constructors and struct.parse.

@defjs/core · Constant

Prefer this object over importing the constructor functions one by one. Compose shapes with struct.object, then parse with struct.parse or let HTTP/SSE/WebSocket endpoints apply the same contracts at the wire boundary. struct is a plain object of helpers.

import { struct } from "@defjs/core";

const User = struct.object({
  id: struct.number(),
  displayName: struct.string().alias("display_name"),
});

function readUser() {
  const [error, user] = struct.parse(User, { id: 1, displayName: "Ada" });
  if (error) {
    console.error(error.message);
    return;
  }
  console.log(user.displayName);
}

readUser();

Primitives and containers

HelperRole
struct.string()Text
struct.number()Number, NaN rejected
struct.boolean()Boolean
struct.bigint()bigint, or a string that parses as one
struct.date()Date, from a Date, timestamp, or string
struct.literal(value)Exact literal
struct.null()Only null
struct.any() / unknown() (struct.unknown)Any value except null and undefined
struct.array(item)Array of one item struct
struct.tuple(items)Fixed-length array; items is one array of structs
struct.record(value)Plain object with string keys and one value struct
struct.object(shape)Plain object with fixed fields; unknown keys are stripped
struct.or(...options)First matching option
struct.intersection(...structs)Every struct must match; object results are merged
struct.discriminatedUnion(tag, options)Object option picked by the value of the tag field
struct.enum(values)String array, or the values of a { Name: value } object

Body codecs and binary values

HelperRole
struct.formData(shape) / urlencoded(shape) (struct.urlencoded)Object struct plus a codec tag. The tag applies only when that value is the request body root
struct.arrayBuffer() / blob() (struct.blob) / file() (struct.file)Binary values. As a body root they are sent as bytes; as output / error they select that representation
struct.string()Text. As a body root or an inbound string struct, the bytes stay text

Parsing and modifiers

struct.parse(struct, value, options?) parses an unknown value and returns [null, value] or [StructError, undefined].

Every struct has the chainable modifiers .optional(), .null() / .nullable(), .nullish(), and .alias(name). See StructMethods.

Members

Prop

Type

any () => Struct<PresentValue, any>

Struct that accepts any value except null and undefined, without validation.

The value is returned unchanged, and nothing inside it is validated or stripped.

  • Chain .null(), .optional(), or .nullish() to accept null or undefined.

  • The output type is any. Prefer struct.unknown() when the caller should narrow the value.

array <S extends StructLike<unknown, unknown, boolean>>(item: S) => ArrayStruct<S>

Struct that parses an array with one item struct.

  • Parses every element with item and outputs a new array. A non-array fails with invalid_type (expected array).

  • Parsing stops at the first failing element, and issue.path holds its index.

  • Throws TypeError when item is not a struct.

ArrayStruct<S> is not exported. It is a Struct<StructInput<S>[], Infer<S>[]>.

arrayBuffer () => Struct<ArrayBuffer, ArrayBuffer>

Struct that accepts an ArrayBuffer.

  • Accepts an ArrayBuffer only. Typed arrays and other views fail with invalid_type.

  • As a body root it is sent as bytes with application/octet-stream.

  • As an output or error struct it makes the response body be read as an ArrayBuffer.

bigint () => Struct<bigint | string, bigint>

Struct that accepts a bigint or a bigint string and outputs a bigint.

  • Accepts a bigint, or a string that BigInt() can parse. A number is rejected.

  • Output is always a bigint; an unparsable string fails with invalid_type.

  • When encoded for the wire, the value becomes a decimal string.

blob () => Struct<Blob, Blob>

Struct that accepts a Blob.

  • Accepts a Blob (a File is a Blob). Anything else fails with invalid_type.

  • As a body root it is sent as is, with the blob's own type or application/octet-stream.

  • As an output or error struct it makes the response body be read as a Blob.

boolean () => Struct<boolean, boolean>

Struct that accepts boolean values.

Accepts true and false only. Other values, including 'true' and 1, fail with invalid_type.

date () => Struct<Date | number | string, Date>

Struct that accepts a Date, timestamp, or date string and outputs a Date.

  • Accepts a Date, a millisecond timestamp, or a string. A valid Date is returned as the same object. A number or string is converted with new Date(value).

  • Output is a Date. A value that produces an invalid date fails with invalid_type.

  • When encoded for the wire, the value becomes an ISO 8601 string (toISOString()).

discriminatedUnion <const TDiscriminator extends string, const TOptions extends readonly [ObjectStruct<ObjectShape>, ...ObjectStruct<ObjectShape>[]]>(discriminator: TDiscriminator, options: TOptions & RequiredDiscriminatorOptions<TDiscriminator, TOptions>) => DiscriminatedUnionStruct<TOptions>

Struct that picks one object struct by the value of a tag field.

const Event = struct.discriminatedUnion("kind", [
  struct.object({ kind: struct.literal("created"), id: struct.number() }),
  struct.object({ kind: struct.literal("deleted"), id: struct.number() }),
]);
  • Every option must be an object struct whose discriminator field is a required struct.literal(). A missing field, a non-literal field, an optional field, or a duplicate tag value throws TypeError when the struct is created.

  • The input must be a plain object. The tag value selects the option, and that option parses the whole object.

  • A missing tag fails with missing_key, and an unknown tag fails with invalid_union. Both report the tag field's path. Other failures come from the selected option.

  • With struct.parse(..., { aliases: true }) the tag is read from the field's alias.

RequiredDiscriminatorOptions and DiscriminatedUnionStruct are not exported. The type check rejects an option whose tag field is missing or may be undefined.

enum { <const T extends readonly [string, ...string[]]>(value: T): Struct<EnumValue<T>, EnumValue<T>>; <const T extends { [key: string]: number | string; }>(value: T): Struct<EnumValue<T>, EnumValue<T>>; }

Struct that accepts one value from a list of strings or from the values of an object.

  • With a non-empty string array, the accepted values are the array items.

  • With an object, the accepted values are the object's string and number values, not its keys. { A: 'a', B: 2 } accepts 'a' and 2.

  • A TypeScript numeric enum object also accepts its member names, because the reverse mapping is part of its values.

  • A value outside the set fails with invalid_enum. An object with no string or number value throws TypeError when the struct is created.

file () => Struct<File, File>

Struct that accepts a File.

  • Accepts a File only; a plain Blob fails with invalid_type.

  • Use it for a field of struct.formData(), or for a raw file body.

  • As an output or error struct it makes the response body be read as a blob.

formData <T extends ObjectShape>(shape: T) => ObjectStruct<T>

Object struct that is encoded as multipart FormData when it is the request body.

Parses like struct.object(), and carries a formData codec tag.

  • The tag applies only when this struct's value is the request body root. The body is then sent as FormData with a platform multipart boundary.

  • Arrays repeat the field name, undefined fields are skipped, and Blob / File values are appended as files. A nested object fails the request with REQ_BUILD_FAILED.

  • struct.parse ignores the tag. It does not accept a FormData instance, only a plain object.

intersection <const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(...structs: T) => T extends readonly [unknown] ? T[0] : T extends readonly [unknown, unknown, ...unknown[]] ? IntersectionStruct<T> : T[0] | IntersectionStruct<T>

Struct that requires a value to satisfy every given struct.

struct.intersection is variadic: pass each struct as its own argument.

  • Parses the input with every struct and merges the results.

  • When every struct is an object struct, the output is one object with the fields of all of them. Keys that none of them declares are removed.

  • When two structs produce incompatible values for the same key, it fails with invalid_intersection.

  • Accepts null only when every struct accepts it, including a nested intersection marked .null().

  • A single struct is returned unchanged, so it keeps its own identity and type, including .optional() and .null(). Calling it with no structs throws TypeError, and so does a non-struct argument.

  • A nonempty variadic tuple must account for that singleton. Spreading a tuple that may contain only one member infers both the singleton and multi-member results. Its object fields may therefore be optional. A tuple known to contain at least two members keeps the required intersection semantics.

IntersectionStruct<T> is not exported. It is a Struct whose input and output are the intersections of the member types.

literal <const T extends LiteralValue>(value: T) => Struct<T, T>

Struct that accepts exactly one boolean, null, number, or string value.

  • Accepts only value. The comparison uses Object.is, so any other value fails with invalid_literal.

  • value must be a boolean, null, number, or string.

  • Use it for the tag field of struct.discriminatedUnion().

null () => Struct<null, null>

Struct that accepts only null.

Accepts null only. Every other value fails with invalid_type. That includes undefined at the root or in an array or tuple; a missing object or record field reports missing_key instead.

This is not the .null() modifier. The modifier adds null to the values another struct accepts.

number () => NumberStruct

Struct that accepts number values except NaN.

  • Accepts number values, including Infinity. NaN and non-numbers fail with invalid_type.

  • Strings are not coerced.

object <T extends ObjectShape>(shape: T) => ObjectStruct<T>

Struct that parses a plain object with a fixed set of fields.

shape maps field names to structs.

  • Every field is required by default. A missing key fails with missing_key.

  • .optional() and .nullish() allow a missing key. An absent optional field stays absent in the output.

  • .null() / .nullable() allow null.

  • .alias(name) sets the field's wire key. struct.parse reads logical keys by default; { aliases: true } and HTTP JSON decoding read wire keys, and HTTP JSON encoding writes them. Parsed output uses logical keys.

  • Keys that are not in shape are removed from the output. The output is a new object with a null prototype.

  • The input must be a plain object. Arrays, Map, and class instances fail with invalid_type (expected object).

  • Parsing stops at the first failing field, and issue.path holds its key.

  • Throws TypeError when shape is not a plain object.

ObjectShape is { [key: string]: any }; each value must be a struct.

or <const T extends readonly [StructLike, ...StructLike[]]>(...options: T) => UnionStruct<T>

Struct that accepts a value matching any one of several structs.

struct.or is variadic: pass each option as its own argument.

  • Tries the options in order and returns the result of the first one that parses.

  • If none matches, it fails with one invalid_union issue at the union's own path. The issues of the individual options are not reported.

  • Throws TypeError when an option is not a struct.

UnionStruct<T> is not exported. It is a Struct whose input and output are the unions of the option types, without undefined. For tagged objects prefer struct.discriminatedUnion(), which reports the issue of the selected option.

parse <S extends StructLike<unknown, unknown, boolean>>(struct: S, value: unknown, options?: { aliases?: boolean; errorMap?: ErrorMap; }) => ParseResult<S["_struct"]["output"]>

Parses an unknown value with a struct and returns an error-first tuple.

  • struct is the struct to parse with. A value that is not a struct throws TypeError.

  • value is the unknown input.

  • options.aliases defaults to false: object keys are read by their logical names (displayName). With true they are read by their wire names (display_name), the way HTTP/JSON codecs read them. The output always uses logical names.

  • options.errorMap is an ErrorMap that rewrites issue messages for this call only, including bigint/date conversion failures from a struct created by another Core copy. Any string, including '', replaces the message; only undefined keeps the default. Custom text is used verbatim.

  • Parsing is fail-fast: it stops at the first failure, so the error holds one issue. Unknown object keys are stripped from the output, and the input is not modified.

  • At most 256 nested containers (object, array, record, or tuple) are visited along a path, counting the root container as the first. The next container fails with invalid_type at its logical path, with expected: 'value nested at most 256 levels deep'. In a union, later candidates may still succeed; if none match, the union reports invalid_union. Cycles through declared container fields also reach this limit; shared non-cyclic children are allowed. Union and intersection branches do not add a container level, and any/unknown values remain opaque rather than being traversed.

import { struct } from "@defjs/core";

const User = struct.object({ id: struct.number(), displayName: struct.string() });

function readUser() {
  const [error, user] = struct.parse(User, { id: 1, displayName: "Ada" });
  if (error) {
    console.error(error.prettify());
    return;
  }
  console.log(user.displayName);
}

readUser();

any and unknown do not traverse their values, but an intersection may recursively merge two distinct object or array values returned by those leaves. This merge shares the same 256-container budget with its enclosing logical path. Exceeding it produces invalid_intersection at the rejected container's logical path. Identical references are retained without traversal, including a shared cyclic value.

record <S extends StructLike<unknown, unknown, boolean>>(value: S) => RecordStruct<S>

Struct that parses an object with string keys and one value struct.

  • Parses every own key of a plain object with value. Anything else fails with invalid_type (expected record).

  • Parsing stops at the first failing value, and issue.path holds its key.

  • The output is a new object with a null prototype.

  • Throws TypeError when value is not a struct.

RecordStruct<S> is not exported. It is a Struct<{ [key: string]: StructInput<S> }, { [key: string]: Output }>, where Output is Infer<S> without undefined. When S is an optional struct, a value that is undefined omits its key from the output.

string () => StringStruct

Struct that accepts string values.

  • Accepts string values only; anything else fails with invalid_type.

  • As a body root, or as an inbound output / error struct, the bytes stay text instead of being parsed as JSON.

tuple <const T extends readonly [StructLike<unknown, unknown, boolean>, ...StructLike<unknown, unknown, boolean>[]]>(items: T) => TupleStruct<T>

Struct that parses a fixed-length array with one struct per position.

struct.tuple takes one array of structs, not separate arguments:

const Pair = struct.tuple([struct.string(), struct.number()]);
  • Each position is parsed with the struct at the same index. The input must be an array of exactly the same length, otherwise it fails with invalid_type (expected tuple of length N).

  • Passing the structs as separate arguments, struct.tuple(a, b), throws a TypeError.

  • Throws TypeError when an item is not a struct.

TupleStruct<T> is not exported. It is a Struct whose input and output are tuples of the item types.

unknown () => UnknownStruct

Struct that accepts any value except null and undefined, typed as unknown.

The value is returned unchanged, and nothing inside it is validated or stripped.

  • Chain .null(), .optional(), or .nullish() to accept null or undefined.

  • The output type is unknown, so the caller must narrow it before use.

urlencoded <T extends ObjectShape>(shape: T) => ObjectStruct<T>

Object struct that is encoded as an URL-encoded form when it is the request body.

Parses like struct.object(), and carries a urlencoded codec tag.

  • The tag applies only when this struct's value is the request body root. The body is then sent as URLSearchParams with application/x-www-form-urlencoded;charset=UTF-8.

  • Arrays repeat the field name and undefined fields are skipped. A nested object or a Blob fails the request with REQ_BUILD_FAILED.

  • struct.parse ignores the tag. It does not accept a URLSearchParams instance, only a plain object.

Examples

const User = struct.object({
  id: struct.number(),
  displayName: struct.string().alias('display_name'),
})

const getUser = defineRequest({
  method: 'GET',
  path: '/users/:id',
  input: struct.object({ id: struct.number() }),
  builder: (input) => ({ path: input }),
  output: User,
})

function readUser() {
  const [error, user] = struct.parse(User, { id: 1, displayName: 'Ada' })
  if (error) {
    console.error(error.message)
    return
  }
  console.log(user.displayName)
}

readUser()
Edit this page on GitHub
Released under the MIT License. · Copyright © 2026 MunMunMiao