veffect
Version:
powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha
775 lines (698 loc) • 19.1 kB
text/typescript
/**
* @since 1.0.0
*/
import type * as Either from "./Either.js"
import * as internal from "./internal/matcher.js"
import type * as Option from "./Option.js"
import type { Pipeable } from "./Pipeable.js"
import * as Predicate from "./Predicate.js"
import type { Contravariant, Covariant, UnionToIntersection } from "./Types.js"
import type { Unify } from "./Unify.js"
/**
* @category type ids
* @since 1.0.0
*/
export const MatcherTypeId: unique symbol = internal.TypeId
/**
* @category type ids
* @since 1.0.0
*/
export type MatcherTypeId = typeof MatcherTypeId
/**
* @category model
* @since 1.0.0
*/
export type Matcher<Input, Filters, RemainingApplied, Result, Provided> =
| TypeMatcher<Input, Filters, RemainingApplied, Result>
| ValueMatcher<Input, Filters, RemainingApplied, Result, Provided>
/**
* @category model
* @since 1.0.0
*/
export interface TypeMatcher<in Input, out Filters, out Remaining, out Result> extends Pipeable {
readonly _tag: "TypeMatcher"
readonly [MatcherTypeId]: {
readonly _input: Contravariant<Input>
readonly _filters: Covariant<Filters>
readonly _remaining: Covariant<Remaining>
readonly _result: Covariant<Result>
}
readonly cases: ReadonlyArray<Case>
add<I, R, RA, A>(_case: Case): TypeMatcher<I, R, RA, A>
}
/**
* @category model
* @since 1.0.0
*/
export interface ValueMatcher<in Input, Filters, out Remaining, out Result, Provided> extends Pipeable {
readonly _tag: "ValueMatcher"
readonly [MatcherTypeId]: {
readonly _input: Contravariant<Input>
readonly _filters: Covariant<Filters>
readonly _result: Covariant<Result>
}
readonly provided: Provided
readonly value: Either.Either<Provided, Remaining>
add<I, R, RA, A, Pr>(_case: Case): ValueMatcher<I, R, RA, A, Pr>
}
/**
* @category model
* @since 1.0.0
*/
export type Case = When | Not
/**
* @category model
* @since 1.0.0
*/
export interface When {
readonly _tag: "When"
guard(u: unknown): boolean
evaluate(input: unknown): any
}
/**
* @category model
* @since 1.0.0
*/
export interface Not {
readonly _tag: "Not"
guard(u: unknown): boolean
evaluate(input: unknown): any
}
/**
* @category constructors
* @since 1.0.0
*/
export const type: <I>() => Matcher<I, Types.Without<never>, I, never, never> = internal.type
/**
* @category constructors
* @since 1.0.0
*/
export const value: <const I>(
i: I
) => Matcher<I, Types.Without<never>, I, never, I> = internal.value
/**
* @category constructors
* @since 1.0.0
*/
export const valueTags: <
const I,
P extends
& {
readonly [Tag in Types.Tags<"_tag", I> & string]: (
_: Extract<I, { readonly _tag: Tag }>
) => any
}
& { readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", I>>]: never }
>(fields: P) => (input: I) => Unify<ReturnType<P[keyof P]>> = internal.valueTags
/**
* @category constructors
* @since 1.0.0
*/
export const typeTags: <I>() => <
P extends
& {
readonly [Tag in Types.Tags<"_tag", I> & string]: (
_: Extract<I, { readonly _tag: Tag }>
) => any
}
& { readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", I>>]: never }
>(fields: P) => (input: I) => Unify<ReturnType<P[keyof P]>> = internal.typeTags
/**
* @category combinators
* @since 1.0.0
*/
export const when: <
R,
const P extends Types.PatternPrimitive<R> | Types.PatternBase<R>,
Fn extends (_: Types.WhenMatch<R, P>) => unknown
>(
pattern: P,
f: Fn
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Types.PForExclude<P>>,
Types.ApplyFilters<I, Types.AddWithout<F, Types.PForExclude<P>>>,
A | ReturnType<Fn>,
Pr
> = internal.when as any
/**
* @category combinators
* @since 1.0.0
*/
export const whenOr: <
R,
const P extends ReadonlyArray<
Types.PatternPrimitive<R> | Types.PatternBase<R>
>,
Fn extends (_: Types.WhenMatch<R, P[number]>) => unknown
>(
...args: [...patterns: P, f: Fn]
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Types.PForExclude<P[number]>>,
Types.ApplyFilters<I, Types.AddWithout<F, Types.PForExclude<P[number]>>>,
A | ReturnType<Fn>,
Pr
> = internal.whenOr as any
/**
* @category combinators
* @since 1.0.0
*/
export const whenAnd: <
R,
const P extends ReadonlyArray<
Types.PatternPrimitive<R> | Types.PatternBase<R>
>,
Fn extends (_: Types.WhenMatch<R, Types.ArrayToIntersection<P>>) => unknown
>(
...args: [...patterns: P, f: Fn]
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Types.PForExclude<Types.ArrayToIntersection<P>>>,
Types.ApplyFilters<
I,
Types.AddWithout<F, Types.PForExclude<Types.ArrayToIntersection<P>>>
>,
A | ReturnType<Fn>,
Pr
> = internal.whenAnd as any
/**
* @category combinators
* @since 1.0.0
*/
export const discriminator: <D extends string>(
field: D
) => <R, P extends Types.Tags<D, R> & string, B>(
...pattern: [
first: P,
...values: Array<P>,
f: (_: Extract<R, Record<D, P>>) => B
]
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Extract<R, Record<D, P>>>,
Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<D, P>>>>,
B | A,
Pr
> = internal.discriminator
/**
* @category combinators
* @since 1.0.0
*/
export const discriminatorStartsWith: <D extends string>(
field: D
) => <R, P extends string, B>(
pattern: P,
f: (_: Extract<R, Record<D, `${P}${string}`>>) => B
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Extract<R, Record<D, `${P}${string}`>>>,
Types.ApplyFilters<
I,
Types.AddWithout<F, Extract<R, Record<D, `${P}${string}`>>>
>,
B | A,
Pr
> = internal.discriminatorStartsWith as any
/**
* @category combinators
* @since 1.0.0
*/
export const discriminators: <D extends string>(
field: D
) => <
R,
P extends
& {
readonly [Tag in Types.Tags<D, R> & string]?:
| ((_: Extract<R, Record<D, Tag>>) => any)
| undefined
}
& { readonly [Tag in Exclude<keyof P, Types.Tags<D, R>>]: never }
>(
fields: P
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Extract<R, Record<D, keyof P>>>,
Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<D, keyof P>>>>,
A | ReturnType<P[keyof P] & {}>,
Pr
> = internal.discriminators
/**
* @category combinators
* @since 1.0.0
*/
export const discriminatorsExhaustive: <D extends string>(
field: D
) => <
R,
P extends
& {
readonly [Tag in Types.Tags<D, R> & string]: (
_: Extract<R, Record<D, Tag>>
) => any
}
& { readonly [Tag in Exclude<keyof P, Types.Tags<D, R>>]: never }
>(
fields: P
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => [Pr] extends [never] ? (u: I) => Unify<A | ReturnType<P[keyof P]>>
: Unify<A | ReturnType<P[keyof P]>> = internal.discriminatorsExhaustive
/**
* @category combinators
* @since 1.0.0
*/
export const tag: <R, P extends Types.Tags<"_tag", R> & string, B>(
...pattern: [
first: P,
...values: Array<P>,
f: (_: Extract<R, Record<"_tag", P>>) => B
]
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Extract<R, Record<"_tag", P>>>,
Types.ApplyFilters<I, Types.AddWithout<F, Extract<R, Record<"_tag", P>>>>,
B | A,
Pr
> = internal.tag
/**
* @category combinators
* @since 1.0.0
*/
export const tagStartsWith: <R, P extends string, B>(
pattern: P,
f: (_: Extract<R, Record<"_tag", `${P}${string}`>>) => B
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Extract<R, Record<"_tag", `${P}${string}`>>>,
Types.ApplyFilters<
I,
Types.AddWithout<F, Extract<R, Record<"_tag", `${P}${string}`>>>
>,
B | A,
Pr
> = internal.tagStartsWith as any
/**
* @category combinators
* @since 1.0.0
*/
export const tags: <
R,
P extends
& {
readonly [Tag in Types.Tags<"_tag", R> & string]?:
| ((_: Extract<R, Record<"_tag", Tag>>) => any)
| undefined
}
& { readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", R>>]: never }
>(
fields: P
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddWithout<F, Extract<R, Record<"_tag", keyof P>>>,
Types.ApplyFilters<
I,
Types.AddWithout<F, Extract<R, Record<"_tag", keyof P>>>
>,
A | ReturnType<P[keyof P] & {}>,
Pr
> = internal.tags
/**
* @category combinators
* @since 1.0.0
*/
export const tagsExhaustive: <
R,
P extends
& {
readonly [Tag in Types.Tags<"_tag", R> & string]: (
_: Extract<R, Record<"_tag", Tag>>
) => any
}
& { readonly [Tag in Exclude<keyof P, Types.Tags<"_tag", R>>]: never }
>(
fields: P
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => [Pr] extends [never] ? (u: I) => Unify<A | ReturnType<P[keyof P]>>
: Unify<A | ReturnType<P[keyof P]>> = internal.tagsExhaustive
/**
* @category combinators
* @since 1.0.0
*/
export const not: <
R,
const P extends Types.PatternPrimitive<R> | Types.PatternBase<R>,
Fn extends (_: Types.NotMatch<R, P>) => unknown
>(
pattern: P,
f: Fn
) => <I, F, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => Matcher<
I,
Types.AddOnly<F, Types.WhenMatch<R, P>>,
Types.ApplyFilters<I, Types.AddOnly<F, Types.WhenMatch<R, P>>>,
A | ReturnType<Fn>,
Pr
> = internal.not as any
/**
* @category predicates
* @since 1.0.0
*/
export const nonEmptyString: SafeRefinement<string, never> = internal.nonEmptyString
/**
* @category predicates
* @since 1.0.0
*/
export const is: <
Literals extends ReadonlyArray<string | number | bigint | boolean | null>
>(...literals: Literals) => Predicate.Refinement<unknown, Literals[number]> = internal.is
/**
* @category predicates
* @since 1.0.0
*/
export const string: Predicate.Refinement<unknown, string> = Predicate.isString
/**
* @category predicates
* @since 1.0.0
*/
export const number: Predicate.Refinement<unknown, number> = Predicate.isNumber
/**
* @category predicates
* @since 1.0.0
*/
export const any: SafeRefinement<unknown, any> = internal.any
/**
* @category predicates
* @since 1.0.0
*/
export const defined: <A>(u: A) => u is A & {} = internal.defined
/**
* @category predicates
* @since 1.0.0
*/
export const boolean: Predicate.Refinement<unknown, boolean> = Predicate.isBoolean
const _undefined: Predicate.Refinement<unknown, undefined> = Predicate.isUndefined
export {
/**
* @category predicates
* @since 1.0.0
*/
_undefined as undefined
}
const _null: Predicate.Refinement<unknown, null> = Predicate.isNull
export {
/**
* @category predicates
* @since 1.0.0
*/
_null as null
}
/**
* @category predicates
* @since 1.0.0
*/
export const bigint: Predicate.Refinement<unknown, bigint> = Predicate.isBigInt
/**
* @category predicates
* @since 1.0.0
*/
export const symbol: Predicate.Refinement<unknown, symbol> = Predicate.isSymbol
/**
* @category predicates
* @since 1.0.0
*/
export const date: Predicate.Refinement<unknown, Date> = Predicate.isDate
/**
* @category predicates
* @since 1.0.0
*/
export const record: Predicate.Refinement<unknown, { [x: string | symbol]: unknown }> = Predicate.isRecord
/**
* @category predicates
* @since 1.0.0
*/
export const instanceOf: <A extends abstract new(...args: any) => any>(
constructor: A
) => SafeRefinement<InstanceType<A>, never> = internal.instanceOf
/**
* @category predicates
* @since 1.0.0
*/
export const instanceOfUnsafe: <A extends abstract new(...args: any) => any>(
constructor: A
) => SafeRefinement<InstanceType<A>, InstanceType<A>> = internal.instanceOf
/**
* @category conversions
* @since 1.0.0
*/
export const orElse: <RA, B>(
f: (b: RA) => B
) => <I, R, A, Pr>(
self: Matcher<I, R, RA, A, Pr>
) => [Pr] extends [never] ? (input: I) => Unify<B | A> : Unify<B | A> = internal.orElse
/**
* @category conversions
* @since 1.0.0
*/
export const orElseAbsurd: <I, R, RA, A, Pr>(
self: Matcher<I, R, RA, A, Pr>
) => [Pr] extends [never] ? (input: I) => Unify<A> : Unify<A> = internal.orElseAbsurd
/**
* @category conversions
* @since 1.0.0
*/
export const either: <I, F, R, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => [Pr] extends [never] ? (input: I) => Either.Either<Unify<A>, R>
: Either.Either<Unify<A>, R> = internal.either
/**
* @category conversions
* @since 1.0.0
*/
export const option: <I, F, R, A, Pr>(
self: Matcher<I, F, R, A, Pr>
) => [Pr] extends [never] ? (input: I) => Option.Option<Unify<A>>
: Option.Option<Unify<A>> = internal.option
/**
* @category conversions
* @since 1.0.0
*/
export const exhaustive: <I, F, A, Pr>(
self: Matcher<I, F, never, A, Pr>
) => [Pr] extends [never] ? (u: I) => Unify<A> : Unify<A> = internal.exhaustive
/**
* @since 1.0.0
* @category type ids
*/
export const SafeRefinementId = Symbol.for("effect/SafeRefinement")
/**
* @since 1.0.0
* @category type ids
*/
export type SafeRefinementId = typeof SafeRefinementId
/**
* @category model
* @since 1.0.0
*/
export interface SafeRefinement<in A, out R = A> {
readonly [SafeRefinementId]: (a: A) => R
}
const Fail = Symbol.for("effect/Fail")
type Fail = typeof Fail
/**
* @since 1.0.0
*/
export declare namespace Types {
/**
* @since 1.0.0
*/
export type WhenMatch<R, P> =
// check for any
[0] extends [1 & R] ? PForMatch<P>
: P extends SafeRefinement<infer SP, never> ? SP
: P extends Predicate.Refinement<infer _R, infer RP>
// try to narrow refinement
? [Extract<R, RP>] extends [infer X] ? [X] extends [never]
// fallback to original refinement
? RP
: X
: never
: P extends PredicateA<infer PP> ? PP
: ExtractMatch<R, PForMatch<P>>
/**
* @since 1.0.0
*/
export type NotMatch<R, P> = Exclude<R, ExtractMatch<R, PForExclude<P>>>
/**
* @since 1.0.0
*/
export type PForMatch<P> = [SafeRefinementP<ResolvePred<P>>] extends [infer X] ? X
: never
/**
* @since 1.0.0
*/
export type PForExclude<P> = [SafeRefinementR<ToSafeRefinement<P>>] extends [infer X] ? X
: never
// utilities
type PredicateA<A> = Predicate.Predicate<A> | Predicate.Refinement<A, A>
type SafeRefinementP<A> = A extends never ? never
: A extends SafeRefinement<infer S, infer _> ? S
: A extends Function ? A
: A extends Record<string, any> ? { [K in keyof A]: SafeRefinementP<A[K]> }
: A
type SafeRefinementR<A> = A extends never ? never
: A extends SafeRefinement<infer _, infer R> ? R
: A extends Function ? A
: A extends Record<string, any> ? { [K in keyof A]: SafeRefinementR<A[K]> }
: A
type ResolvePred<A> = A extends never ? never
: A extends Predicate.Refinement<any, infer P> ? P
: A extends Predicate.Predicate<infer P> ? P
: A extends SafeRefinement<any> ? A
: A extends Record<string, any> ? { [K in keyof A]: ResolvePred<A[K]> }
: A
type ToSafeRefinement<A> = A extends never ? never
: A extends Predicate.Refinement<any, infer P> ? SafeRefinement<P, P>
: A extends Predicate.Predicate<infer P> ? SafeRefinement<P, never>
: A extends SafeRefinement<any> ? A
: A extends Record<string, any> ? { [K in keyof A]: ToSafeRefinement<A[K]> }
: NonLiteralsTo<A, never>
type NonLiteralsTo<A, T> = [A] extends [string | number | boolean | bigint] ? [string] extends [A] ? T
: [number] extends [A] ? T
: [boolean] extends [A] ? T
: [bigint] extends [A] ? T
: A
: A
/**
* @since 1.0.0
*/
export type PatternBase<A> = A extends ReadonlyArray<infer _T> ? ReadonlyArray<any> | PatternPrimitive<A>
: A extends Record<string, any> ? Partial<
{ [K in keyof A]: PatternPrimitive<A[K] & {}> | PatternBase<A[K] & {}> }
>
: never
/**
* @since 1.0.0
*/
export type PatternPrimitive<A> = PredicateA<A> | A | SafeRefinement<any>
/**
* @since 1.0.0
*/
export interface Without<out X> {
readonly _tag: "Without"
readonly _X: X
}
/**
* @since 1.0.0
*/
export interface Only<out X> {
readonly _tag: "Only"
readonly _X: X
}
/**
* @since 1.0.0
*/
export type AddWithout<A, X> = [A] extends [Without<infer WX>] ? Without<X | WX>
: [A] extends [Only<infer OX>] ? Only<Exclude<OX, X>>
: never
/**
* @since 1.0.0
*/
export type AddOnly<A, X> = [A] extends [Without<infer WX>] ? [X] extends [WX] ? never
: Only<X>
: [A] extends [Only<infer OX>] ? [X] extends [OX] ? Only<X>
: never
: never
/**
* @since 1.0.0
*/
export type ApplyFilters<I, A> = A extends Only<infer X> ? X
: A extends Without<infer X> ? Exclude<I, X>
: never
/**
* @since 1.0.0
*/
export type Tags<D extends string, P> = P extends Record<D, infer X> ? X : never
/**
* @since 1.0.0
*/
export type ArrayToIntersection<A extends ReadonlyArray<any>> = UnionToIntersection<
A[number]
>
/**
* @since 1.0.0
*/
export type ExtractMatch<I, P> = [ExtractAndNarrow<I, P>] extends [infer EI] ? EI
: never
type Replace<A, B> = A extends Function ? A
: A extends Record<string | number, any> ? { [K in keyof A]: K extends keyof B ? Replace<A[K], B[K]> : A[K] }
: [B] extends [A] ? B
: A
type MaybeReplace<I, P> = [P] extends [I] ? P
: [I] extends [P] ? Replace<I, P>
: Fail
type BuiltInObjects =
| Function
| Date
| RegExp
| Generator
| { readonly [Symbol.toStringTag]: string }
type IsPlainObject<T> = T extends BuiltInObjects ? false
: T extends Record<string, any> ? true
: false
type Simplify<A> = { [K in keyof A]: A[K] } & {}
type ExtractAndNarrow<Input, P> =
// unknown is a wildcard pattern
unknown extends P ? Input
: Input extends infer I ? Exclude<
I extends ReadonlyArray<any> ? P extends ReadonlyArray<any> ? {
readonly [K in keyof I]: K extends keyof P ? ExtractAndNarrow<I[K], P[K]>
: I[K]
} extends infer R ? Fail extends R[keyof R] ? never
: R
: never
: never
: IsPlainObject<I> extends true ? string extends keyof I ? I extends P ? I
: never
: symbol extends keyof I ? I extends P ? I
: never
: Simplify<
& { [RK in Extract<keyof I, keyof P>]-?: ExtractAndNarrow<I[RK], P[RK]> }
& Omit<I, keyof P>
> extends infer R ? [keyof P] extends [keyof RemoveFails<R>] ? R
: never
: never
: MaybeReplace<I, P> extends infer R ? [I] extends [R] ? I
: R
: never,
Fail
> :
never
type RemoveFails<A> = NonFailKeys<A> extends infer K ? [K] extends [keyof A] ? { [RK in K]: A[RK] }
: {}
: {}
type NonFailKeys<A> = keyof A & {} extends infer K ? K extends keyof A ? A[K] extends Fail ? never : K
: never :
never
}