src | ||
.gitignore | ||
biome.json | ||
bun.lock | ||
LICENSE | ||
package.json | ||
README.md | ||
tsconfig.json |
Parsec
A tiny (<1kb gzipped) library for parsing and validating data in TypeScript.
For more comprehensive type modeling, consider zod
Install
Configure your environment to be aware of @endeavorance packages:
# Bunfig.toml
[install.scopes]
endeavorance = "https://git.astral.camp/api/packages/endeavorance/npm/"
Then install the package:
bun add @endeavorance/parsec
API
Parsec exports p
which contains a set of functions for parsing and
validating data.
Primitives
These functions can be invoked directly, passing in the value to p.
p.unknown(value: unknown): unknown
p.string(value: unknown): string
p.number(value: unknown): number
p.boolean(value: unknown): boolean
p.null(value: unknown): null
p.undefined(value: unknown): undefined
p.bigint(value: unknown): bigint
p.symbol(value: unknown): symbol
const num = p.number(42);
const str = p.string("hello");
const wontParse = p.number("hello"); // throws ParseError
Primitive Values
These functions can be invoked directly and parse primitive values with additional constraints or specific meaning.
p.int(value: unknown): number
p.nan(value: unknown): number
const anInteger = p.int(42);
const willThrow = p.int(42.5); // throws ParseError
const itsNan = p.nan(NaN);
const itsNotNan = p.nan(42); // throws ParseError
Parser Creators
These functions return a new parser function that can be used to parse values.
These functions each take an optional second parameter: a human-readable string identifying the shape that this parser is for. This is useful for debugging but is not required and will default to an generic version of the parser.
p.arrayOf<T>(parser: Parser<T>): (val: unknown) => T[]
p.shape<T>(shape: T): (val: unknown) => T
p.enum<E extends string>(values: readonly E[]): (val: unknown) => E
p.regex(regex: RegExp): (val: unknown) => string
p.literal(literal: unknown): (val: unknown) => literal
p.instanceOf<T>(classDef: Class): (val: unknown) => T
p.recordOf<K, V>(keyParser: Parser<K>, valParser: Parser<V>): (val: unknown) => Record<K, V>
p.oneOf<T extends Parser<unknown>[]>: (val: unknown) => T
p.guarded<T>(typeguard: (val: unknown) => val is T) => (val: unknown) => T
// An array of strings
const arrayOfStrings = p.arrayOf(p.string)(["hello", "world"]);
// A defined object shape
const parsePerson = p.shape({
name: p.string,
age: p.number,
isCool: p.boolean,
}, "Person");
const person = parsePerson({
name: "Alice",
age: 42,
isCool: true,
});
// An element of an enum
const MOODS = ["happy", "sad", "angry"] as const;
const moodParser = p.enum(MOODS);
const mood = moodParser("happy");
// A string that matches a regex
const allCaps = p.regex(/^[A-Z]+$/)("HELLO");
// A literal value
const parseHello = p.literal("hello");
const parsedHello = parseHello("hello");
const wontWork = parseHello("world"); // throws ParseError
// An instance
const parseDate = p.instanceOf(Date)(new Date());
Modifiers
Use p.nullable()
and p.optional()
to wrap parsers to allow for null
or undefined
respectively:
const numberOrNull = p.nullable(p.number);
const optionalArrayOfStrings = p.optional(p.arrayOf(p.string)));
Both nullable
and optional
can be passed true
as a second parameter
to allow parsing null
or undefined
into the respective empty value.
For example, if you want to allow parsing a field that may be null
or undefined
,
but want to return only null
if either is parsed, you can use:
const nullifiedVal = p.nullable(undefined, true); // parses to null
Defaulting and Transforming
Use p.defaulted()
and p.transformed()
to wrap parsers and provide
a default value or value transformer
const parsedWithDefault = p.defaulted(p.number, 42)(undefined); // 42
const asNum = p.transformed(p.string, (val) => parseInt(val, 10))("42"); // 42
Extracting Types from Parsers
When composing parsers to create more complex types, you can use the ReturnType
type helper:
const parsePerson = p.shape({
name: p.string,
age: p.number,
isCool: p.boolean,
});
type Person = ReturnType<typeof parsePerson>;
Errors
Parsec will throw ParseError
errors when parsing fails.
ParseError
objects have a string shape
property which contains the
expected type of the value.
Branding
Parsec exports a helper type, Brand<T, B>
which brands type T with the
label provided in B. This is useful for creating custom types that are
primitive types under the hood but have a different name.
import type { Brand } from "@endeavorance/parsec";
type NegativeInteger = Brand<number, "NegativeInteger">;
function parseInteger(val: unknown): Integer {
if (typeof val !== "number" || !Number.isInteger(val) || val > 0) {
throw new ParseTypeMismatch("Integer", val);
}
return val as NegativeInteger;
}