typestruct
Composable and checkable JavaScript (and TypeScript) data structure.
Basic usage
import {
assert,
number,
object,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
const Product = object({
id: string(),
name: string(),
price: number(),
category: object({
id: string(),
name: string(),
}),
});
declare const data: unknown;
assert(Product, data);The data is infer as follows:
interface data {
id: string;
name: string;
price: number;
category: {
id: string;
name: string;
};
}assert throws an error if the data does not match the struct and reports
issues.
Other functions such as is and the validate function can also be used.
Check functions
Check function is a runner that takes a Struct (strictly Cheakable) and input
and checks it.
The following check functions differ in the way they express their results.
is
Whether the input satisfies struct or not. With type guard, inputs are type inferred.
This is best used if you are only interested in whether or not the struct is satisfied.
import { is, string } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(string(), "any input"), true);
assertEquals(is(string(), {}), false);assert
Assert whether the input satisfies the struct.With assert signature, inputs are type inferred.
If Struct is not satisfied, a StructError will be thrown.
If you want to stop execution, it is best to use this.
import {
assert,
maxSize,
minSize,
StructError,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertThrows } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertThrows(() => assert(maxSize(5), "typestruct"), StructError);validate
Returns the checking result. If input satisfies struct, the valid field is
true and returns an object with type-inferred data. Otherwise, the valid
field is false and returns an object containing the errors field.
Use this if you want to control the check results.
import {
number,
object,
validate,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const Product = object({
price: number(),
});
assertEquals(validate(Product, { price: 100 }), {
valid: true,
data: { price: 100 },
});Struct factory
Central to the definition of Struct is the use of struct factories.
The standard struct factory makes it easy to create a basic Struct that checks for standard types and values.
For example, the maxSize returns Struct<Iterable<unknown>>. This guarantees
an upper bound on the number of elements in the input.
import { maxSize } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
const Max10 = maxSize(10);Core Struct factories
string
Create string data type struct.
import { is, string } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(string(), ""), true);
assertEquals(is(string(), 0), false);custom message
Default message
expected string, actual ${typeof INPUT}Full customize
import { string } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
string("Custom message");
string(({ actual, expected }) => `expected ${expected}, actual ${actual}`);number
Create number data type struct.
import { is, number } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(number(), 0), true);
assertEquals(is(number(), ""), false);bigint
Create bigint data type struct.
import { bigint, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(bigint(), 0), true);
assertEquals(is(bigint(), 0n), false);boolean
Create boolean data type struct.
import { boolean, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(boolean(), true), true);
assertEquals(is(boolean(), ""), false);func
Create function data type struct.
import { func, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(func(), () => {}), true);
assertEquals(is(func(), {}), false);symbol
Create symbol data type struct.
import { is, symbol } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(symbol(), Symbol.iterator), true);
assertEquals(is(symbol(), {}), false);value
Create primitive value struct.
import { is, value } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(value(null), null), true);
assertEquals(is(value(null), undefined), false);object
Create object data type struct. Treat null as not an object.
import { is, object } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(object(), {}), true);
assertEquals(is(object(), null), false);Create object literal struct. Additional properties will ignore.
import {
is,
object,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const Book = object({
title: string(),
postBy: object({
name: string(),
}),
});
assertEquals(
is(Book, {
title: "Diary of Anne Frank",
postBy: { name: "Anne Frank" },
}),
true,
);record
Create Record struct. Ensure the input is object, and keys and values satisfy
struct.
import {
is,
number,
record,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const Record = record(string(), number()); // { [k: string]: number }
assertEquals(is(Record, { john: 80, tom: 100 }), true);
assertEquals(is(Record, { name: "john", hobby: "swimming" }), false);array
Create any[] data type struct.
import { array, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(array(), []), true);
assertEquals(is(array(), {}), false);instance
Create instanceof struct. Ensure that the input is an instance of a defined
constructor.
import { instance, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(instance(Array), []), true);
assertEquals(is(instance(class Any {}), null), false);pick
Create Pick struct. From struct, pick a set of properties whose keys are in
the definition.
import {
is,
object,
pick,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const User = object({ id: string(), name: string() });
const data = { name: "tom" };
assertEquals(is(User, data), false);
assertEquals(is(pick(User, ["name"]), data), true);omit
Create Omit struct. From struct, omit a set of properties whose keys are in
the definition.
import {
is,
object,
omit,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const User = object({ id: string(), name: string() });
const data = { name: "tom" };
assertEquals(is(User, data), false);
assertEquals(is(omit(User, ["id"]), data), true);partial
Create Partial struct. Make all properties in struct optional.
import {
is,
object,
partial,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const User = object({ id: string(), name: string() });
assertEquals(is(User, {}), false);
assertEquals(is(partial(User), {}), true);nullable
Create nullable struct. Add null tolerance to struct.
import {
is,
nullable,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const strOrNull = nullable(string());
assertEquals(is(strOrNull, "typestruct"), true);
assertEquals(is(strOrNull, null), true);
assertEquals(is(strOrNull, undefined), false);optional
Create optional struct. Add undefined tolerance to struct.
import {
is,
optional,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const strOr = optional(string());
assertEquals(is(strOr, "typestruct"), true);
assertEquals(is(strOr, undefined), true);
assertEquals(is(strOr, null), false);Operator Struct
Logical operations for Struct.
and
Create intersection struct. Ensure all structures satisfy.
The first Struct::In and the last Struct::Out create a new
Struct<In, Out>.
Strong type-narrowing safely joins intermediate struct.
import {
and,
is,
maxSize,
minSize,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const String5_10 = and(string()).and(minSize(5)).and(maxSize(10));
assertEquals(is(String5_10, "typestruct"), true);
assertEquals(is(String5_10, ""), false);or
Create union struct. Ensure any of struct satisfy.
The first Struct::In and all Struct::Out create a new Struct<In, Out>
import {
is,
number,
or,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const StrOrNum = or(string()).or(number());
assertEquals(is(StrOrNum, ""), true);
assertEquals(is(StrOrNum, 0), true);
assertEquals(is(StrOrNum, {}), false);not
Create Inversion struct. Ensure the structure is not satisfied.
import {
is,
not,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const NotString = not(string());
assertEquals(is(NotString, 0), true);
assertEquals(is(NotString, "typestruct"), false);Sub Struct
Sub struct refers to a struct whose input type is other than Top-type.
maximum
Create maximum struct. Ensure the input less than or equal to threshold.
import { is, maximum } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(maximum(5), 5), true);
assertEquals(is(maximum(5), 6), false);Any type can be defined as a threshold. Internally, they are compared by comparison operators.
import { is, maximum } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(
is(maximum(new Date("2022-12-31")), new Date("2023-01-01")),
false,
);minimum
Create minimum struct. Ensure the input grater than or equal to threshold.
import { is, minimum } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(minimum(5), 5), true);
assertEquals(is(minimum(5), 4), false);Any type can be defined as a threshold. Internally, they are compared by comparison operators.
import { is, minimum } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(
is(minimum(new Date("2000-01-01")), new Date("1999-12-31")),
false,
);maxSize
Create max size struct. Sets the maximum number of elements.
import { is, maxSize } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(maxSize(10), "typestruct"), true);
assertEquals(is(maxSize(4), new Array(5)), false);minSize
Create min size struct. Sets the minimum number of elements.
import { is, minSize } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(minSize(10), "typestruct"), true);
assertEquals(is(minSize(10), new Array(5)), false);size
Create size struct. Ensure the number of elements.
import { is, size } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(size(10), "typestruct"), true);
assertEquals(is(size(1), new Set()), false);empty
Create empty struct. Empty means there are no elements.
import { empty, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(empty(), ""), true);
assertEquals(is(empty(), [1]), false);nonempty
Create non empty struct. Non empty meas there are more than one element.
import { is, nonempty } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(nonempty(), new Set([1, 2, 3])), true);
assertEquals(is(nonempty(), new Map()), false);pattern
Create pattern struct. Ensure the input match to the pattern.
import { is, pattern } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(pattern(/type/), "typescript"), true);
assertEquals(is(pattern(/type/), "javascript"), false);list
Create list struct. List is array subtype. Ensure that all elements are same type.
import {
and,
array,
is,
list,
number,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(list(string()), ["typescript", "javascript"]), true);
assertEquals(is(and(array()).and(list(number())), [1, 2, 3]), true);tuple
Create tuple struct. Tuple is array subtype. Ensure that the position and type of the elements match.
import {
and,
array,
is,
number,
object,
string,
tuple,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const Tuple = tuple([string(), number(), object()]);
assertEquals(is(Tuple, ["", 0, {}]), true);
assertEquals(is(and(array()).and(Tuple), [1, 2, 3] as unknown), true);int
Create integer struct. Ensure the input is integer.
import { int, is } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(int(), 1.0), true);
assertEquals(is(int(), 1.1), false);positive
Create positive number struct. Ensure the input is positive number. Positive number means greater than zero.
import { is, positive } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(positive(), 0.1), true);
assertEquals(is(positive(), 0), false);nonpositive
Create non-positive value struct. Ensure the input is non-positive number. Non-positive number means less than or equal to zero.
import {
is,
nonpositive,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(nonpositive(), 0), true);
assertEquals(is(nonpositive(), -1), true);
assertEquals(is(nonpositive(), 1), false);negative
Create negative number struct. Ensure the input is negative number. Negative number means a number less than zero.
import { is, negative } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(negative(), -0.1), true);
assertEquals(is(negative(), 0), false);nonnegative
Create negative value struct. Ensure the input is non-negative number. Non-negative number means greater than or equal to zero.
import {
is,
nonnegative,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(nonnegative(), 0), true);
assertEquals(is(nonnegative(), 1), true);
assertEquals(is(nonnegative(), -1), false);nan
Create NaN struct. Ensure the input is NaN.
import { is, nan } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(nan(), NaN), true);
assertEquals(is(nan(), 0), false);validDate
Create valid date struct. Ensure the input is valid date (non NaN) format.
import { is, validDate } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
assertEquals(is(validDate(), new Date("2022-01-01")), true);
assertEquals(is(validDate(), new Date("invalid date")), false);Struct deep dive
The essence of struct is to guarantee types and values. And you probably do validation for the same reason.
Struct is an interface with In and Out generics.
interface Struct<In, Out extends In = any> {}In represents the accepted data types. It can be any type, including the top
types unknown and string.
Out represents the guaranteed data type. It indicates the type of the result
of narrowing the data.
Type Guarantee
Now let’s look at the string factory.
import { string } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
const String = string(); // Struct<unknown, string>Another example is Struct<{}, { length: number }>, which accepts Non-nullable
data types and guarantees that the data has the length property.
Depending on the data type of In, there are constraints on the inputs that
can be passed to the check function.
Value Guarantee
If there is no type narrowing, nothing needs to be specified for Out. This
implies that it is a value-checking struct.
import { maxSize } from "https://deno.land/x/typestruct@$VERSION/mod.ts";
const MaxSize = maxSize(5); // Struct<Iterable<unknown>>This indicates that the Iterable<unknown> type is accepted and no type
narrowing.
Composable struct
Struct should be singly liable. Struct should have a single responsibility because things that do one thing can be composited together.
Struct and Composition
Struct should be singly liable. Structs that do one thing can be efficiently synthesized with each other.
We provide and and or as structs that compose.
For example, suppose you want to guarantee that the input is a string, and that it has between 10 and 100 characters.
Combine the and and string, maxSize and minSize modules to create a new
struct.
import {
and,
maxSize,
minSize,
string,
} from "https://deno.land/x/typestruct@$VERSION/mod.ts";
const MinSize10 = minSize(10);
const MaxSize100 = maxSize(100);
const String = string();
const String10To100 = and(String).and(MinSize10).and(MaxSize100);Here’s what’s happening with and.
String is Struct<unknown, string>. When and accepts String, it narrows down
the next acceptable type; string, the Out of String. That is,
Struct<string, string>.
MinSize10 is Struct<Iterable<unknown>>. Iterable<unknown> is accepted
because it is compatible with string. Since Out is omitted (any), no
narrowing is done. The same goes for MaxSize100.
In this way, you can compose type-safe.
API
All APIs can be found in the deno doc.
Bundle size
The bundle size adapted to tree-shaking with ESbuild is as follows:
License
Copyright © 2022-present Tomoki Miyauchi.
Released under the MIT license