Skip to content

Commit

Permalink
feat: improve type-level performance of Path.ParamsOf + Path.Interpolate
Browse files Browse the repository at this point in the history
  • Loading branch information
TylorS committed Feb 29, 2024
1 parent 02fbf69 commit 96409d0
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 31 deletions.
13 changes: 13 additions & 0 deletions .changeset/chilly-lobsters-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@typed/path": patch
---

Improve type-level performance of `@typed/path`'s ParamsOf + Interpolate types.

This removes the need for @ts-expect-error for possibly infinite types.

For ParamsOf this was accomplished by switching from a tuple/reduce-based type for creating an intersection of types to a more
"standard" UnionToIntersection which works by changing the variance.

For Interpolate this was accomplished by using a type-level map to
allow TypeScript to narrow the problem space to only the type-level AST types used internally for parsing a path without the need of constraining the input values and dealing with type-level casts.
94 changes: 92 additions & 2 deletions docs/fx/Fx.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ Added in v1.20.0
- [toEffect (method)](#toeffect-method)
- [KeyedOptions (interface)](#keyedoptions-interface)
- [MatchCauseOptions (type alias)](#matchcauseoptions-type-alias)
- [MatchCauseOptionsEffect (type alias)](#matchcauseoptionseffect-type-alias)
- [MatchErrorOptions (type alias)](#matcherroroptions-type-alias)
- [MatchErrorOptionsEffect (type alias)](#matcherroroptionseffect-type-alias)
- [Success (type alias)](#success-type-alias-1)
- [Unify (type alias)](#unify-type-alias-1)
- [WithKeyOptions (interface)](#withkeyoptions-interface)
Expand Down Expand Up @@ -198,6 +200,8 @@ Added in v1.20.0
- [periodic](#periodic)
- [prepend](#prepend)
- [prependAll](#prependall)
- [promise](#promise)
- [promiseFx](#promisefx)
- [provide](#provide)
- [provideContext](#providecontext)
- [provideLayer](#providelayer)
Expand Down Expand Up @@ -227,7 +231,9 @@ Added in v1.20.0
- [switchMapEffect](#switchmapeffect)
- [switchMapError](#switchmaperror)
- [switchMatchCause](#switchmatchcause)
- [switchMatchCauseEffect](#switchmatchcauseeffect)
- [switchMatchError](#switchmatcherror)
- [switchMatchErrorEffect](#switchmatcherroreffect)
- [sync](#sync)
- [take](#take)
- [takeUntiEffect](#takeuntieffect)
Expand Down Expand Up @@ -719,6 +725,20 @@ export type MatchCauseOptions<E, A, B, E2, R2, C, E3, R3> = {
Added in v1.20.0
## MatchCauseOptionsEffect (type alias)
**Signature**
```ts
export type MatchCauseOptionsEffect<E, A, B, E2, R2, C, E3, R3> = {
readonly onFailure: (cause: Cause.Cause<E>) => Effect.Effect<B, E2, R2>
readonly onSuccess: (a: A) => Effect.Effect<C, E3, R3>
readonly executionStrategy?: ExecutionStrategy.ExecutionStrategy | undefined
}
```
Added in v1.20.0
## MatchErrorOptions (type alias)
**Signature**
Expand All @@ -733,6 +753,20 @@ export type MatchErrorOptions<E, A, B, E2, R2, C, E3, R3> = {
Added in v1.20.0
## MatchErrorOptionsEffect (type alias)
**Signature**
```ts
export type MatchErrorOptionsEffect<E, A, B, E2, R2, C, E3, R3> = {
readonly onFailure: (e: E) => Effect.Effect<B, E2, R2>
readonly onSuccess: (a: A) => Effect.Effect<C, E3, R3>
readonly executionStrategy?: ExecutionStrategy.ExecutionStrategy | undefined
}
```
Added in v1.20.0
## Success (type alias)
**Signature**
Expand Down Expand Up @@ -2507,7 +2541,7 @@ export declare const matchError: {
): <R>(fx: Fx<A, E, R>) => Fx<B | C, E2 | E3, Scope.Scope | R2 | R3 | R>
<A, E, R, B, E2, R2, C, E3, R3>(
fx: Fx<A, E, R>,
opts: core.MatchErrorOptions<E, A, B, E2, R2, C, E3, R3>
opts: MatchErrorOptions<E, A, B, E2, R2, C, E3, R3>
): Fx<B | C, E2 | E3, Scope.Scope | R | R2 | R3>
}
```
Expand Down Expand Up @@ -2878,6 +2912,26 @@ export declare const prependAll: {

Added in v1.20.0

## promise

**Signature**

```ts
export declare function promise<A>(f: (signal: AbortSignal) => Promise<A>)
```

Added in v2.0.0

## promiseFx

**Signature**

```ts
export declare function promiseFx<A, E = never, R = never>(f: (signal: AbortSignal) => Promise<Fx<A, E, R>>)
```

Added in v2.0.0

## provide

**Signature**
Expand Down Expand Up @@ -3299,6 +3353,24 @@ export declare const switchMatchCause: {

Added in v1.20.0

## switchMatchCauseEffect

**Signature**

```ts
export declare const switchMatchCauseEffect: {
<E, A, B, E2, R2, C, E3, R3>(
opts: MatchCauseOptionsEffect<E, A, B, E2, R2, C, E3, R3>
): <R>(fx: Fx<A, E, R>) => Fx<B | C, E2 | E3, Scope.Scope | R2 | R3 | R>
<A, E, R, B, E2, R2, C, E3, R3>(
fx: Fx<A, E, R>,
opts: MatchCauseOptionsEffect<E, A, B, E2, R2, C, E3, R3>
): Fx<B | C, E2 | E3, Scope.Scope | R | R2 | R3>
}
```

Added in v2.0.0

## switchMatchError

**Signature**
Expand All @@ -3310,13 +3382,31 @@ export declare const switchMatchError: {
): <R>(fx: Fx<A, E, R>) => Fx<B | C, E2 | E3, Scope.Scope | R2 | R3 | R>
<A, E, R, B, E2, R2, C, E3, R3>(
fx: Fx<A, E, R>,
opts: core.MatchErrorOptions<E, A, B, E2, R2, C, E3, R3>
opts: MatchErrorOptions<E, A, B, E2, R2, C, E3, R3>
): Fx<B | C, E2 | E3, Scope.Scope | R | R2 | R3>
}
```

Added in v1.20.0

## switchMatchErrorEffect

**Signature**

```ts
export declare const switchMatchErrorEffect: {
<E, A, B, E2, R2, C, E3, R3>(
opts: MatchErrorOptionsEffect<E, A, B, E2, R2, C, E3, R3>
): <R>(fx: Fx<A, E, R>) => Fx<B | C, E2 | E3, Scope.Scope | R2 | R3 | R>
<A, E, R, B, E2, R2, C, E3, R3>(
fx: Fx<A, E, R>,
opts: MatchErrorOptionsEffect<E, A, B, E2, R2, C, E3, R3>
): Fx<B | C, E2 | E3, Scope.Scope | R | R2 | R3>
}
```

Added in v2.0.0

## sync

**Signature**
Expand Down
2 changes: 1 addition & 1 deletion docs/fx/RefSubject.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ Added in v1.20.0
**Signature**

```ts
export declare function tagged<E, A>(
export declare function tagged<A, E = never>(
replay?: number
): {
<const I extends C.IdentifierFactory<any>>(identifier: I): RefSubject.Tagged<C.IdentifierOf<I>, E, A>
Expand Down
5 changes: 2 additions & 3 deletions docs/path/Interpolate.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ Interpolate a path with parameters
**Signature**

```ts
export type Interpolate<P extends string, Params extends ParamsOf<P>> = PathJoin<
InterpolateParts<ParseSegments<PathToSegments<P>>, Params>
>
export type Interpolate<P extends string, Params extends ParamsOf<P>> =
A.Equals<P, string> extends 1 ? string : PathJoin<InterpolateParts<ParseSegments<PathToSegments<P>>, Params>>
```
Added in v1.0.0
2 changes: 1 addition & 1 deletion docs/path/ParamsOf.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Extract the parameters from a path
**Signature**

```ts
export type ParamsOf<T extends string> = ToParams<ParseSegments<PathToSegments<T>>>
export type ParamsOf<T extends string> = A.Equals<T, string> extends 1 ? {} : ToParams<ParseSegments<PathToSegments<T>>>
```
Added in v1.0.0
2 changes: 1 addition & 1 deletion docs/router/CurrentRoute.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Added in v1.0.0

```ts
export declare const CurrentParams: RefSubject.Filtered<
Readonly<Record<string, string>>,
Readonly<Record<string, string | readonly string[]>>,
never,
CurrentRoute<string> | Navigation
>
Expand Down
2 changes: 1 addition & 1 deletion docs/template/EventHandler.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Added in v1.0.0
**Signature**

```ts
export declare function make<R, E, Ev extends Event>(
export declare function make<Ev extends Event, E = never, R = never>(
handler: (event: Ev) => Effect<unknown, E, R>,
options?: AddEventListenerOptions
): EventHandler<Ev, E, R>
Expand Down
44 changes: 29 additions & 15 deletions packages/path/src/Interpolate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
PrefixNode,
QueryParamNode,
QueryParamsNode,
SegmentAST,
SuffixNode,
TextNode,
UnnamedParamNode
Expand All @@ -40,23 +41,36 @@ type InterpolateParts<
readonly [K in keyof T]: InterpolatePart<T[K], Params>
}

type InterpolatePart<T, Params extends {}, IsOptional extends boolean = false, Prefix extends string = "/"> = T extends
ModifierNode<infer M, infer S> ? InterpolatePart<S, Params, M extends "+" ? false : true>
: T extends TextNode<infer T> ? T
: T extends NamedParamNode<infer N> ?
N extends keyof Params ? EnsureIsString<Params[N], Prefix> : IsOptional extends true ? "" : string
: T extends NamedParamNodeWithRegex<infer N, infer _> ?
type InterpolatePartMap<T, Params extends {}, IsOptional extends boolean, Prefix extends string> = {
Modifier: T extends ModifierNode<infer M, infer S> ? InterpolatePart<S, Params, M extends "+" ? false : true>
: never
Text: T extends TextNode<infer T> ? T : never
NamedParam: T extends NamedParamNode<infer N> ? N extends keyof Params ? EnsureIsString<Params[N], Prefix>
: IsOptional extends true ? ""
: string
: never
NamedParamWithRegex: T extends NamedParamNodeWithRegex<infer N, infer _> ?
N extends keyof Params ? EnsureIsString<Params[N], Prefix> : IsOptional extends true ? "" : string
: T extends UnnamedParamNode<infer I extends Extract<keyof Params, number>> ? EnsureIsString<Params[I], Prefix>
: T extends PrefixNode<infer P, infer S> ? // @ts-expect-error Type potentially infinite
S extends PrefixNode<infer P2, any> ? `${P}${InterpolatePart<S, Params, IsOptional, P2>}`
: `` extends InterpolatePart<S, Params, IsOptional, P> ? ``
: never
UnnamedParam: T extends UnnamedParamNode<infer I extends Extract<keyof Params, number>> ?
EnsureIsString<Params[I], Prefix>
: IsOptional extends true ? ""
: never
Prefix: T extends PrefixNode<infer P, infer S> ?
S extends PrefixNode<infer P2, infer _> ? `${P}${InterpolatePart<S, Params, IsOptional, P2>}` :
`` extends InterpolatePart<S, Params, IsOptional, P> ? ``
: `${P}${InterpolatePart<S, Params, IsOptional, P>}`
: T extends SuffixNode<infer S, infer P> ?
`` extends InterpolatePart<S, Params, IsOptional> ? `` : `${InterpolatePart<S, Params, IsOptional>}${P}`
: T extends QueryParamsNode<infer Q> ? `?${InterpolateQueryParams<Q, Params>}`
: T extends QueryParamNode<infer K, infer V> ? `${K}=${InterpolatePart<V, Params>}`
: ""
: never
Suffix: T extends SuffixNode<infer S, infer P> ? `` extends InterpolatePart<S, Params, IsOptional> ? ``
: `${InterpolatePart<S, Params, IsOptional>}${P}`
: never
QueryParams: T extends QueryParamsNode<infer Q> ? `?${InterpolateQueryParams<Q, Params>}` : never
QueryParam: T extends QueryParamNode<infer K, infer V> ? `${K}=${InterpolatePart<V, Params>}` : never
}

type InterpolatePart<T, Params extends {}, IsOptional extends boolean = false, Prefix extends string = "/"> = T extends
SegmentAST | QueryParamNode<any, any> ? InterpolatePartMap<T, Params, IsOptional, Prefix>[T["_tag"]]
: never

type InterpolateQueryParams<T extends ReadonlyArray<any>, Params extends {}> = S.Join<
{
Expand Down
12 changes: 5 additions & 7 deletions packages/path/src/ParamsOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,18 @@ import type {
* Extract the parameters from a path
* @since 1.0.0
*/
export type ParamsOf<T extends string> = A.Equals<T, string> extends 1 ? {} : ToParams<ParseSegments<PathToSegments<T>>>
export type ParamsOf<T extends string> = A.Equals<T, string> extends 1 ? {}
: ToParams<ParseSegments<PathToSegments<T>>>

type ToParams<T extends ReadonlyArray<any>, Params = {}> = [
// @ts-expect-error Type potentially infinite
ListToIntersection<
UnionToIntersection<
{
[K in keyof T]: AstToParams<T[K], Params>
}
}[number]
>
] extends [infer P] ? { readonly [K in keyof P]: P[K] } : never

type ListToIntersection<T extends ReadonlyArray<any>> = T extends readonly [infer Head, ...infer Tail]
? Head & ListToIntersection<Tail>
: unknown
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never

type AstToParams<T, Params = {}> = T extends ModifierNode<infer M, infer S> ? ModifyParams<M, AstToParams<S>> :
T extends TextNode<infer _> ? {} :
Expand Down

0 comments on commit 96409d0

Please sign in to comment.