New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@badrap/valita

Package Overview
Dependencies
Maintainers
2
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@badrap/valita - npm Package Compare versions

Comparing version 0.0.3 to 0.0.4

dist/default/index.js

14

package.json
{
"name": "@badrap/valita",
"version": "0.0.3",
"version": "0.0.4",
"description": "A validation & parsing library for TypeScript",
"main": "./dist/index.js",
"main": "./dist/main/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
"node": "./dist/node/index.js",
"default": "./dist/main/index.js"
},
"sideEffects": false,

@@ -14,3 +19,6 @@ "repository": "badrap/valita",

"test": "mocha --require ts-node/register tests/**/*.test.ts",
"build": "rm -rf dist && tsc -p ./tsconfig.build.json",
"build": "rm -rf dist && npm run build:types && npm run build:main && npm run build:node",
"build:types": "tsc -p ./tsconfig.build.json --emitDeclarationOnly --declaration --declarationDir ./dist/types",
"build:main": "tsc -p ./tsconfig.build.json --target es5 --outDir ./dist/default",
"build:node": "tsc -p ./tsconfig.build.json --target es2019 --outDir ./dist/node",
"prepack": "npm run build"

@@ -17,0 +25,0 @@ },

@@ -55,3 +55,3 @@ # @badrap/valita [![tests](https://github.com/badrap/valita/workflows/tests/badge.svg)](https://github.com/badrap/valita/actions?query=workflow%3Atests) [![npm](https://img.shields.io/npm/v/@badrap/valita.svg)](https://www.npmjs.com/package/@badrap/valita)

In fact, you can get your mitts on the this type in the code:
You can use `infer<T>` to get your mitts on the inferred type in your code:

@@ -58,0 +58,0 @@ ```ts

@@ -1,46 +0,71 @@

type IssueCode =
| "invalid_type"
| "invalid_literal_value"
| "invalid_union"
| "missing_key"
| "unrecognized_key";
// This is magic that turns object intersections to nicer-looking types.
type PrettyIntersection<V> = Extract<{ [K in keyof V]: V[K] }, unknown>;
type IssuePath = (string | number)[];
type Literal = string | number | bigint | boolean;
type Key = string | number;
type BaseType =
| "object"
| "array"
| "null"
| "undefined"
| "string"
| "number"
| "bigint"
| "boolean";
type Issue = {
code: IssueCode;
path: IssuePath;
message: string;
};
type I<Code, Extra = unknown> = Readonly<
PrettyIntersection<
Extra & {
code: Code;
path?: Key[];
}
>
>;
function _collectIssues(
ctx: ErrorContext,
path: IssuePath,
issues: Issue[]
): void {
if (ctx.type === "error") {
issues.push({
code: ctx.code,
path: path.slice(),
message: ctx.message,
});
type Issue =
| I<"invalid_type", { expected: BaseType[] }>
| I<"invalid_literal", { expected: Literal[] }>
| I<"missing_key", { key: Key }>
| I<"unrecognized_key", { key: Key }>
| I<"invalid_union", { tree: IssueTree }>;
type IssueTree =
| Readonly<{ code: "prepend"; key: Key; tree: IssueTree }>
| Readonly<{ code: "join"; left: IssueTree; right: IssueTree }>
| Issue;
function _collectIssues(tree: IssueTree, path: Key[], issues: Issue[]): void {
if (tree.code === "join") {
_collectIssues(tree.left, path, issues);
_collectIssues(tree.right, path, issues);
} else if (tree.code === "prepend") {
path.push(tree.key);
_collectIssues(tree.tree, path, issues);
path.pop();
} else {
if (ctx.next) {
_collectIssues(ctx.next, path, issues);
}
path.push(ctx.value);
_collectIssues(ctx.current, path, issues);
path.pop();
issues.push({ ...tree, path: path.concat(tree.path || []) });
}
}
function collectIssues(ctx: ErrorContext): Issue[] {
function collectIssues(tree: IssueTree): Issue[] {
const issues: Issue[] = [];
const path: IssuePath = [];
_collectIssues(ctx, path, issues);
const path: Key[] = [];
_collectIssues(tree, path, issues);
return issues;
}
function orList(list: string[]): string {
const last = list[list.length - 1];
if (list.length < 2) {
return last;
}
return `${list.slice(0, -1).join(", ")} or ${last}`;
}
function formatLiteral(value: Literal): string {
return typeof value === "bigint" ? `${value}n` : JSON.stringify(value);
}
export class ValitaError extends Error {
constructor(private readonly ctx: ErrorContext) {
constructor(private readonly issueTree: IssueTree) {
super();

@@ -52,3 +77,3 @@ Object.setPrototypeOf(this, new.target.prototype);

get issues(): readonly Issue[] {
const issues = collectIssues(this.ctx);
const issues = collectIssues(this.issueTree);
Object.defineProperty(this, "issues", {

@@ -60,60 +85,76 @@ value: issues,

}
get message(): string {
const issue = this.issues[0];
let message = "invalid value";
if (issue.code === "invalid_type") {
message = `expected ${orList(issue.expected)}`;
} else if (issue.code === "invalid_literal") {
message = `expected ${orList(issue.expected.map(formatLiteral))}`;
} else if (issue.code === "missing_key") {
message = `missing key ${formatLiteral(issue.key)}`;
} else if (issue.code === "unrecognized_key") {
message = `unrecognized key ${formatLiteral(issue.key)}`;
}
const path = "." + (issue.path || []).join(".");
return `${issue.code} at ${path} (${message})`;
}
}
function isObject(v: unknown): v is Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v);
function joinIssues(left: IssueTree, right: IssueTree | undefined): IssueTree {
return right ? { code: "join", left, right } : left;
}
type PrettifyObjectType<V> = Extract<{ [K in keyof V]: V[K] }, unknown>;
function prependPath(key: Key, tree: IssueTree): IssueTree {
return { code: "prepend", key, tree };
}
type ErrorContext = Readonly<
| {
ok: false;
type: "path";
value: string | number;
current: ErrorContext;
next?: ErrorContext;
}
| {
ok: false;
type: "error";
code: IssueCode;
message: string;
}
>;
type Ok<T> =
| true
| Readonly<{
ok: true;
code: "ok";
value: T;
}>;
type Result<T> = Ok<T> | ErrorContext;
type Result<T> = Ok<T> | IssueTree;
function err(code: IssueCode, message: string): ErrorContext {
return { ok: false, type: "error", code, message };
function isObject(v: unknown): v is Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
function appendErr(
to: ErrorContext | undefined,
key: string | number,
err: ErrorContext
): ErrorContext {
return {
ok: false,
type: "path",
value: key,
current: err,
next: to,
};
function toTerminals(type: Type): TerminalType[] {
const result: TerminalType[] = [];
type.toTerminals(result);
return result;
}
type Infer<T extends Vx<unknown>> = T extends Vx<infer I> ? I : never;
type Infer<T extends Type> = T extends Type<infer I> ? I : never;
class Vx<T> {
constructor(
private readonly genFunc: () => (v: unknown) => Result<T>,
readonly isOptional: boolean
) {}
const enum FuncMode {
PASS = 0,
STRICT = 1,
STRIP = 2,
}
type Func<T> = (v: unknown, mode: FuncMode) => Result<T>;
get func(): (v: unknown) => Result<T> {
type ParseOptions = {
mode: "passthrough" | "strict" | "strip";
};
abstract class Type<Out = unknown> {
abstract readonly name: string;
abstract genFunc(): Func<Out>;
abstract toTerminals(into: TerminalType[]): void;
get isOptional(): boolean {
const isOptional = toTerminals(this).some((t) => t.name === "undefined");
Object.defineProperty(this, "isOptional", {
value: isOptional,
writable: false,
});
return isOptional;
}
get func(): Func<Out> {
const f = this.genFunc();

@@ -127,21 +168,14 @@ Object.defineProperty(this, "func", {

transform<O>(func: (v: T) => Result<O>): Vx<O> {
const f = this.func;
return new Vx(
() => (v) => {
const r = f(v);
if (r !== true && !r.ok) {
return r;
}
return func(r === true ? (v as T) : r.value);
},
this.isOptional
);
}
parse(v: unknown, options?: Partial<ParseOptions>): Out {
let mode: FuncMode = FuncMode.PASS;
if (options && options.mode === "strict") {
mode = FuncMode.STRICT;
} else if (options && options.mode === "strip") {
mode = FuncMode.STRIP;
}
parse(v: unknown): T {
const r = this.func(v);
const r = this.func(v, mode);
if (r === true) {
return v as T;
} else if (r.ok) {
return v as Out;
} else if (r.code === "ok") {
return r.value;

@@ -153,86 +187,86 @@ } else {

optional(): Vx<T | undefined> {
const f = this.func;
return new Vx(
() => (v) => {
return v === undefined ? true : f(v);
},
true
);
optional(): OptionalType<Out, undefined> {
return new OptionalType(this, undefined);
}
transform<T>(this: Type, func: (v: Out) => Result<T>): TransformType<T> {
return new TransformType(this, func as (v: unknown) => Result<T>);
}
}
type Optionals<T extends Record<string, Vx<unknown>>> = {
type Optionals<T extends Record<string, Type>> = {
[K in keyof T]: undefined extends Infer<T[K]> ? K : never;
}[keyof T];
type UnknownKeys = "passthrough" | "strict" | "strip" | Vx<unknown>;
type ObjectShape = Record<string, Type>;
type VxObjOutput<
T extends Record<string, Vx<unknown>>,
U extends UnknownKeys
> = PrettifyObjectType<
type ObjectOutput<
T extends ObjectShape,
R extends Type | undefined
> = PrettyIntersection<
{ [K in Optionals<T>]?: Infer<T[K]> } &
{ [K in Exclude<keyof T, Optionals<T>>]: Infer<T[K]> } &
(U extends "passthrough" ? { [K: string]: unknown } : unknown) &
(U extends Vx<infer C> ? { [K: string]: C } : unknown)
(R extends Type ? { [K: string]: Infer<R> } : unknown)
>;
class VxObj<
T extends Record<string, Vx<unknown>>,
U extends UnknownKeys
> extends Vx<VxObjOutput<T, U>> {
constructor(private readonly shape: T, private readonly unknownKeys: U) {
super(() => {
const shape = this.shape;
const strip = this.unknownKeys === "strip";
const strict = this.unknownKeys === "strict";
const passthrough = this.unknownKeys === "passthrough";
const catchall =
this.unknownKeys instanceof Vx
? (this.unknownKeys.func as (v: unknown) => Result<unknown>)
: undefined;
class ObjectType<
T extends ObjectShape = ObjectShape,
Rest extends Type | undefined = Type | undefined
> extends Type<ObjectOutput<T, Rest>> {
readonly name = "object";
const keys: string[] = [];
const funcs: ((v: unknown) => Result<unknown>)[] = [];
const required: boolean[] = [];
const knownKeys = Object.create(null);
const shapeTemplate = {} as Record<string, unknown>;
for (const key in shape) {
keys.push(key);
funcs.push(shape[key].func);
required.push(!shape[key].isOptional);
knownKeys[key] = true;
shapeTemplate[key] = undefined;
constructor(readonly shape: T, private readonly restType: Rest) {
super();
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
genFunc(): Func<ObjectOutput<T, Rest>> {
const shape = this.shape;
const rest = this.restType ? this.restType.func : undefined;
const keys: string[] = [];
const funcs: Func<unknown>[] = [];
const required: boolean[] = [];
const knownKeys = Object.create(null);
const shapeTemplate = {} as Record<string, unknown>;
for (const key in shape) {
keys.push(key);
funcs.push(shape[key].func);
required.push(!shape[key].isOptional);
knownKeys[key] = true;
shapeTemplate[key] = undefined;
}
return (obj, mode) => {
if (!isObject(obj)) {
return { code: "invalid_type", expected: ["object"] };
}
const pass = mode === FuncMode.PASS;
const strict = mode === FuncMode.STRICT;
const strip = mode === FuncMode.STRIP;
const template = pass || rest ? obj : shapeTemplate;
return (obj) => {
if (!isObject(obj)) {
return err("invalid_type", "expected an object");
}
let ctx: ErrorContext | undefined = undefined;
let output: Record<string, unknown> = obj;
const template = strict || strip ? shapeTemplate : obj;
if (!passthrough) {
for (const key in obj) {
if (!knownKeys[key]) {
if (strict) {
return err(
"unrecognized_key",
`unrecognized key ${JSON.stringify(key)}`
);
} else if (strip) {
output = { ...template };
break;
} else if (catchall) {
const r = catchall(obj[key]);
if (r !== true) {
if (r.ok) {
if (output === obj) {
output = { ...template };
}
output[key] = r.value;
} else {
ctx = appendErr(ctx, key, r);
let issueTree: IssueTree | undefined = undefined;
let output: Record<string, unknown> = obj;
if (strict || strip || rest) {
for (const key in obj) {
if (!knownKeys[key]) {
if (strict) {
return { code: "unrecognized_key", key };
} else if (strip) {
output = { ...template };
break;
} else if (rest) {
const r = rest(obj[key], mode);
if (r !== true) {
if (r.code === "ok") {
if (output === obj) {
output = { ...template };
}
output[key] = r.value;
} else {
issueTree = joinIssues(prependPath(key, r), issueTree);
}

@@ -243,137 +277,481 @@ }

}
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = obj[key];
}
if (value === undefined && required[i]) {
ctx = appendErr(ctx, key, err("missing_key", `missing key`));
} else {
const r = funcs[i](value);
if (r !== true) {
if (r.ok) {
if (output === obj) {
output = { ...template };
}
output[keys[i]] = r.value;
} else {
ctx = appendErr(ctx, key, r);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = obj[key];
if (value === undefined && required[i]) {
return { code: "missing_key", key };
} else {
const r = funcs[i](value, mode);
if (r !== true) {
if (r.code === "ok") {
if (output === obj) {
output = { ...template };
}
output[keys[i]] = r.value;
} else {
issueTree = joinIssues(prependPath(key, r), issueTree);
}
}
}
}
if (ctx) {
return ctx;
} else if (obj === output) {
return true;
} else {
return { ok: true, value: output as VxObjOutput<T, U> };
}
};
}, false);
if (issueTree) {
return issueTree;
} else if (obj === output) {
return true;
} else {
return { code: "ok", value: output as ObjectOutput<T, Rest> };
}
};
}
passthrough(): VxObj<T, "passthrough"> {
return new VxObj(this.shape, "passthrough");
rest<R extends Type>(restType: R): ObjectType<T, R> {
return new ObjectType(this.shape, restType);
}
strict(): VxObj<T, "strict"> {
return new VxObj(this.shape, "strict");
}
class ArrayType<T extends Type = Type> extends Type<Infer<T>[]> {
readonly name = "array";
constructor(readonly item: T) {
super();
}
strip(): VxObj<T, "strip"> {
return new VxObj(this.shape, "strip");
toTerminals(into: TerminalType[]): void {
into.push(this);
}
catchall<C extends Vx<unknown>>(catchall: C): VxObj<T, C> {
return new VxObj(this.shape, catchall);
}
}
class VxArr<T extends Vx<unknown>> extends Vx<Infer<T>[]> {
constructor(private readonly item: T) {
super(() => {
const func = this.item.func;
return (arr) => {
if (!Array.isArray(arr)) {
return err("invalid_type", "expected an array");
}
let ctx: ErrorContext | undefined = undefined;
let output: Infer<T>[] = arr;
for (let i = 0; i < arr.length; i++) {
const r = func(arr[i]);
if (r !== true) {
if (r.ok) {
if (output === arr) {
output = arr.slice();
}
output[i] = r.value as Infer<T>;
} else {
ctx = appendErr(ctx, i, r);
genFunc(): Func<Infer<T>[]> {
const func = this.item.func;
return (arr, mode) => {
if (!Array.isArray(arr)) {
return { code: "invalid_type", expected: ["array"] };
}
let issueTree: IssueTree | undefined = undefined;
let output: Infer<T>[] = arr;
for (let i = 0; i < arr.length; i++) {
const r = func(arr[i], mode);
if (r !== true) {
if (r.code === "ok") {
if (output === arr) {
output = arr.slice();
}
output[i] = r.value as Infer<T>;
} else {
issueTree = joinIssues(prependPath(i, r), issueTree);
}
}
if (ctx) {
return ctx;
} else if (arr === output) {
return true;
}
if (issueTree) {
return issueTree;
} else if (arr === output) {
return true;
} else {
return { code: "ok", value: output };
}
};
}
}
function toBaseType(v: unknown): BaseType {
const type = typeof v;
if (type !== "object") {
return type as BaseType;
} else if (v === null) {
return "null";
} else if (Array.isArray(v)) {
return "array";
} else {
return type;
}
}
function dedup<T>(arr: T[]): T[] {
const output = [];
const seen = new Set();
for (let i = 0; i < arr.length; i++) {
if (!seen.has(arr[i])) {
output.push(arr[i]);
seen.add(arr[i]);
}
}
return output;
}
function findCommonKeys(rs: ObjectShape[]): string[] {
const map = new Map<string, number>();
rs.forEach((r) => {
for (const key in r) {
map.set(key, (map.get(key) || 0) + 1);
}
});
const result = [] as string[];
map.forEach((count, key) => {
if (count === rs.length) {
result.push(key);
}
});
return result;
}
function createObjectMatchers(
t: { root: Type; terminal: TerminalType }[]
): {
key: string;
isOptional: boolean;
matcher: (
rootValue: unknown,
value: unknown,
mode: FuncMode
) => Result<unknown>;
}[] {
const objects: {
root: Type;
terminal: TerminalType & { name: "object" };
}[] = [];
t.forEach(({ root, terminal }) => {
if (terminal.name === "object") {
objects.push({ root, terminal });
}
});
const shapes = objects.map(({ terminal }) => terminal.shape);
const common = findCommonKeys(shapes);
const discriminants = common.filter((key) => {
const types = new Map<BaseType, unknown[]>();
const literals = new Map<unknown, unknown[]>();
shapes.forEach((shape) => {
toTerminals(shape[key]).forEach((terminal) => {
if (terminal.name === "literal") {
const options = literals.get(terminal.value) || [];
options.push(shape);
literals.set(terminal.value, options);
} else {
return { ok: true, value: output };
const options = types.get(terminal.name) || [];
options.push(shape);
types.set(terminal.name, options);
}
};
}, false);
});
});
literals.forEach((found, value) => {
const options = types.get(toBaseType(value));
if (options) {
options.push(...found);
literals.delete(value);
}
});
let success = true;
literals.forEach((found) => {
if (dedup(found).length > 1) {
success = false;
}
});
types.forEach((found) => {
if (dedup(found).length > 1) {
success = false;
}
});
return success;
});
return discriminants.map((key) => {
const flattened = flatten(
objects.map(({ root, terminal }) => ({
root,
type: terminal.shape[key],
}))
);
return {
key,
matcher: createUnionMatcher(flattened),
isOptional: objects.some(
({ terminal }) => terminal.shape[key].isOptional
),
};
});
}
function createUnionMatcher(
t: { root: Type; terminal: TerminalType }[]
): (rootValue: unknown, value: unknown, mode: FuncMode) => Result<unknown> {
const literals = new Map<unknown, Type[]>();
const types = new Map<BaseType, Type[]>();
const allTypes = new Set<BaseType>();
t.forEach(({ root, terminal }) => {
if (terminal.name === "literal") {
const roots = literals.get(terminal.value) || [];
roots.push(root);
literals.set(terminal.value, roots);
allTypes.add(toBaseType(terminal.value));
} else {
const roots = types.get(terminal.name) || [];
roots.push(root);
types.set(terminal.name, roots);
allTypes.add(terminal.name);
}
});
literals.forEach((vxs, value) => {
const options = types.get(toBaseType(value));
if (options) {
options.push(...vxs);
literals.delete(value);
}
});
types.forEach((roots, type) => types.set(type, dedup(roots)));
literals.forEach((roots, value) => literals.set(value, dedup(roots)));
const expectedTypes: BaseType[] = [];
allTypes.forEach((type) => expectedTypes.push(type));
const expectedLiterals: Literal[] = [];
literals.forEach((_, value) => {
expectedLiterals.push(value as Literal);
});
const invalidType: Issue = {
code: "invalid_type",
expected: expectedTypes,
};
const invalidLiteral: Issue = {
code: "invalid_literal",
expected: expectedLiterals,
};
return (rootValue, value, mode) => {
const type = toBaseType(value);
if (!allTypes.has(type)) {
return invalidType;
}
const options = literals.get(value) || types.get(type);
if (options) {
let issueTree: IssueTree | undefined;
for (let i = 0; i < options.length; i++) {
const r = options[i].func(rootValue, mode);
if (r === true || r.code === "ok") {
return r;
}
issueTree = joinIssues(r, issueTree);
}
if (issueTree) {
if (options.length > 1) {
return { code: "invalid_union", tree: issueTree };
}
return issueTree;
}
}
return invalidLiteral;
};
}
function flatten(
t: { root: Type; type: Type }[]
): { root: Type; terminal: TerminalType }[] {
const result: { root: Type; terminal: TerminalType }[] = [];
t.forEach(({ root, type }) =>
toTerminals(type).forEach((terminal) => {
result.push({ root, terminal });
})
);
return result;
}
class UnionType<T extends Type[] = Type[]> extends Type<Infer<T[number]>> {
readonly name = "union";
constructor(readonly options: T) {
super();
}
toTerminals(into: TerminalType[]): void {
this.options.forEach((o) => o.toTerminals(into));
}
genFunc(): Func<Infer<T[number]>> {
const flattened = flatten(
this.options.map((root) => ({ root, type: root }))
);
const objects = createObjectMatchers(flattened);
const base = createUnionMatcher(flattened);
return (v, mode) => {
if (objects.length > 0 && isObject(v)) {
const item = objects[0];
const value = v[item.key];
if (value === undefined && !item.isOptional && !(item.key in v)) {
return { code: "missing_key", key: item.key };
}
const r = item.matcher(v, value, mode);
if (r === true || r.code === "ok") {
return r as Result<Infer<T[number]>>;
}
return prependPath(item.key, r);
}
return base(v, v, mode) as Result<Infer<T[number]>>;
};
}
}
function number(): Vx<number> {
const e = err("invalid_type", "expected a number");
return new Vx(() => (v) => (typeof v === "number" ? true : e), false);
class NumberType extends Type<number> {
readonly name = "number";
genFunc(): Func<number> {
const issue: Issue = { code: "invalid_type", expected: ["number"] };
return (v, _mode) => (typeof v === "number" ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function bigint(): Vx<bigint> {
const e = err("invalid_type", "expected a bigint");
return new Vx(() => (v) => (typeof v === "bigint" ? true : e), false);
class StringType extends Type<number> {
readonly name = "string";
genFunc(): Func<number> {
const issue: Issue = { code: "invalid_type", expected: ["string"] };
return (v, _mode) => (typeof v === "string" ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function string(): Vx<string> {
const e = err("invalid_type", "expected a string");
return new Vx(() => (v) => (typeof v === "string" ? true : e), false);
class BigIntType extends Type<number> {
readonly name = "bigint";
genFunc(): Func<number> {
const issue: Issue = { code: "invalid_type", expected: ["bigint"] };
return (v, _mode) => (typeof v === "bigint" ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function boolean(): Vx<boolean> {
const e = err("invalid_type", "expected a boolean");
return new Vx(() => (v) => (typeof v === "boolean" ? true : e), false);
class BooleanType extends Type<number> {
readonly name = "boolean";
genFunc(): Func<number> {
const issue: Issue = { code: "invalid_type", expected: ["boolean"] };
return (v, _mode) => (typeof v === "boolean" ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function object<T extends Record<string, Vx<unknown>>>(
obj: T
): VxObj<T, "strict"> {
return new VxObj(obj, "strict");
class UndefinedType extends Type<undefined> {
readonly name = "undefined";
genFunc(): Func<undefined> {
const issue: Issue = { code: "invalid_type", expected: ["undefined"] };
return (v, _mode) => (v === undefined ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function array<T extends Vx<unknown>>(item: T): VxArr<T> {
return new VxArr(item);
class NullType extends Type<null> {
readonly name = "null";
genFunc(): Func<null> {
const issue: Issue = { code: "invalid_type", expected: ["null"] };
return (v, _mode) => (v === null ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function literal<T extends string | number | boolean | bigint>(
value: T
): Vx<T> {
const exp = typeof value === "bigint" ? `${value}n` : JSON.stringify(value);
const e = err("invalid_literal_value", `expected ${exp}`);
return new Vx(() => (v) => (v === value ? true : e), false);
class LiteralType<Out extends Literal = Literal> extends Type<Out> {
readonly name = "literal";
constructor(readonly value: Out) {
super();
}
genFunc(): Func<Out> {
const value = this.value;
const issue: Issue = { code: "invalid_literal", expected: [value] };
return (v, _) => (v === value ? true : issue);
}
toTerminals(into: TerminalType[]): void {
into.push(this);
}
}
function undefined_(): Vx<undefined> {
const e = err("invalid_type", "expected undefined");
return new Vx(() => (v) => (v === undefined ? true : e), true);
class OptionalType<Out, Default> extends Type<Out | Default> {
readonly name = "optional";
constructor(
private readonly type: Type<Out>,
private readonly defaultValue: Default
) {
super();
}
genFunc(): Func<Out | Default> {
const func = this.type.func;
const defaultResult =
this.defaultValue === undefined
? true
: ({ code: "ok", value: this.defaultValue } as const);
return (v, mode) => (v === undefined ? defaultResult : func(v, mode));
}
toTerminals(into: TerminalType[]): void {
into.push(undefined_());
this.type.toTerminals(into);
}
}
function null_(): Vx<null> {
const e = err("invalid_type", "expected null");
return new Vx(() => (v) => (v === null ? true : e), false);
}
function union<T extends Vx<unknown>[]>(...args: T): Vx<Infer<T[number]>> {
return new Vx(() => {
const error = err("invalid_union", "invalid union");
const funcs = args.map((arg) => arg.func);
return (v) => {
for (let i = 0; i < args.length; i++) {
const r = funcs[i](v);
if (r === true || r.ok) {
return r as Result<Infer<T[number]>>;
}
class TransformType<Out> extends Type<Out> {
readonly name = "transform";
constructor(
readonly transformed: Type,
private readonly transformFunc: (v: unknown) => Result<Out>
) {
super();
}
genFunc(): Func<Out> {
const f = this.transformed.func;
const t = this.transformFunc;
return (v, mode) => {
const r = f(v, mode);
if (r !== true && r.code !== "ok") {
return r;
}
return error;
return t(r === true ? v : r.value);
};
}, false);
}
toTerminals(into: TerminalType[]): void {
this.transformed.toTerminals(into);
}
}
function number(): NumberType {
return new NumberType();
}
function bigint(): BigIntType {
return new BigIntType();
}
function string(): StringType {
return new StringType();
}
function boolean(): BooleanType {
return new BooleanType();
}
function undefined_(): UndefinedType {
return new UndefinedType();
}
function null_(): NullType {
return new NullType();
}
function object<T extends Record<string, Type>>(
obj: T
): ObjectType<T, undefined> {
return new ObjectType(obj, undefined);
}
function array<T extends Type>(item: T): ArrayType<T> {
return new ArrayType(item);
}
function literal<T extends Literal>(value: T): LiteralType<T> {
return new LiteralType(value);
}
function union<T extends Type[]>(...options: T): UnionType<T> {
return new UnionType(options);
}
type TerminalType =
| StringType
| NumberType
| BigIntType
| BooleanType
| UndefinedType
| NullType
| ObjectType
| ArrayType
| LiteralType;
export {

@@ -380,0 +758,0 @@ number,

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc