true-myth
Version:
A library for safe functional programming in JavaScript, with first-class support for TypeScript
267 lines (229 loc) • 9.02 kB
TypeScript
/**
Provides useful integrations between True Myth’s {@linkcode Result} and
{@linkcode Task} types and any library that implements [Standard Schema][ss].
[ss]: https://standardschema.dev
@module
*/
import Result from './result.js';
import Task from './task.js';
/**
The result of parsing data with a synchronous Standard Schema-compliant
schema. Just a {@linkcode Result} whose failure type is always a Standard
Schema `FailureResult`.
*/
export type ParseResult<T> = Result<T, StandardSchemaV1.FailureResult>;
/**
A type to name a parser, most often used in conjunction with {@linkcode
parserFor}. Helpful if you want to use a type you have written to constrain a
Standard Schema-compatible schema, instead of creating the type from the
schema.
## Example
```ts
import { parserFor, type ParserFor } from 'true-myth/standard-schema';
import * as z from 'zod';
interface Person {
name?: string | undefined;
age: number;
}
const personParser: ParserFor<Person> = parserFor(z.object({
name: z.string().optional(),
age: z.number().nonnegative(),
}));
```
*/
export type ParserFor<T> = (data: unknown) => ParseResult<T>;
/**
The result of parsing data with an asynchronous Standard Schema-compliant
schema. Just a {@linkcode Task} whose rejection type is always a Standard
Schema `FailureResult`.
*/
export type ParseTask<T> = Task<T, StandardSchemaV1.FailureResult>;
/**
A type to name an async parser, most often used in conjunction with {@linkcode
asyncParserFor}. Helpful if you want to use a type you have written to
constrain a Standard Schema-compatible async schema, instead of creating the
type from the schema.
## Example
```ts
import { parserFor, type ParserFor } from 'true-myth/standard-schema';
import { type } from 'arktype';
interface Person {
name?: string | undefined;
age: number;
}
const personParser: ParserFor<Person> = parserFor(type({
"name?": "string",
age: "number>=0",
}));
```
*/
export type AsyncParserFor<T> = (data: unknown) => ParseTask<T>;
/**
Create a synchronous parser for unknown data to use with any library that
implements Standard Schema (Zod, Arktype, Valibot, etc.).
The resulting parser will accept `unknown` data and emit a {@linkcode Result},
which will be {@linkcode result.Ok Ok} if the schema successfully validates,
or a {@linkcode result.Err Err} with the {@linkcode StandardSchemaV1.Issue
Issue}s generated by the schema for invalid data.
## Examples
Creating a parser with Zod:
```ts
import { parserFor } from 'true-myth/standard-schema';
import * as z from 'zod';
interface Person {
name?: string | undefined;
age: number;
}
const parsePerson = parserFor(z.object({
name: z.string().optional(),
age: z.number().nonnegative(),
}));
```
Creating a parser with Arktype:
```ts
import { parserFor } from 'true-myth/standard-schema';
import { type } from 'arktype';
interface Person {
name?: string | undefined;
age: number;
}
const parsePerson = parserFor(type({
'name?': 'string',
age: 'number>=0',
}));
```
Other libraries work similarly!
Once you have a parser, you can simply call it with any value and then use the
normal {@linkcode Result} APIs.
```ts
parsePerson({ name: "Old!", age: 112 }).match({
Ok: (person) => {
console.log(`${person.name ?? "someone"} is ${person.age} years old.`);
},
Err: (error) => {
console.error("Something is wrong!", ...error.issues);
}
});
```
## Throws
The parser created by `parserFor` will throw an {@linkcode InvalidAsyncSchema}
error if the schema it was created from produces an async result, i.e., a
`Promise`. Standard Schema is [currently unable][gh] to distinguish between
synchronous and asynchronous parsers due to limitations in Zod.
If you need to handle schemas which may throw, use {@linkcode asyncParserFor}
instead. It will safely lift *all* results into a {@linkcode Task}, which you
can then safely interact with asynchronously as usual.
[gh]: https://github.com/standard-schema/standard-schema/issues/22
*/
export declare function parserFor<S extends StandardSchemaV1>(schema: S): ParserFor<StandardSchemaV1.InferOutput<S>>;
/**
An error thrown when calling a parser created with `parserFor` produces a
`Promise` instance.
*/
declare class InvalidAsyncSchema extends Error {
readonly name = "InvalidAsyncSchema";
constructor();
}
export type { InvalidAsyncSchema };
/**
Create an asynchronous parser for unknown data to use with any library that
implements Standard Schema (Zod, Arktype, Valibot, etc.).
The resulting parser will accept `unknown` data and emit a {@linkcode Task},
which will be {@linkcode task.Resolved Resolved} if the schema successfully
validates, or {@linkcode task.Rejected Rejected} with the {@linkcode
StandardSchemaV1.Issue Issue}s generated by the schema for invalid data.
If passed a parser that produces results synchronously, this function will
lift it into a {@linkcode Task}.
## Examples
With Zod:
```ts
import { asyncParserFor } from 'true-myth/standard-schema';
import * as z from 'zod';
interface Person {
name?: string | undefined;
age: number;
}
const parsePerson = asyncParserFor(z.object({
name: z.optional(z.string()),
// Define an async refinement so we have something to work with. This is a
// placeholder for some kind of *real* async validation you might do!
age: z.number().refine(async (val) => val >= 0),
}));
```
Other libraries that support async validation or transformation work similarly
(but not all libraries support this).
Once you have a parser, you can simply call it with any value and then use the
normal {@linkcode Task} APIs.
```ts
await parsePerson({ name: "Old!", age: 112 }).match({
Resolved: (person) => {
console.log(`${person.name ?? "someone"} is ${person.age} years old.`);
},
Rejected: (error) => {
console.error("Something is wrong!", ...error.issues);
}
});
```
@param schema A Standard Schema-compatible schema that produces a result,
possibly asynchronously.
@returns A {@linkcode Task} that resolves to the output of the schema when it
parses successfully and rejects with the `StandardSchema` `FailureResult`
when it fails to parse.
*/
export declare function asyncParserFor<S extends StandardSchemaV1>(schema: S): AsyncParserFor<StandardSchemaV1.InferOutput<S>>;
/** The Standard Schema interface. */
export interface StandardSchemaV1<Input = unknown, Output = Input> {
/** The Standard Schema properties. */
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
}
export declare namespace StandardSchemaV1 {
/** The Standard Schema properties interface. */
interface Props<Input = unknown, Output = Input> {
/** The version number of the standard. */
readonly version: 1;
/** The vendor name of the schema library. */
readonly vendor: string;
/** Validates unknown input values. */
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
/** Inferred types associated with the schema. */
readonly types?: Types<Input, Output> | undefined;
}
/** The result interface of the validate function. */
type Result<Output> = SuccessResult<Output> | FailureResult;
/** The result interface if validation succeeds. */
interface SuccessResult<Output> {
/** The typed output value. */
readonly value: Output;
/** The non-existent issues. */
readonly issues?: undefined;
}
/** The result interface if validation fails. */
interface FailureResult {
/** The issues of failed validation. */
readonly issues: ReadonlyArray<Issue>;
}
/** The issue interface of the failure output. */
interface Issue {
/** The error message of the issue. */
readonly message: string;
/** The path of the issue, if any. */
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
}
/** The path segment interface of the issue. */
interface PathSegment {
/** The key representing a path segment. */
readonly key: PropertyKey;
}
/** The Standard Schema types interface. */
interface Types<Input = unknown, Output = Input> {
/** The input type of the schema. */
readonly input: Input;
/** The output type of the schema. */
readonly output: Output;
}
/** Infers the input type of a Standard Schema. */
type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];
/** Infers the output type of a Standard Schema. */
type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];
}
//# sourceMappingURL=standard-schema.d.ts.map