Initial commit
This commit is contained in:
commit
1baf20f6c0
9 changed files with 825 additions and 0 deletions
157
README.md
Normal file
157
README.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
# Parsec
|
||||
|
||||
A tiny (<1kb gzipped) library for parsing and validating data in TypeScript.
|
||||
|
||||
For more comprehensive type modeling, consider [zod](https://zod.dev)
|
||||
|
||||
## Install
|
||||
|
||||
Configure your environment to be aware of @endeavorance packages:
|
||||
|
||||
```toml
|
||||
# Bunfig.toml
|
||||
[install.scopes]
|
||||
endeavorance = "https://git.astral.camp/api/packages/endeavorance/npm/"
|
||||
```
|
||||
|
||||
Then install the package:
|
||||
|
||||
```sh
|
||||
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`
|
||||
|
||||
|
||||
```ts
|
||||
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`
|
||||
|
||||
```ts
|
||||
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`
|
||||
|
||||
```ts
|
||||
// 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:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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.
|
||||
|
||||
```ts
|
||||
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;
|
||||
}
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue