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
| Helper | Role |
|---|---|
| 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
| Helper | Role |
|---|---|
| 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 acceptnullorundefined. -
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
itemand outputs a new array. A non-array fails withinvalid_type(expectedarray). -
Parsing stops at the first failing element, and
issue.pathholds its index. -
Throws
TypeErrorwhenitemis 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
ArrayBufferonly. Typed arrays and other views fail withinvalid_type. -
As a body root it is sent as bytes with
application/octet-stream. -
As an
outputorerrorstruct it makes the response body be read as anArrayBuffer.
bigint () => Struct<bigint | string, bigint>Struct that accepts a bigint or a bigint string and outputs a bigint.
-
Accepts a
bigint, or a string thatBigInt()can parse. Anumberis rejected. -
Output is always a
bigint; an unparsable string fails withinvalid_type. -
When encoded for the wire, the value becomes a decimal string.
blob () => Struct<Blob, Blob>Struct that accepts a Blob.
-
Accepts a
Blob(aFileis aBlob). Anything else fails withinvalid_type. -
As a body root it is sent as is, with the blob's own type or
application/octet-stream. -
As an
outputorerrorstruct it makes the response body be read as aBlob.
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 validDateis returned as the same object. A number or string is converted withnew Date(value). -
Output is a
Date. A value that produces an invalid date fails withinvalid_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
discriminatorfield is a required struct.literal(). A missing field, a non-literal field, an optional field, or a duplicate tag value throwsTypeErrorwhen 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 withinvalid_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'and2. -
A TypeScript numeric
enumobject 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 throwsTypeErrorwhen the struct is created.
file () => Struct<File, File>Struct that accepts a File.
-
Accepts a
Fileonly; a plainBlobfails withinvalid_type. -
Use it for a field of struct.formData(), or for a raw file body.
-
As an
outputorerrorstruct 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
FormDatawith a platform multipart boundary. -
Arrays repeat the field name,
undefinedfields are skipped, andBlob/Filevalues are appended as files. A nested object fails the request withREQ_BUILD_FAILED. -
struct.parseignores the tag. It does not accept aFormDatainstance, 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
nullonly 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 throwsTypeError, 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 usesObject.is, so any other value fails withinvalid_literal. -
valuemust be aboolean,null,number, orstring. -
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 () => NumberStructStruct that accepts number values except NaN.
-
Accepts
numbervalues, includingInfinity.NaNand non-numbers fail withinvalid_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()allownull. -
.alias(name)sets the field's wire key.struct.parsereads 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
shapeare removed from the output. The output is a new object with anullprototype. -
The input must be a plain object. Arrays,
Map, and class instances fail withinvalid_type(expectedobject). -
Parsing stops at the first failing field, and
issue.pathholds its key. -
Throws
TypeErrorwhenshapeis 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_unionissue at the union's own path. The issues of the individual options are not reported. -
Throws
TypeErrorwhen 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). Withtruethey 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; onlyundefinedkeeps 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, ortuple) are visited along a path, counting the root container as the first. The next container fails withinvalid_typeat its logical path, withexpected: 'value nested at most 256 levels deep'. In a union, later candidates may still succeed; if none match, the union reportsinvalid_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, andany/unknownvalues 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 withinvalid_type(expectedrecord). -
Parsing stops at the first failing value, and
issue.pathholds its key. -
The output is a new object with a
nullprototype. -
Throws
TypeErrorwhenvalueis 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 () => StringStructStruct that accepts string values.
-
Accepts
stringvalues only; anything else fails withinvalid_type. -
As a body root, or as an inbound
output/errorstruct, 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(expectedtuple of length N). -
Passing the structs as separate arguments,
struct.tuple(a, b), throws aTypeError. -
Throws
TypeErrorwhen 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 () => UnknownStructStruct 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 acceptnullorundefined. -
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
URLSearchParamswithapplication/x-www-form-urlencoded;charset=UTF-8. -
Arrays repeat the field name and
undefinedfields are skipped. A nested object or aBlobfails the request withREQ_BUILD_FAILED. -
struct.parseignores the tag. It does not accept aURLSearchParamsinstance, 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()