3 ms·
I made a similar thing back in time, unfortunately not a public code though. The idea itself is simple, it was approximately something like this: const kin
by lifthrasiir 4y ago
I made a similar thing back in time, unfortunately not a public code though. The idea itself is simple, it was approximately something like this:
const kind = Symbol();
type Spec = "unknown" | "string" | "number" | ... |
{ [kind]: "optional"; spec: Spec } |
{ [kind]: "array"; element: Spec } |
{ [P in string | number | symbol]: Spec } | ...;
function optional<S extends Spec>(spec: S): { [kind]: "optional"; spec: S } { ... }
function arrayOf<E extends Spec>(element: E): { [kind]: "array"; element: E } { ... }
// and so on, you get the idea. these helper functions exist so that
// you can write `optional(...)` or `arrayOf(...)` instead of the full specification
type Validated<S> =
S extends "unknown" ? unknown :
...
S extends { [kind]: "array"; element: infer E } ? Array<Validated<E>> :
S extends { [P in string | number | symbol]: Spec } ? { [P in keyof S]: Validated<S[P]> } :
...;
function validate<S extends Spec>(value: unknown, spec: S): asserts value is Validated<S> {
if (typeof spec === "string") {
switch (spec) { ... }
} else {
switch (spec[kind]) { ... }
}
}
The actual implementation of course had to take care of implicit nulls and other caveats of the TypeScript type system.