parsec/README.md
2025-03-26 14:19:01 -04:00

4.2 KiB

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 the Parse constant which contains a set of functions for parsing and validating data.

Primitives

These functions can be invoked directly, passing in the value to parse.

  • Parse.unknown(value: unknown): unknown
  • Parse.string(value: unknown): string
  • Parse.number(value: unknown): number
  • Parse.boolean(value: unknown): boolean
  • Parse.null(value: unknown): null
  • Parse.undefined(value: unknown): undefined
  • Parse.bigint(value: unknown): bigint
  • Parse.symbol(value: unknown): symbol
const num = Parse.number(42);
const str = Parse.string("hello");
const wontParse = Parse.number("hello"); // throws ParseError

Primitive Values

These functions can be invoked directly and parse primitive values with additional constraints or specific meaning.

  • Parse.int(value: unknown): number
  • Parse.nan(value: unknown): number
const anInteger = Parse.int(42);
const willThrow = Parse.int(42.5); // throws ParseError

const itsNan = Parse.nan(NaN);
const itsNotNan = Parse.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.

  • Parse.arrayOf<T>(parser: Parser<T>): (val: unknown) => T[]
  • Parse.shape<T>(shape: T): (val: unknown) => T
  • Parse.enum<E extends string>(values: readonly E[]): (val: unknown) => E
  • Parse.regex(regex: RegExp): (val: unknown) => string
  • Parse.literal(literal: unknown): (val: unknown) => literal
  • Parse.instanceOf<T>(classDef: Class): (val: unknown) => T
// An array of strings
const arrayOfStrings = Parse.arrayOf(Parse.string)(["hello", "world"]);

// A defined object shape
const personParser = Parse.shape({
  name: Parse.string,
  age: Parse.number,
  isCool: Parse.boolean,
}, "Person");

const person = personParser({
  name: "Alice",
  age: 42,
  isCool: true,
});

// An element of an enum
const MOODS = ["happy", "sad", "angry"] as const;
const moodParser = Parse.enum(MOODS);
const mood = moodParser("happy");

// A string that matches a regex
const allCaps = Parse.regex(/^[A-Z]+$/)("HELLO");

// A literal value
const parseHello = Parse.literal("hello");
const parsedHello = parseHello("hello");
const wontWork = parseHello("world"); // throws ParseError

// An instance
const parseDate = Parse.instanceOf(Date)(new Date());

Modifiers

Use Parse.nullable() and Parse.optional() to wrap parsers to allow for null or undefined respectively:

const numberOrNull = Parse.nullable(Parse.number);
const optionalArrayOfStrings = Parse.optional(Parse.arrayOf(Parse.string)));

Extracting Types from Parsers

When composing parsers to create more complex types, you can use the ReturnType type helper:

const parsePerson = Parse.shape({
  name: Parse.string,
  age: Parse.number,
  isCool: Parse.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;
}