Package Exports
- reflect-types
- reflect-types/lib/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (reflect-types) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Reflect Types
Reflect types allows you to work with types both at compile time and at runtime.
Think Zod, but with the useful feature of being able to analyse and traverse the types, not just use them for validation.
Why would I want to use this?
- You want to declare and validate some JavaScript objects at the same time
- You want to do some metaprogramming like in C++
- You want to extend the system with your own types easily and safely
- You want your users to specify types of data and be able to inspect what they return
Examples
Define an object type and validate some data with it
import { type Infer, types as t, validate } from "reflect-types"
const personT = t.object({
id: t.uuid4(),
fullName: t.string(),
email: t.email(),
dateOfBirth: t.date(),
});
type Person = Infer<typeof personT>;
const person1: Person = {
id: '22ba434a-c662-4cc9-8a05-5cf1c7c90fd7',
fullName: 'James Smith',
email: 'james.smith@gmail.com',
dateOfBirth: new Date('8/23/1997'),
}
const [errors, result] = validate(person1, personT);
if (errors.length > 0) {
for (const error of errors) {
console.log(error);
}
} else {
// No errors, yay! Now, `result` may be stored in the database.
}Inspect a type in order to infer whether it is nullable
This is where this library really shines.
import { type Type } from "reflect-types"
function isNullable(type: Type): boolean {
switch (type.kind) {
case 'literal':
return type.value === null;
case 'null':
return true;
case 'union':
return (type.types as Type[]).some(isNullable);
default:
return false;
}
}
// @ts-expect-error
function fetchSomeTypeSomehow(): Type {
// TODO
}
const type = fetchSomeTypeSomehow();
console.log(
isNullable(type)
? `The computed type is nullable.`
: `The computed type is not nullable.`);Installation
Just install reflect-types using your favorite package manager, e.g.:
npm install reflect-typesAPI Reference
Most examples in this section require the following import to be present:
import { types as t } from "reflect-types";Infer<T>
Infer the value of a certain reflected type.
Use the typeof keyword to lift the reflected type to the TypeScript type
level.
Example:
import { type Infer, types as t } from "reflect-types";
const objT = t.object({
foo: t.number(),
bar: t.string(),
});
type Obj = Infer<typeof objT>;
const fo = {
foo: 1,
bar: "hello",
} satisfies Obj;t.undefined()
Represents the TypeScript undefined type.
t.null_()
Represents the TypeScript null type.
Notice the trailing _ to avoid conflict with JavaScript's built-in null keyword.
t.boolean()
Represents the TypeScript boolean type.
t.number()
Represents the TypeScript number type.
t.string()
Represents the TypeScript string type.
t.literal(value)
Represents a TypeScript literal type.
Examples:
import { type Infer, types as t } from "reflect-types";
const trueT = t.literal(true);
const foobarT = t.literal("foobar");
const theAnswerT = t.literal(42);
const x1: Infer<typeof trueT> = true; // ok
// @ts-expect-error
const x2: Infer<typeof trueT> = false; // type error
const x3: Infer<typeof foobarT> = "foobar"; // ok
// @ts-expect-error
const x4: Infer<typeof foobarT> = "blablabla"; // type error
const x5: Infer<typeof theAnswerT> = 42; // ok
// @ts-expect-error
const x6: Infer<typeof theAnswerT> = 3; // type errort.date()
Represents a plain Date object in JavaScript.
t.nullable(inner)
Represents a union of the type of inner and a null literal type.
import { types as t } from "reflect-types";
const maybeStringT = t.nullable(t.string());t.array(elementType)
Represents a TypeScript Array<T> where T is the inferred type of
elementType.
import { types as t } from "reflect-types";
const numbersT = t.array(t.number());
const personsT = t.array(t.object({
fullName: t.string(),
dateOfBirth: t.date(),
email: t.email(),
}));t.object(objLiteral)
Represents an object literal at the type level, where the keys and values are
specified in objLiteral.
import { types as t } from "reflect-types";
const loginT = t.object({
username: t.string(),
password: t.string(),
rememberMe: t.boolean(),
});t.optional(inner)
[!WARNING]
This constructor must only be used inside
t.object().
Marks a field of the object being defined as optional.
inner is a reflected type that represents the type of the field.
import { types as t } from "reflect-types";
const productT = t.object({
title: t.string(),
description: t.optional(t.string()),
price: t.boolean(),
});t.union(elementTypes)
Represents a TypeScript union type.
elementTypes must be an array of reflected types, like so:
import { types as t } from "reflect-types";
const stringOrNumberT = t.union([
t.string(),
t.number(),
]);t.callable(params, returns)
Represents a function signature.
Note that due to a limitation in TypeScript you need to add as const to the
parameter type array, like so:
import { type Infer, types as t } from "reflect-types";
const stringLengthSig = t.callable(
[ t.string() ] as const,
t.number()
);
const getLength: Infer<typeof stringLengthSig> = x => x.length;Advanced Usage
Extending the type system
The type system is flexible enough to be extended with user-defined types.
Here is a code snippet that create a new type for RGB-colors.
import { type TypeBase } from "reflect-types"
type RGB = [r: number, g: number, b: number];
export class RGBType implements TypeBase {
// Give your type an unique name. Names should be lowercase and use dashes
// when consisting of multiple words.
readonly kind = 'rgb';
// This resolves to the actual TypeScript type of value that is held by this type.
__type!: RGB;
}
declare module "reflect-types" {
interface Types {
rgb: RGBType,
}
}
export function rgb(): RGBType {
return new RGBType();
}In the code above, we first define a TypeScript type for the values we wish to support, in this case RGB.
Next, we create the actual class that will represent these values in
reflect-types. Usually these have the suffix Type. They implement
TypeBase, a minimal interface that every type should adhere to.
The fields kind and __type indicate the tag and the TypeScript type,
respectively. Next, the declare module-directive ensures that when a user
specifies our new type somewhere, it is actually accepted by e.g.
types.object().
Finally, we create a simple constructor for our type, making the class
transparent and avoiding the use of new.
License
This project is licensed under the MIT license. See LICENSE.txt for more information.