From 96409d01a3ac17ba041cad2a6c28482293362b5a Mon Sep 17 00:00:00 2001 From: Tylor Steinberger Date: Thu, 29 Feb 2024 15:55:47 -0500 Subject: [PATCH] feat: improve type-level performance of Path.ParamsOf + Path.Interpolate --- .changeset/chilly-lobsters-refuse.md | 13 ++++ docs/fx/Fx.ts.md | 94 +++++++++++++++++++++++++++- docs/fx/RefSubject.ts.md | 2 +- docs/path/Interpolate.ts.md | 5 +- docs/path/ParamsOf.ts.md | 2 +- docs/router/CurrentRoute.ts.md | 2 +- docs/template/EventHandler.ts.md | 2 +- packages/path/src/Interpolate.ts | 44 ++++++++----- packages/path/src/ParamsOf.ts | 12 ++-- 9 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 .changeset/chilly-lobsters-refuse.md diff --git a/.changeset/chilly-lobsters-refuse.md b/.changeset/chilly-lobsters-refuse.md new file mode 100644 index 000000000..2c281cbec --- /dev/null +++ b/.changeset/chilly-lobsters-refuse.md @@ -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. diff --git a/docs/fx/Fx.ts.md b/docs/fx/Fx.ts.md index ee263752f..c8faa3366 100644 --- a/docs/fx/Fx.ts.md +++ b/docs/fx/Fx.ts.md @@ -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) @@ -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) @@ -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) @@ -719,6 +725,20 @@ export type MatchCauseOptions = { Added in v1.20.0 +## MatchCauseOptionsEffect (type alias) + +**Signature** + +```ts +export type MatchCauseOptionsEffect = { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + readonly executionStrategy?: ExecutionStrategy.ExecutionStrategy | undefined +} +``` + +Added in v1.20.0 + ## MatchErrorOptions (type alias) **Signature** @@ -733,6 +753,20 @@ export type MatchErrorOptions = { Added in v1.20.0 +## MatchErrorOptionsEffect (type alias) + +**Signature** + +```ts +export type MatchErrorOptionsEffect = { + readonly onFailure: (e: E) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + readonly executionStrategy?: ExecutionStrategy.ExecutionStrategy | undefined +} +``` + +Added in v1.20.0 + ## Success (type alias) **Signature** @@ -2507,7 +2541,7 @@ export declare const matchError: { ): (fx: Fx) => Fx ( fx: Fx, - opts: core.MatchErrorOptions + opts: MatchErrorOptions ): Fx } ``` @@ -2878,6 +2912,26 @@ export declare const prependAll: { Added in v1.20.0 +## promise + +**Signature** + +```ts +export declare function promise(f: (signal: AbortSignal) => Promise) +``` + +Added in v2.0.0 + +## promiseFx + +**Signature** + +```ts +export declare function promiseFx(f: (signal: AbortSignal) => Promise>) +``` + +Added in v2.0.0 + ## provide **Signature** @@ -3299,6 +3353,24 @@ export declare const switchMatchCause: { Added in v1.20.0 +## switchMatchCauseEffect + +**Signature** + +```ts +export declare const switchMatchCauseEffect: { + ( + opts: MatchCauseOptionsEffect + ): (fx: Fx) => Fx + ( + fx: Fx, + opts: MatchCauseOptionsEffect + ): Fx +} +``` + +Added in v2.0.0 + ## switchMatchError **Signature** @@ -3310,13 +3382,31 @@ export declare const switchMatchError: { ): (fx: Fx) => Fx ( fx: Fx, - opts: core.MatchErrorOptions + opts: MatchErrorOptions ): Fx } ``` Added in v1.20.0 +## switchMatchErrorEffect + +**Signature** + +```ts +export declare const switchMatchErrorEffect: { + ( + opts: MatchErrorOptionsEffect + ): (fx: Fx) => Fx + ( + fx: Fx, + opts: MatchErrorOptionsEffect + ): Fx +} +``` + +Added in v2.0.0 + ## sync **Signature** diff --git a/docs/fx/RefSubject.ts.md b/docs/fx/RefSubject.ts.md index ad34cc405..03e260121 100644 --- a/docs/fx/RefSubject.ts.md +++ b/docs/fx/RefSubject.ts.md @@ -1022,7 +1022,7 @@ Added in v1.20.0 **Signature** ```ts -export declare function tagged( +export declare function tagged( replay?: number ): { >(identifier: I): RefSubject.Tagged, E, A> diff --git a/docs/path/Interpolate.ts.md b/docs/path/Interpolate.ts.md index 4f1148f06..228ea6a87 100644 --- a/docs/path/Interpolate.ts.md +++ b/docs/path/Interpolate.ts.md @@ -28,9 +28,8 @@ Interpolate a path with parameters **Signature** ```ts -export type Interpolate

> = PathJoin< - InterpolateParts>, Params> -> +export type Interpolate

> = + A.Equals extends 1 ? string : PathJoin>, Params>> ``` Added in v1.0.0 diff --git a/docs/path/ParamsOf.ts.md b/docs/path/ParamsOf.ts.md index 93e14e549..af7e11a12 100644 --- a/docs/path/ParamsOf.ts.md +++ b/docs/path/ParamsOf.ts.md @@ -28,7 +28,7 @@ Extract the parameters from a path **Signature** ```ts -export type ParamsOf = ToParams>> +export type ParamsOf = A.Equals extends 1 ? {} : ToParams>> ``` Added in v1.0.0 diff --git a/docs/router/CurrentRoute.ts.md b/docs/router/CurrentRoute.ts.md index fbaf5ba31..33494a5ca 100644 --- a/docs/router/CurrentRoute.ts.md +++ b/docs/router/CurrentRoute.ts.md @@ -36,7 +36,7 @@ Added in v1.0.0 ```ts export declare const CurrentParams: RefSubject.Filtered< - Readonly>, + Readonly>, never, CurrentRoute | Navigation > diff --git a/docs/template/EventHandler.ts.md b/docs/template/EventHandler.ts.md index 91836d30b..96927167a 100644 --- a/docs/template/EventHandler.ts.md +++ b/docs/template/EventHandler.ts.md @@ -109,7 +109,7 @@ Added in v1.0.0 **Signature** ```ts -export declare function make( +export declare function make( handler: (event: Ev) => Effect, options?: AddEventListenerOptions ): EventHandler diff --git a/packages/path/src/Interpolate.ts b/packages/path/src/Interpolate.ts index adcf8a2c8..824768acd 100644 --- a/packages/path/src/Interpolate.ts +++ b/packages/path/src/Interpolate.ts @@ -14,6 +14,7 @@ import type { PrefixNode, QueryParamNode, QueryParamsNode, + SegmentAST, SuffixNode, TextNode, UnnamedParamNode @@ -40,23 +41,36 @@ type InterpolateParts< readonly [K in keyof T]: InterpolatePart } -type InterpolatePart = T extends - ModifierNode ? InterpolatePart - : T extends TextNode ? T - : T extends NamedParamNode ? - N extends keyof Params ? EnsureIsString : IsOptional extends true ? "" : string - : T extends NamedParamNodeWithRegex ? +type InterpolatePartMap = { + Modifier: T extends ModifierNode ? InterpolatePart + : never + Text: T extends TextNode ? T : never + NamedParam: T extends NamedParamNode ? N extends keyof Params ? EnsureIsString + : IsOptional extends true ? "" + : string + : never + NamedParamWithRegex: T extends NamedParamNodeWithRegex ? N extends keyof Params ? EnsureIsString : IsOptional extends true ? "" : string - : T extends UnnamedParamNode> ? EnsureIsString - : T extends PrefixNode ? // @ts-expect-error Type potentially infinite - S extends PrefixNode ? `${P}${InterpolatePart}` - : `` extends InterpolatePart ? `` + : never + UnnamedParam: T extends UnnamedParamNode> ? + EnsureIsString + : IsOptional extends true ? "" + : never + Prefix: T extends PrefixNode ? + S extends PrefixNode ? `${P}${InterpolatePart}` : + `` extends InterpolatePart ? `` : `${P}${InterpolatePart}` - : T extends SuffixNode ? - `` extends InterpolatePart ? `` : `${InterpolatePart}${P}` - : T extends QueryParamsNode ? `?${InterpolateQueryParams}` - : T extends QueryParamNode ? `${K}=${InterpolatePart}` - : "" + : never + Suffix: T extends SuffixNode ? `` extends InterpolatePart ? `` + : `${InterpolatePart}${P}` + : never + QueryParams: T extends QueryParamsNode ? `?${InterpolateQueryParams}` : never + QueryParam: T extends QueryParamNode ? `${K}=${InterpolatePart}` : never +} + +type InterpolatePart = T extends + SegmentAST | QueryParamNode ? InterpolatePartMap[T["_tag"]] + : never type InterpolateQueryParams, Params extends {}> = S.Join< { diff --git a/packages/path/src/ParamsOf.ts b/packages/path/src/ParamsOf.ts index 5c37a371a..127f4222b 100644 --- a/packages/path/src/ParamsOf.ts +++ b/packages/path/src/ParamsOf.ts @@ -25,20 +25,18 @@ import type { * Extract the parameters from a path * @since 1.0.0 */ -export type ParamsOf = A.Equals extends 1 ? {} : ToParams>> +export type ParamsOf = A.Equals extends 1 ? {} + : ToParams>> type ToParams, Params = {}> = [ - // @ts-expect-error Type potentially infinite - ListToIntersection< + UnionToIntersection< { [K in keyof T]: AstToParams - } + }[number] > ] extends [infer P] ? { readonly [K in keyof P]: P[K] } : never -type ListToIntersection> = T extends readonly [infer Head, ...infer Tail] - ? Head & ListToIntersection - : unknown +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never type AstToParams = T extends ModifierNode ? ModifyParams> : T extends TextNode ? {} :