diff --git a/.commands/test-lint.sh b/.commands/test-lint.sh new file mode 100755 index 000000000..45a248e5d --- /dev/null +++ b/.commands/test-lint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +ARGUMENTS="$@" + +# scripts +eslint --quiet $ARGUMENTS scripts/ +# tests +eslint --quiet $ARGUMENTS tests/ +eslint --quiet $ARGUMENTS integration/ +# documentation +eslint --quiet $ARGUMENTS docs/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 523b7c14c..70a596b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules dist coverage -cache \ No newline at end of file +cache +.temp \ No newline at end of file diff --git a/docs/en/v1/api/clean/index.md b/docs/en/v1/api/clean/index.md index d5867a461..9c6120a7d 100644 --- a/docs/en/v1/api/clean/index.md +++ b/docs/en/v1/api/clean/index.md @@ -69,6 +69,12 @@ Declares an aggregate of linked business actions that must run in order. ### [equal](/en/v1/api/clean/primitives/operators/equal) Compares two wrapped primitives (or a primitive and a raw value) with a type guard. +### [matchWithString](/en/v1/api/clean/primitives/operators/matchWithString) +Performs exhaustive matching with raw string keys and passes the narrowed original Clean primitive. + +### [matchWithNumber](/en/v1/api/clean/primitives/operators/matchWithNumber) +Performs exhaustive matching with raw number keys and passes the narrowed original Clean primitive. + ### [add](/en/v1/api/clean/primitives/operators/add) Adds two `Number` (supports the curried version). diff --git a/docs/en/v1/api/clean/primitives/index.md b/docs/en/v1/api/clean/primitives/index.md index 31a5529c6..eda387d1e 100644 --- a/docs/en/v1/api/clean/primitives/index.md +++ b/docs/en/v1/api/clean/primitives/index.md @@ -96,6 +96,18 @@ function is( ### [equal](/en/v1/api/clean/primitives/operators/equal) Compares two wrapped primitives (or a primitive and a raw value) with a type guard. +### [matchWithString](/en/v1/api/clean/primitives/operators/matchWithString) +Performs exhaustive matching with raw string keys and passes the narrowed original Clean primitive. + +### [matchWithStringOtherwise](/en/v1/api/clean/primitives/operators/matchWithStringOtherwise) +Partially matches raw string keys and passes remaining wrapped values to a typed fallback. + +### [matchWithNumber](/en/v1/api/clean/primitives/operators/matchWithNumber) +Performs exhaustive matching with raw number keys and passes the narrowed original Clean primitive. + +### [matchWithNumberOtherwise](/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise) +Partially matches raw number keys and passes remaining wrapped values to a typed fallback. + ### [add](/en/v1/api/clean/primitives/operators/add) Adds two `Number` (supports the curried version). diff --git a/docs/en/v1/api/clean/primitives/operators/add.md b/docs/en/v1/api/clean/primitives/operators/add.md index 007a0b4c7..5cbd238d3 100644 --- a/docs/en/v1/api/clean/primitives/operators/add.md +++ b/docs/en/v1/api/clean/primitives/operators/add.md @@ -1,8 +1,8 @@ --- outline: [2, 3] prev: - text: "equal" - link: "/en/v1/api/clean/primitives/operators/equal" + text: "matchWithNumberOtherwise" + link: "/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise" next: text: "subtract" link: "/en/v1/api/clean/primitives/operators/subtract" diff --git a/docs/en/v1/api/clean/primitives/operators/equal.md b/docs/en/v1/api/clean/primitives/operators/equal.md index 3b89e3bef..e9266198e 100644 --- a/docs/en/v1/api/clean/primitives/operators/equal.md +++ b/docs/en/v1/api/clean/primitives/operators/equal.md @@ -1,8 +1,8 @@ --- outline: [2, 3] next: - text: "add" - link: "/en/v1/api/clean/primitives/operators/add" + text: "matchWithString" + link: "/en/v1/api/clean/primitives/operators/matchWithString" --- # equal diff --git a/docs/en/v1/api/clean/primitives/operators/matchWithNumber.md b/docs/en/v1/api/clean/primitives/operators/matchWithNumber.md new file mode 100644 index 000000000..7863d8218 --- /dev/null +++ b/docs/en/v1/api/clean/primitives/operators/matchWithNumber.md @@ -0,0 +1,59 @@ +--- +outline: [2, 3] +description: "matchWithNumber() performs exhaustive pattern matching on a Clean number primitive value and passes the correctly narrowed original primitive to each branch." +prev: + text: "matchWithStringOtherwise" + link: "/en/v1/api/clean/primitives/operators/matchWithStringOtherwise" +next: + text: "matchWithNumberOtherwise" + link: "/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise" +--- + +# matchWithNumber + +`matchWithNumber()` performs exhaustive pattern matching on the value of a Clean number primitive. Every possible value must have a processing branch. + +A Clean primitive is a wrapped object, so it cannot be used directly as a matcher key. The keys are therefore raw `number` values. When a key matches, its callback receives the original Clean primitive narrowed to that value, preserving its `Primitive`, `ConstrainedType`, or `NewType` information. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithNumber, Matcher>( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Curried signature + +```typescript +function matchWithNumber, Matcher>( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Parameters + +- `input`: a Clean primitive containing a `number`, including constrained values and new types. +- `matcher`: an exhaustive object indexed by the possible raw `number` values. A broad `Primitive` requires an indexed `Record`. + +Every key must have exactly one handler. TypeScript rejects a missing key or a key outside the input union. The selected handler receives the original Clean object narrowed with `Primitive`. + +## Return value + +The selected handler result, typed as the union of every handler return type. + +## See also + +- [`matchWithString`](/en/v1/api/clean/primitives/operators/matchWithString) - String primitive equivalent. +- [`equal`](/en/v1/api/clean/primitives/operators/equal) - Compares wrapped primitive values. diff --git a/docs/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise.md b/docs/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise.md new file mode 100644 index 000000000..a5cc03b31 --- /dev/null +++ b/docs/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithNumberOtherwise() partially matches a Clean number primitive while preserving the narrowed original primitive in handlers and otherwise." +prev: + text: "matchWithNumber" + link: "/en/v1/api/clean/primitives/operators/matchWithNumber" +next: + text: "add" + link: "/en/v1/api/clean/primitives/operators/add" +--- + +# matchWithNumberOtherwise + +`matchWithNumberOtherwise()` handles selected values of a Clean number primitive. Raw number values form the matcher keys. Missing cases are sent to `otherwise`, which receives the original primitive narrowed to the remaining values. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithNumberOtherwise, Matcher, Output>( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): MatcherResult | Output +``` + +### Curried signature + +```typescript +function matchWithNumberOtherwise, Matcher, Output>( + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): (input: Input) => MatcherResult | Output +``` + +## Parameters + +- `input`: a Clean number primitive with literal values. +- `matcher`: a partial object indexed by raw values from the primitive. Unknown keys are rejected. +- `otherwise`: receives the original `Primitive`, `ConstrainedType`, or `NewType`, narrowed to values without a handler. + +## Return value + +The selected handler result, or the `otherwise` result for an unhandled wrapped value. + +## See also + +- [`matchWithNumber`](/en/v1/api/clean/primitives/operators/matchWithNumber) - Exhaustive primitive matching. +- [`matchWithStringOtherwise`](/en/v1/api/clean/primitives/operators/matchWithStringOtherwise) - String primitive equivalent. diff --git a/docs/en/v1/api/clean/primitives/operators/matchWithString.md b/docs/en/v1/api/clean/primitives/operators/matchWithString.md new file mode 100644 index 000000000..023b05bb4 --- /dev/null +++ b/docs/en/v1/api/clean/primitives/operators/matchWithString.md @@ -0,0 +1,59 @@ +--- +outline: [2, 3] +description: "matchWithString() performs exhaustive pattern matching on a Clean string primitive value and passes the correctly narrowed original primitive to each branch." +prev: + text: "equal" + link: "/en/v1/api/clean/primitives/operators/equal" +next: + text: "matchWithStringOtherwise" + link: "/en/v1/api/clean/primitives/operators/matchWithStringOtherwise" +--- + +# matchWithString + +`matchWithString()` performs exhaustive pattern matching on the value of a Clean string primitive. Every possible value must have a processing branch. + +A Clean primitive is a wrapped object, so it cannot be used directly as a matcher key. The keys are therefore raw `string` values. When a key matches, its callback receives the original Clean primitive narrowed to that value, preserving its `Primitive`, `ConstrainedType`, or `NewType` information. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithString, Matcher>( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Curried signature + +```typescript +function matchWithString, Matcher>( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Parameters + +- `input`: a Clean primitive containing a `string`, including constrained values and new types. +- `matcher`: an exhaustive object indexed by the possible raw `string` values. A broad `Primitive` requires an indexed `Record`. + +Every key must have exactly one handler. TypeScript rejects a missing key or a key outside the input union. The selected handler receives the original Clean object narrowed with `Primitive`. + +## Return value + +The selected handler result, typed as the union of every handler return type. + +## See also + +- [`matchWithNumber`](/en/v1/api/clean/primitives/operators/matchWithNumber) - Numeric primitive equivalent. +- [`equal`](/en/v1/api/clean/primitives/operators/equal) - Compares wrapped primitive values. diff --git a/docs/en/v1/api/clean/primitives/operators/matchWithStringOtherwise.md b/docs/en/v1/api/clean/primitives/operators/matchWithStringOtherwise.md new file mode 100644 index 000000000..1223953a4 --- /dev/null +++ b/docs/en/v1/api/clean/primitives/operators/matchWithStringOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithStringOtherwise() partially matches a Clean string primitive while preserving the narrowed original primitive in handlers and otherwise." +prev: + text: "matchWithString" + link: "/en/v1/api/clean/primitives/operators/matchWithString" +next: + text: "matchWithNumber" + link: "/en/v1/api/clean/primitives/operators/matchWithNumber" +--- + +# matchWithStringOtherwise + +`matchWithStringOtherwise()` handles selected values of a Clean string primitive. Raw string values form the matcher keys. Missing cases are sent to `otherwise`, which receives the original primitive narrowed to the remaining values. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithStringOtherwise, Matcher, Output>( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): MatcherResult | Output +``` + +### Curried signature + +```typescript +function matchWithStringOtherwise, Matcher, Output>( + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): (input: Input) => MatcherResult | Output +``` + +## Parameters + +- `input`: a Clean string primitive with literal values. +- `matcher`: a partial object indexed by raw values from the primitive. Unknown keys are rejected. +- `otherwise`: receives the original `Primitive`, `ConstrainedType`, or `NewType`, narrowed to values without a handler. + +## Return value + +The selected handler result, or the `otherwise` result for an unhandled wrapped value. + +## See also + +- [`matchWithString`](/en/v1/api/clean/primitives/operators/matchWithString) - Exhaustive primitive matching. +- [`matchWithNumberOtherwise`](/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise) - Number primitive equivalent. diff --git a/docs/en/v1/api/common/asyncInnerPipe.md b/docs/en/v1/api/common/asyncInnerPipe.md index 5d39a600e..a7357a1ac 100644 --- a/docs/en/v1/api/common/asyncInnerPipe.md +++ b/docs/en/v1/api/common/asyncInnerPipe.md @@ -5,8 +5,8 @@ prev: text: "asyncPipe" link: "/en/v1/api/common/asyncPipe" next: - text: "forward" - link: "/en/v1/api/common/forward" + text: "prepareAsyncPipe" + link: "/en/v1/api/common/prepareAsyncPipe" --- # asyncInnerPipe diff --git a/docs/en/v1/api/common/asyncPipe.md b/docs/en/v1/api/common/asyncPipe.md index 0885a4d1a..d72592192 100644 --- a/docs/en/v1/api/common/asyncPipe.md +++ b/docs/en/v1/api/common/asyncPipe.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "The asyncPipe() method chains asynchronous functions (promises or FutureEither) in series. Each step waits for the previous one to resolve and the last value is returned in a promise." prev: - text: "innerPipe" - link: "/en/v1/api/common/innerPipe" + text: "preparePipe" + link: "/en/v1/api/common/preparePipe" next: text: "asyncInnerPipe" link: "/en/v1/api/common/asyncInnerPipe" diff --git a/docs/en/v1/api/common/forward.md b/docs/en/v1/api/common/forward.md index db7b52610..03b9d5ba5 100644 --- a/docs/en/v1/api/common/forward.md +++ b/docs/en/v1/api/common/forward.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "The forward() function returns the passed argument without modifying it. Useful to standardize an API that expects a function, or to improve readability in a pipeline." prev: - text: "asyncInnerPipe" - link: "/en/v1/api/common/asyncInnerPipe" + text: "prepareAsyncPipe" + link: "/en/v1/api/common/prepareAsyncPipe" next: text: "forwardLog" link: "/en/v1/api/common/forwardLog" diff --git a/docs/en/v1/api/common/index.md b/docs/en/v1/api/common/index.md index ab05a51f2..0c4a8ce04 100644 --- a/docs/en/v1/api/common/index.md +++ b/docs/en/v1/api/common/index.md @@ -32,12 +32,18 @@ Composes synchronous functions by chaining a single input. ### [innerPipe](/en/v1/api/common/innerPipe) Prepares a reusable pipe that returns a function to apply later. +### [preparePipe](/en/v1/api/common/preparePipe) +Declares reusable synchronous steps with contextual input-to-output inference. + ### [asyncPipe](/en/v1/api/common/asyncPipe) Composes promises or `FutureEither` sequentially and returns a `Promise`. ### [asyncInnerPipe](/en/v1/api/common/asyncInnerPipe) Curried version of `asyncPipe` that returns a function ready to accept a value (sync or async). +### [prepareAsyncPipe](/en/v1/api/common/prepareAsyncPipe) +Declares reusable maybe-promise steps with contextual input-to-output inference. + ### [forward](/en/v1/api/common/forward) Convenient identity function in a chain to pass the value through unchanged. diff --git a/docs/en/v1/api/common/innerPipe.md b/docs/en/v1/api/common/innerPipe.md index 1c9cf25b5..4aacee81e 100644 --- a/docs/en/v1/api/common/innerPipe.md +++ b/docs/en/v1/api/common/innerPipe.md @@ -5,8 +5,8 @@ prev: text: "pipe" link: "/en/v1/api/common/pipe" next: - text: "asyncPipe" - link: "/en/v1/api/common/asyncPipe" + text: "preparePipe" + link: "/en/v1/api/common/preparePipe" --- # innerPipe diff --git a/docs/en/v1/api/common/prepareAsyncPipe.md b/docs/en/v1/api/common/prepareAsyncPipe.md new file mode 100644 index 000000000..33dfefc6f --- /dev/null +++ b/docs/en/v1/api/common/prepareAsyncPipe.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "prepareAsyncPipe() declares maybe-promise pipeline steps once and returns a reusable asynchronous function with contextual input inference." +prev: + text: "asyncInnerPipe" + link: "/en/v1/api/common/asyncInnerPipe" +next: + text: "forward" + link: "/en/v1/api/common/forward" +--- + +# prepareAsyncPipe + +`prepareAsyncPipe()` is the asynchronous counterpart of `preparePipe()`. It declares up to fifteen transformations once, accepts synchronous or promise-returning steps, and returns a reusable asynchronous function. + +The prepared function accepts either a direct input or a promise. Its contextual input-to-output signature can drive the local input inference, including when the pipeline participates in a recursive asynchronous function. + +## Interactive example + + + +## Syntax + +### Explicit input type + +```typescript +const prepared = prepareAsyncPipe()( + pipe1, + pipe2 +); +``` + +### Contextual input-to-output type + +```typescript +const prepared: (input: Input) => Promise = prepareAsyncPipe()( + pipe1, + pipe2 +); +``` + +## Parameters + +- `Input`: optional upper constraint for the direct or promised input. +- `pipe1, pipe2, ...`: one to fifteen transformations returning either a value or a promise. + +## Return value + +A reusable function accepting `Input | PromiseLike` and returning a promise of the final output. + +## See also + +- [`preparePipe`](/en/v1/api/common/preparePipe) - Synchronous prepared variant. +- [`asyncInnerPipe`](/en/v1/api/common/asyncInnerPipe) - Reusable asynchronous composition inferred directly from its steps. diff --git a/docs/en/v1/api/common/preparePipe.md b/docs/en/v1/api/common/preparePipe.md new file mode 100644 index 000000000..e3bd311cd --- /dev/null +++ b/docs/en/v1/api/common/preparePipe.md @@ -0,0 +1,60 @@ +--- +outline: [2, 3] +description: "preparePipe() declares synchronous pipeline steps once and returns a reusable function whose input can be inferred from its contextual input-to-output contract." +prev: + text: "innerPipe" + link: "/en/v1/api/common/innerPipe" +next: + text: "asyncPipe" + link: "/en/v1/api/common/asyncPipe" +--- + +# preparePipe + +`preparePipe()` declares up to fifteen synchronous transformations once and returns the function that executes them. Reusing that function avoids rebuilding the list of steps at every call. + +Unlike a directly inferred pipe, the returned function may provide its input through a contextual signature. This allows `preparePipe()` to infer the local input and all intermediate outputs without an explicit generic argument. The pattern is particularly useful for recursive functions, where the complete input-to-output contract is usually declared first. + +## Interactive example + + + +## Syntax + +### Explicit input type + +```typescript +const prepared = preparePipe()( + pipe1, + pipe2 +); +``` + +### Contextual input-to-output type + +```typescript +const prepared: (input: Input) => Output = preparePipe()( + pipe1, + pipe2 +); +``` + +## Parameters + +- `Input`: optional upper constraint for the input accepted by the prepared function. +- `pipe1, pipe2, ...`: one to fifteen transformations. Each receives the previous output. + +The local input generic is inferred from the resulting function's contextual type and is used by the first step and by the returned function. + +## Return value + +A reusable function that accepts the inferred input and synchronously returns the final pipeline output. + +## See also + +- [`innerPipe`](/en/v1/api/common/innerPipe) - Builds a reusable pipe directly from its steps. +- [`prepareAsyncPipe`](/en/v1/api/common/prepareAsyncPipe) - Maybe-promise asynchronous variant. diff --git a/docs/en/v1/api/either/index.md b/docs/en/v1/api/either/index.md index c4178845c..e8c3f7c4a 100644 --- a/docs/en/v1/api/either/index.md +++ b/docs/en/v1/api/either/index.md @@ -195,6 +195,9 @@ Type guard based on the literal information to precisely target a business case. ### [whenHasInformation](/en/v1/api/either/whenHasInformation) Pattern matching that triggers a function when the information (or a list of infos) matches. +### [whenIsSelected](/en/v1/api/either/whenIsSelected) +Runs a callback for the informations selected with `true` in an exhaustive selector and forwards all other inputs unchanged. + ### [matchInformation](/en/v1/api/either/matchInformation) Exhaustive pattern matching by information where every Either case must be handled. diff --git a/docs/en/v1/api/either/matchInformation.md b/docs/en/v1/api/either/matchInformation.md index af46f0404..66532a766 100644 --- a/docs/en/v1/api/either/matchInformation.md +++ b/docs/en/v1/api/either/matchInformation.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "Exhaustive pattern matching on Either information. Every information case must be handled." prev: - text: "whenHasInformation" - link: "/en/v1/api/either/whenHasInformation" + text: "whenIsSelected" + link: "/en/v1/api/either/whenIsSelected" next: text: "matchInformationOtherwise" link: "/en/v1/api/either/matchInformationOtherwise" diff --git a/docs/en/v1/api/either/whenHasInformation.md b/docs/en/v1/api/either/whenHasInformation.md index 1ea47b213..53c174639 100644 --- a/docs/en/v1/api/either/whenHasInformation.md +++ b/docs/en/v1/api/either/whenHasInformation.md @@ -5,8 +5,8 @@ prev: text: "hasInformation" link: "/en/v1/api/either/hasInformation" next: - text: "matchInformation" - link: "/en/v1/api/either/matchInformation" + text: "whenIsSelected" + link: "/en/v1/api/either/whenIsSelected" --- # whenHasInformation diff --git a/docs/en/v1/api/either/whenIsSelected.md b/docs/en/v1/api/either/whenIsSelected.md new file mode 100644 index 000000000..9fc1f2cd6 --- /dev/null +++ b/docs/en/v1/api/either/whenIsSelected.md @@ -0,0 +1,71 @@ +--- +outline: [2, 3] +description: "Runs a callback on the unwrapped Either payloads selected by an exhaustive information selector and forwards all other inputs unchanged." +prev: + text: "whenHasInformation" + link: "/en/v1/api/either/whenHasInformation" +next: + text: "matchInformation" + link: "/en/v1/api/either/matchInformation" +--- + +# whenIsSelected + +Runs a callback on the unwrapped `Either` payloads selected by an exhaustive information selector and forwards all other inputs unchanged. + +The selector maps every possible information of the input union to `true` or `false`. A `true` entry executes the callback with the unwrapped payload; a `false` entry preserves the original `Either`. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function whenIsSelected< + GenericInput extends unknown, + GenericSelector extends Record, + GenericOutput, +>( + input: GenericInput, + selector: GenericSelector, + theFunction: (value: UnwrappedSelectedInputs) => GenericOutput, +): GenericOutput | UnselectedInputs +``` + +### Curried signature + +```typescript +function whenIsSelected< + GenericInput extends unknown, + GenericSelector extends Record, + GenericOutput, +>( + selector: GenericSelector, + theFunction: (value: UnwrappedSelectedInputs) => GenericOutput, +): (input: GenericInput) => GenericOutput | UnselectedInputs +``` + +## Parameters + +- `selector`: Exhaustive object mapping every possible input information to `true` or `false`. +- `theFunction`: Callback receiving the unwrapped payload of the selected informations. +- `input`: Value to process immediately, or later through the curried form. + +## Return value + +Returns the callback result when the current information is selected with `true`. Otherwise, it returns the original input unchanged. + +When a selector entry is typed as `boolean`, the return type includes both the callback result and the original `Either` for that information. + +## See also + +- [`whenHasInformation`](/en/v1/api/either/whenHasInformation) - Selects one or several informations without an exhaustive selector. +- [`unwrapSelection`](/en/v1/api/either/unwrapSelection) - Unwraps selected payloads without applying a callback. +- [`matchInformation`](/en/v1/api/either/matchInformation) - Exhaustive callback-based matching by information. diff --git a/docs/en/v1/api/pattern/index.md b/docs/en/v1/api/pattern/index.md index aac156780..af934527c 100644 --- a/docs/en/v1/api/pattern/index.md +++ b/docs/en/v1/api/pattern/index.md @@ -28,6 +28,18 @@ import * as P from "@duplojs/utils/pattern"; ### [match](/fr/v1/api/pattern/match) Associates a pattern and a transformation function. Returns a `PatternResult` when the input matches the pattern exactly (primitive, tuple, object, union...). +### [matchWithString](/en/v1/api/pattern/matchWithString) +Exhaustively dispatches a string literal union to typed handlers. + +### [matchWithStringOtherwise](/en/v1/api/pattern/matchWithStringOtherwise) +Handles selected string literals and sends remaining cases to a typed fallback. + +### [matchWithNumber](/en/v1/api/pattern/matchWithNumber) +Exhaustively dispatches a number literal union to typed handlers. + +### [matchWithNumberOtherwise](/en/v1/api/pattern/matchWithNumberOtherwise) +Handles selected number literals and sends remaining cases to a typed fallback. + ### [when](/fr/v1/api/pattern/when) Adds a guard (type predicate or boolean) in a pipeline. If the condition is true, the associated function is executed and its result is wrapped. diff --git a/docs/en/v1/api/pattern/match.md b/docs/en/v1/api/pattern/match.md index 5a372c9f4..c43ce70fe 100644 --- a/docs/en/v1/api/pattern/match.md +++ b/docs/en/v1/api/pattern/match.md @@ -5,8 +5,8 @@ prev: text: "Pattern" link: "/en/v1/api/pattern/" next: - text: "when" - link: "/en/v1/api/pattern/when" + text: "matchWithString" + link: "/en/v1/api/pattern/matchWithString" --- # match diff --git a/docs/en/v1/api/pattern/matchWithNumber.md b/docs/en/v1/api/pattern/matchWithNumber.md new file mode 100644 index 000000000..664cc58a6 --- /dev/null +++ b/docs/en/v1/api/pattern/matchWithNumber.md @@ -0,0 +1,55 @@ +--- +outline: [2, 3] +description: "matchWithNumber() performs exhaustive pattern matching on a number literal union and guarantees processing for every possible value." +prev: + text: "matchWithStringOtherwise" + link: "/en/v1/api/pattern/matchWithStringOtherwise" +next: + text: "matchWithNumberOtherwise" + link: "/en/v1/api/pattern/matchWithNumberOtherwise" +--- + +# matchWithNumber + +`matchWithNumber()` performs exhaustive pattern matching on a number literal or a union of number literals. Every possible value must have exactly one handler, guaranteeing that no case is left unprocessed. The selected callback receives the literal value corresponding to its key, correctly narrowed by TypeScript. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithNumber( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Curried signature + +```typescript +function matchWithNumber( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Parameters + +- `input`: a number literal or a union of number literals. Broad `number` values are rejected. +- `matcher`: an exhaustive object whose keys are exactly the input literals. Each handler receives its corresponding narrowed literal. + +## Return value + +The selected handler result. Its static type is the union of all handler return types. + +## See also + +- [`matchWithString`](/en/v1/api/pattern/matchWithString) - String equivalent. +- [`match`](/en/v1/api/pattern/match) - General-purpose pattern matching. diff --git a/docs/en/v1/api/pattern/matchWithNumberOtherwise.md b/docs/en/v1/api/pattern/matchWithNumberOtherwise.md new file mode 100644 index 000000000..d1e6c3c40 --- /dev/null +++ b/docs/en/v1/api/pattern/matchWithNumberOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithNumberOtherwise() partially matches a number literal union and sends precisely the unhandled values to an otherwise callback." +prev: + text: "matchWithNumber" + link: "/en/v1/api/pattern/matchWithNumber" +next: + text: "when" + link: "/en/v1/api/pattern/when" +--- + +# matchWithNumberOtherwise + +`matchWithNumberOtherwise()` handles selected members of a number literal union. Matcher keys must belong to the input union, but may omit cases. The `otherwise` callback receives only the literals that have no handler. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithNumberOtherwise( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): MatcherResult | Output +``` + +### Curried signature + +```typescript +function matchWithNumberOtherwise( + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): (input: Input) => MatcherResult | Output +``` + +## Parameters + +- `input`: a number literal or literal union; broad `number` values are rejected. +- `matcher`: a partial object whose keys can only be members of `input`. A property may be `undefined` to route that case to `otherwise`. +- `otherwise`: receives the exact union of cases without a handler. + +## Return value + +The selected handler result, or the `otherwise` result for an unhandled value. + +## See also + +- [`matchWithNumber`](/en/v1/api/pattern/matchWithNumber) - Exhaustive number matching. +- [`matchWithStringOtherwise`](/en/v1/api/pattern/matchWithStringOtherwise) - String partial matching. diff --git a/docs/en/v1/api/pattern/matchWithString.md b/docs/en/v1/api/pattern/matchWithString.md new file mode 100644 index 000000000..04bb6f1d9 --- /dev/null +++ b/docs/en/v1/api/pattern/matchWithString.md @@ -0,0 +1,55 @@ +--- +outline: [2, 3] +description: "matchWithString() performs exhaustive pattern matching on a string literal union and guarantees processing for every possible value." +prev: + text: "match" + link: "/en/v1/api/pattern/match" +next: + text: "matchWithStringOtherwise" + link: "/en/v1/api/pattern/matchWithStringOtherwise" +--- + +# matchWithString + +`matchWithString()` performs exhaustive pattern matching on a string literal or a union of string literals. Every possible value must have exactly one handler, guaranteeing that no case is left unprocessed. The selected callback receives the literal value corresponding to its key, correctly narrowed by TypeScript. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithString( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Curried signature + +```typescript +function matchWithString( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Parameters + +- `input`: a string literal or a union of string literals. Broad `string` values are rejected. +- `matcher`: an exhaustive object whose keys are exactly the input literals. Each handler receives its corresponding narrowed literal. + +## Return value + +The selected handler result. Its static type is the union of all handler return types. + +## See also + +- [`matchWithNumber`](/en/v1/api/pattern/matchWithNumber) - Numeric equivalent. +- [`match`](/en/v1/api/pattern/match) - General-purpose pattern matching. diff --git a/docs/en/v1/api/pattern/matchWithStringOtherwise.md b/docs/en/v1/api/pattern/matchWithStringOtherwise.md new file mode 100644 index 000000000..c11fa1b75 --- /dev/null +++ b/docs/en/v1/api/pattern/matchWithStringOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithStringOtherwise() partially matches a string literal union and sends precisely the unhandled values to an otherwise callback." +prev: + text: "matchWithString" + link: "/en/v1/api/pattern/matchWithString" +next: + text: "matchWithNumber" + link: "/en/v1/api/pattern/matchWithNumber" +--- + +# matchWithStringOtherwise + +`matchWithStringOtherwise()` handles selected members of a string literal union. Matcher keys must belong to the input union, but may omit cases. The `otherwise` callback receives only the literals that have no handler. + +## Interactive example + + + +## Syntax + +### Classic signature + +```typescript +function matchWithStringOtherwise( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): MatcherResult | Output +``` + +### Curried signature + +```typescript +function matchWithStringOtherwise( + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): (input: Input) => MatcherResult | Output +``` + +## Parameters + +- `input`: a string literal or literal union; broad `string` values are rejected. +- `matcher`: a partial object whose keys can only be members of `input`. A property may be `undefined` to route that case to `otherwise`. +- `otherwise`: receives the exact union of cases without a handler. + +## Return value + +The selected handler result, or the `otherwise` result for an unhandled value. + +## See also + +- [`matchWithString`](/en/v1/api/pattern/matchWithString) - Exhaustive string matching. +- [`matchWithNumberOtherwise`](/en/v1/api/pattern/matchWithNumberOtherwise) - Numeric partial matching. diff --git a/docs/en/v1/api/pattern/when.md b/docs/en/v1/api/pattern/when.md index e6c23fcf8..3dc6d434b 100644 --- a/docs/en/v1/api/pattern/when.md +++ b/docs/en/v1/api/pattern/when.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "when() adds a guard in the pattern matching pipeline. As soon as predicate returns true, the associated function is executed and its result is wrapped in a PatternResult. With a type guard, the branch is automatically typed with the predicated shape." prev: - text: "match" - link: "/en/v1/api/pattern/match" + text: "matchWithNumberOtherwise" + link: "/en/v1/api/pattern/matchWithNumberOtherwise" next: text: "whenNot" link: "/en/v1/api/pattern/whenNot" diff --git a/docs/examples/v1/api/clean/primitives/operators/matchWithNumber/tryout.doc.ts b/docs/examples/v1/api/clean/primitives/operators/matchWithNumber/tryout.doc.ts new file mode 100644 index 000000000..ec0ced5fc --- /dev/null +++ b/docs/examples/v1/api/clean/primitives/operators/matchWithNumber/tryout.doc.ts @@ -0,0 +1,25 @@ +import { DClean, pipe, type ExpectType } from "@duplojs/utils"; + +const status = DClean.Number.createOrThrow(200 as 200 | 404); + +const result = pipe( + status, + DClean.matchWithNumber({ + 200: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<200 | 404> & DClean.Primitive<200>, + "strict" + >; + + return "success" as const; + }, + 404: () => "missing" as const, + }), +); + +type check = ExpectType< + typeof result, + "success" | "missing", + "strict" +>; diff --git a/docs/examples/v1/api/clean/primitives/operators/matchWithNumberOtherwise/tryout.doc.ts b/docs/examples/v1/api/clean/primitives/operators/matchWithNumberOtherwise/tryout.doc.ts new file mode 100644 index 000000000..70d9cee57 --- /dev/null +++ b/docs/examples/v1/api/clean/primitives/operators/matchWithNumberOtherwise/tryout.doc.ts @@ -0,0 +1,25 @@ +import { DClean, type ExpectType, pipe, unwrap } from "@duplojs/utils"; + +const status = DClean.Number.createOrThrow( + 404 as 200 | 404 | 500, +); + +const result = pipe( + status, + DClean.matchWithNumberOtherwise( + { + 200: () => "success" as const, + }, + (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<200 | 404 | 500> + & DClean.Primitive<404 | 500>, + "strict" + >; + return value; + }, + ), +); + +console.log(unwrap(result)); // 404 diff --git a/docs/examples/v1/api/clean/primitives/operators/matchWithString/tryout.doc.ts b/docs/examples/v1/api/clean/primitives/operators/matchWithString/tryout.doc.ts new file mode 100644 index 000000000..db1428619 --- /dev/null +++ b/docs/examples/v1/api/clean/primitives/operators/matchWithString/tryout.doc.ts @@ -0,0 +1,24 @@ +import { DClean, pipe, type ExpectType } from "@duplojs/utils"; + +const status = DClean.String.createOrThrow( + "success" as "success" | "failure", +); + +const result = pipe( + status, + DClean.matchWithString({ + success: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<"success" | "failure"> + & DClean.Primitive<"success">, + "strict" + >; + + return 200 as const; + }, + failure: () => 500 as const, + }), +); + +type check = ExpectType; diff --git a/docs/examples/v1/api/clean/primitives/operators/matchWithStringOtherwise/tryout.doc.ts b/docs/examples/v1/api/clean/primitives/operators/matchWithStringOtherwise/tryout.doc.ts new file mode 100644 index 000000000..2e2cc374f --- /dev/null +++ b/docs/examples/v1/api/clean/primitives/operators/matchWithStringOtherwise/tryout.doc.ts @@ -0,0 +1,25 @@ +import { DClean, type ExpectType, pipe, unwrap } from "@duplojs/utils"; + +const status = DClean.String.createOrThrow( + "failure" as "success" | "failure" | "pending", +); + +const result = pipe( + status, + DClean.matchWithStringOtherwise( + { + success: () => 200 as const, + }, + (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<"success" | "failure" | "pending"> + & DClean.Primitive<"failure" | "pending">, + "strict" + >; + return value; + }, + ), +); + +console.log(unwrap(result)); // "failure" diff --git a/docs/examples/v1/api/common/prepareAsyncPipe/tryout.doc.ts b/docs/examples/v1/api/common/prepareAsyncPipe/tryout.doc.ts new file mode 100644 index 000000000..533d40731 --- /dev/null +++ b/docs/examples/v1/api/common/prepareAsyncPipe/tryout.doc.ts @@ -0,0 +1,36 @@ +import { type ExpectType, prepareAsyncPipe, DString } from "@duplojs/utils"; + +const numberToString: ( + input: number +) => Promise = prepareAsyncPipe()( + async(value) => Promise.resolve(value), + DString.to, +); + +const firstResult = await numberToString(10); +const secondResult = await numberToString( + await Promise.resolve(42), +); + +type firstCheck = ExpectType< + typeof firstResult, + string, + "strict" +>; + +console.log(firstResult); // "10" +console.log(secondResult); // "42" + +const explicitlyTypedPipe = prepareAsyncPipe()( + async(value) => Promise.resolve(value * 2), + DString.to, +); +const explicitResult = await explicitlyTypedPipe(21); + +type explicitCheck = ExpectType< + typeof explicitResult, + `${number}`, + "strict" +>; + +console.log(explicitResult); // "42" diff --git a/docs/examples/v1/api/common/preparePipe/tryout.doc.ts b/docs/examples/v1/api/common/preparePipe/tryout.doc.ts new file mode 100644 index 000000000..bb5bf6651 --- /dev/null +++ b/docs/examples/v1/api/common/preparePipe/tryout.doc.ts @@ -0,0 +1,31 @@ +import { type ExpectType, preparePipe, DString } from "@duplojs/utils"; + +const numberToString: (input: number) => string = preparePipe()( + DString.to, +); + +const firstResult = numberToString(10); +const secondResult = numberToString(42); + +type firstCheck = ExpectType< + typeof firstResult, + string, + "strict" +>; + +console.log(firstResult); // "10" +console.log(secondResult); // "42" + +const explicitlyTypedPipe = preparePipe()( + (value) => value * 2, + DString.to, +); +const explicitResult = explicitlyTypedPipe(21); + +type explicitCheck = ExpectType< + typeof explicitResult, + `${number}`, + "strict" +>; + +console.log(explicitResult); // "42" diff --git a/docs/examples/v1/api/either/whenIsSelected/tryout.doc.ts b/docs/examples/v1/api/either/whenIsSelected/tryout.doc.ts new file mode 100644 index 000000000..926e0bff1 --- /dev/null +++ b/docs/examples/v1/api/either/whenIsSelected/tryout.doc.ts @@ -0,0 +1,37 @@ +import { E, pipe, type ExpectType } from "@duplojs/utils"; + +const payment = true + ? E.right("payment.accepted", 120 as const) + : E.left("payment.rejected", "insufficient funds" as const); + +const formattedPayment = E.whenIsSelected( + payment, + { + "payment.accepted": true, + "payment.rejected": false, + }, + (amount) => `paid:${amount}` as const, +); + +type formattedPaymentCheck = ExpectType< + typeof formattedPayment, + "paid:120" | E.Left<"payment.rejected", "insufficient funds">, + "strict" +>; + +const pipedPayment = pipe( + payment, + E.whenIsSelected( + { + "payment.accepted": true, + "payment.rejected": false, + }, + (amount) => ({ amount }), + ), +); + +type pipedPaymentCheck = ExpectType< + typeof pipedPayment, + { readonly amount: 120 } | E.Left<"payment.rejected", "insufficient funds">, + "strict" +>; diff --git a/docs/examples/v1/api/pattern/matchWithNumber/tryout.doc.ts b/docs/examples/v1/api/pattern/matchWithNumber/tryout.doc.ts new file mode 100644 index 000000000..0ecf8a80b --- /dev/null +++ b/docs/examples/v1/api/pattern/matchWithNumber/tryout.doc.ts @@ -0,0 +1,25 @@ +import { DPattern, pipe, type ExpectType } from "@duplojs/utils"; + +const status = 200 as 200 | 404; + +const result = pipe( + status, + DPattern.matchWithNumber({ + 200: (value) => { + type check = ExpectType< + typeof value, + 200, + "strict" + >; + + return "success" as const; + }, + 404: () => "missing" as const, + }), +); + +type check = ExpectType< + typeof result, + "success" | "missing", + "strict" +>; diff --git a/docs/examples/v1/api/pattern/matchWithNumberOtherwise/tryout.doc.ts b/docs/examples/v1/api/pattern/matchWithNumberOtherwise/tryout.doc.ts new file mode 100644 index 000000000..c71274023 --- /dev/null +++ b/docs/examples/v1/api/pattern/matchWithNumberOtherwise/tryout.doc.ts @@ -0,0 +1,21 @@ +import { DPattern, type ExpectType, pipe } from "@duplojs/utils"; + +const status = 404 as 200 | 404 | 500; + +const result = pipe( + status, + DPattern.matchWithNumberOtherwise( + { + 200: (value) => { + type check = ExpectType; + return "success" as const; + }, + }, + (value) => { + type check = ExpectType; + return `error: ${value}`; + }, + ), +); + +console.log(result); // "error: 404" diff --git a/docs/examples/v1/api/pattern/matchWithString/tryout.doc.ts b/docs/examples/v1/api/pattern/matchWithString/tryout.doc.ts new file mode 100644 index 000000000..a19c86378 --- /dev/null +++ b/docs/examples/v1/api/pattern/matchWithString/tryout.doc.ts @@ -0,0 +1,25 @@ +import { DPattern, pipe, type ExpectType } from "@duplojs/utils"; + +const status = "success" as "success" | "failure"; + +const result = pipe( + status, + DPattern.matchWithString({ + success: (value) => { + type check = ExpectType< + typeof value, + "success", + "strict" + >; + + return 200 as const; + }, + failure: () => 500 as const, + }), +); + +type check = ExpectType< + typeof result, + 200 | 500, + "strict" +>; diff --git a/docs/examples/v1/api/pattern/matchWithStringOtherwise/tryout.doc.ts b/docs/examples/v1/api/pattern/matchWithStringOtherwise/tryout.doc.ts new file mode 100644 index 000000000..2c1cec5a0 --- /dev/null +++ b/docs/examples/v1/api/pattern/matchWithStringOtherwise/tryout.doc.ts @@ -0,0 +1,21 @@ +import { DPattern, type ExpectType, pipe } from "@duplojs/utils"; + +const status = "failure" as "success" | "failure" | "pending"; + +const result = pipe( + status, + DPattern.matchWithStringOtherwise( + { + success: (value) => { + type check = ExpectType; + return 200 as const; + }, + }, + (value) => { + type check = ExpectType; + return `unhandled: ${value}`; + }, + ), +); + +console.log(result); // "unhandled: failure" diff --git a/docs/fr/v1/api/clean/index.md b/docs/fr/v1/api/clean/index.md index fe141626c..cce9356bb 100644 --- a/docs/fr/v1/api/clean/index.md +++ b/docs/fr/v1/api/clean/index.md @@ -69,6 +69,12 @@ Déclare un agrégat d'actions métier liées qui doivent s'exécuter dans l'ord ### [equal](/fr/v1/api/clean/primitives/operators/equal) Compare deux primitives wrappées (ou une primitive et une valeur brute) avec un type guard. +### [matchWithString](/fr/v1/api/clean/primitives/operators/matchWithString) +Effectue un matching exhaustif avec des clés string brutes et transmet la primitive Clean originale affinée. + +### [matchWithNumber](/fr/v1/api/clean/primitives/operators/matchWithNumber) +Effectue un matching exhaustif avec des clés number brutes et transmet la primitive Clean originale affinée. + ### [add](/fr/v1/api/clean/primitives/operators/add) Additionne deux `Number` (supporte la version currifiée). diff --git a/docs/fr/v1/api/clean/primitives/index.md b/docs/fr/v1/api/clean/primitives/index.md index 6c6fba9fb..83a0fb9d3 100644 --- a/docs/fr/v1/api/clean/primitives/index.md +++ b/docs/fr/v1/api/clean/primitives/index.md @@ -96,6 +96,18 @@ function is( ### [equal](/fr/v1/api/clean/primitives/operators/equal) Compare deux primitives wrappées (ou une primitive et une valeur brute) avec un type guard. +### [matchWithString](/fr/v1/api/clean/primitives/operators/matchWithString) +Effectue un matching exhaustif avec des clés string brutes et transmet la primitive Clean originale affinée. + +### [matchWithStringOtherwise](/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise) +Effectue un matching partiel avec des clés string brutes et transmet les valeurs restantes à un fallback typé. + +### [matchWithNumber](/fr/v1/api/clean/primitives/operators/matchWithNumber) +Effectue un matching exhaustif avec des clés number brutes et transmet la primitive Clean originale affinée. + +### [matchWithNumberOtherwise](/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise) +Effectue un matching partiel avec des clés number brutes et transmet les valeurs restantes à un fallback typé. + ### [add](/fr/v1/api/clean/primitives/operators/add) Additionne deux `Number` (supporte la version currifiée). diff --git a/docs/fr/v1/api/clean/primitives/operators/add.md b/docs/fr/v1/api/clean/primitives/operators/add.md index 48819caa8..5aab403f3 100644 --- a/docs/fr/v1/api/clean/primitives/operators/add.md +++ b/docs/fr/v1/api/clean/primitives/operators/add.md @@ -1,8 +1,8 @@ --- outline: [2, 3] prev: - text: "equal" - link: "/fr/v1/api/clean/primitives/operators/equal" + text: "matchWithNumberOtherwise" + link: "/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise" next: text: "subtract" link: "/fr/v1/api/clean/primitives/operators/subtract" diff --git a/docs/fr/v1/api/clean/primitives/operators/equal.md b/docs/fr/v1/api/clean/primitives/operators/equal.md index ac648fe93..e35b265b0 100644 --- a/docs/fr/v1/api/clean/primitives/operators/equal.md +++ b/docs/fr/v1/api/clean/primitives/operators/equal.md @@ -1,8 +1,8 @@ --- outline: [2, 3] next: - text: "add" - link: "/fr/v1/api/clean/primitives/operators/operators/add" + text: "matchWithString" + link: "/fr/v1/api/clean/primitives/operators/matchWithString" --- # equal diff --git a/docs/fr/v1/api/clean/primitives/operators/matchWithNumber.md b/docs/fr/v1/api/clean/primitives/operators/matchWithNumber.md new file mode 100644 index 000000000..2462c1ced --- /dev/null +++ b/docs/fr/v1/api/clean/primitives/operators/matchWithNumber.md @@ -0,0 +1,59 @@ +--- +outline: [2, 3] +description: "matchWithNumber() effectue un pattern matching exhaustif sur la valeur d’une primitive Clean number et transmet à chaque branche la primitive originale correctement affinée." +prev: + text: "matchWithStringOtherwise" + link: "/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise" +next: + text: "matchWithNumberOtherwise" + link: "/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise" +--- + +# matchWithNumber + +`matchWithNumber()` effectue un pattern matching exhaustif sur la valeur d’une primitive Clean number. Chaque valeur possible doit avoir une branche de traitement. + +Une primitive Clean étant un objet wrappé, elle ne peut pas servir directement de clé au matcher. Les clés sont donc les valeurs `number` brutes. Lorsqu’une clé correspond, son callback reçoit la primitive Clean originale, affinée sur cette valeur. Ses informations `Primitive`, `ConstrainedType` ou `NewType` sont ainsi conservées. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithNumber, Matcher>( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Signature currifiée + +```typescript +function matchWithNumber, Matcher>( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Paramètres + +- `input` : une primitive Clean contenant une valeur `number`, y compris une valeur contrainte ou un new type. +- `matcher` : un objet exhaustif indexé par les valeurs `number` brutes possibles. Une `Primitive` large nécessite un `Record` indexé. + +Chaque clé doit avoir exactement un handler. Une clé manquante ou étrangère à l’union est refusée par TypeScript. Le handler sélectionné reçoit l’objet Clean original affiné avec `Primitive`. + +## Valeur de retour + +Le résultat du handler sélectionné, typé comme l’union des retours de tous les handlers. + +## Voir aussi + +- [`matchWithString`](/fr/v1/api/clean/primitives/operators/matchWithString) - Équivalent pour les primitives string. +- [`equal`](/fr/v1/api/clean/primitives/operators/equal) - Compare les valeurs de primitives wrappées. diff --git a/docs/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise.md b/docs/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise.md new file mode 100644 index 000000000..127df61fe --- /dev/null +++ b/docs/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithNumberOtherwise() traite partiellement une primitive Clean number tout en préservant la primitive originale affinée dans les handlers et otherwise." +prev: + text: "matchWithNumber" + link: "/fr/v1/api/clean/primitives/operators/matchWithNumber" +next: + text: "add" + link: "/fr/v1/api/clean/primitives/operators/add" +--- + +# matchWithNumberOtherwise + +`matchWithNumberOtherwise()` traite certaines valeurs d’une primitive Clean number. Les valeurs number brutes forment les clés du matcher. Les cas manquants sont transmis à `otherwise`, qui reçoit la primitive originale affinée sur les valeurs restantes. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithNumberOtherwise, Matcher, Output>( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): MatcherResult | Output +``` + +### Signature currifiée + +```typescript +function matchWithNumberOtherwise, Matcher, Output>( + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): (input: Input) => MatcherResult | Output +``` + +## Paramètres + +- `input` : une primitive Clean number contenant des valeurs littérales. +- `matcher` : un objet partiel indexé par les valeurs brutes de la primitive. Les clés inconnues sont refusées. +- `otherwise` : reçoit la primitive `Primitive`, `ConstrainedType` ou `NewType` originale, affinée sur les valeurs sans handler. + +## Valeur de retour + +Le résultat du handler sélectionné, ou celui d’`otherwise` pour une valeur wrappée non traitée. + +## Voir aussi + +- [`matchWithNumber`](/fr/v1/api/clean/primitives/operators/matchWithNumber) - Matching exhaustif de primitive. +- [`matchWithStringOtherwise`](/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise) - Équivalent pour une primitive string. diff --git a/docs/fr/v1/api/clean/primitives/operators/matchWithString.md b/docs/fr/v1/api/clean/primitives/operators/matchWithString.md new file mode 100644 index 000000000..7233fcb77 --- /dev/null +++ b/docs/fr/v1/api/clean/primitives/operators/matchWithString.md @@ -0,0 +1,59 @@ +--- +outline: [2, 3] +description: "matchWithString() effectue un pattern matching exhaustif sur la valeur d’une primitive Clean string et transmet à chaque branche la primitive originale correctement affinée." +prev: + text: "equal" + link: "/fr/v1/api/clean/primitives/operators/equal" +next: + text: "matchWithStringOtherwise" + link: "/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise" +--- + +# matchWithString + +`matchWithString()` effectue un pattern matching exhaustif sur la valeur d’une primitive Clean string. Chaque valeur possible doit avoir une branche de traitement. + +Une primitive Clean étant un objet wrappé, elle ne peut pas servir directement de clé au matcher. Les clés sont donc les valeurs `string` brutes. Lorsqu’une clé correspond, son callback reçoit la primitive Clean originale, affinée sur cette valeur. Ses informations `Primitive`, `ConstrainedType` ou `NewType` sont ainsi conservées. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithString, Matcher>( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Signature currifiée + +```typescript +function matchWithString, Matcher>( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Paramètres + +- `input` : une primitive Clean contenant une valeur `string`, y compris une valeur contrainte ou un new type. +- `matcher` : un objet exhaustif indexé par les valeurs `string` brutes possibles. Une `Primitive` large nécessite un `Record` indexé. + +Chaque clé doit avoir exactement un handler. Une clé manquante ou étrangère à l’union est refusée par TypeScript. Le handler sélectionné reçoit l’objet Clean original affiné avec `Primitive`. + +## Valeur de retour + +Le résultat du handler sélectionné, typé comme l’union des retours de tous les handlers. + +## Voir aussi + +- [`matchWithNumber`](/fr/v1/api/clean/primitives/operators/matchWithNumber) - Équivalent pour les primitives numériques. +- [`equal`](/fr/v1/api/clean/primitives/operators/equal) - Compare les valeurs de primitives wrappées. diff --git a/docs/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise.md b/docs/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise.md new file mode 100644 index 000000000..ca90e1d57 --- /dev/null +++ b/docs/fr/v1/api/clean/primitives/operators/matchWithStringOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithStringOtherwise() traite partiellement une primitive Clean string tout en préservant la primitive originale affinée dans les handlers et otherwise." +prev: + text: "matchWithString" + link: "/fr/v1/api/clean/primitives/operators/matchWithString" +next: + text: "matchWithNumber" + link: "/fr/v1/api/clean/primitives/operators/matchWithNumber" +--- + +# matchWithStringOtherwise + +`matchWithStringOtherwise()` traite certaines valeurs d’une primitive Clean string. Les valeurs string brutes forment les clés du matcher. Les cas manquants sont transmis à `otherwise`, qui reçoit la primitive originale affinée sur les valeurs restantes. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithStringOtherwise, Matcher, Output>( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): MatcherResult | Output +``` + +### Signature currifiée + +```typescript +function matchWithStringOtherwise, Matcher, Output>( + matcher: Matcher, + otherwise: (value: UnhandledPrimitive) => Output +): (input: Input) => MatcherResult | Output +``` + +## Paramètres + +- `input` : une primitive Clean string contenant des valeurs littérales. +- `matcher` : un objet partiel indexé par les valeurs brutes de la primitive. Les clés inconnues sont refusées. +- `otherwise` : reçoit la primitive `Primitive`, `ConstrainedType` ou `NewType` originale, affinée sur les valeurs sans handler. + +## Valeur de retour + +Le résultat du handler sélectionné, ou celui d’`otherwise` pour une valeur wrappée non traitée. + +## Voir aussi + +- [`matchWithString`](/fr/v1/api/clean/primitives/operators/matchWithString) - Matching exhaustif de primitive. +- [`matchWithNumberOtherwise`](/fr/v1/api/clean/primitives/operators/matchWithNumberOtherwise) - Équivalent pour une primitive number. diff --git a/docs/fr/v1/api/common/asyncInnerPipe.md b/docs/fr/v1/api/common/asyncInnerPipe.md index 99d4ca564..6d564b7ee 100644 --- a/docs/fr/v1/api/common/asyncInnerPipe.md +++ b/docs/fr/v1/api/common/asyncInnerPipe.md @@ -5,8 +5,8 @@ prev: text: "asyncPipe" link: "/fr/v1/api/common/asyncPipe" next: - text: "forward" - link: "/fr/v1/api/common/forward" + text: "prepareAsyncPipe" + link: "/fr/v1/api/common/prepareAsyncPipe" --- # asyncInnerPipe diff --git a/docs/fr/v1/api/common/asyncPipe.md b/docs/fr/v1/api/common/asyncPipe.md index f7c200f9e..b38b9ba91 100644 --- a/docs/fr/v1/api/common/asyncPipe.md +++ b/docs/fr/v1/api/common/asyncPipe.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "La méthode asyncPipe() enchaîne des fonctions asynchrones (promesses ou FutureEither) en série. Chaque étape attend la résolution de la précédente et la dernière valeur est retournée dans une promesse." prev: - text: "innerPipe" - link: "/fr/v1/api/common/innerPipe" + text: "preparePipe" + link: "/fr/v1/api/common/preparePipe" next: text: "asyncInnerPipe" link: "/fr/v1/api/common/asyncInnerPipe" diff --git a/docs/fr/v1/api/common/forward.md b/docs/fr/v1/api/common/forward.md index 53b135c8d..b1610af41 100644 --- a/docs/fr/v1/api/common/forward.md +++ b/docs/fr/v1/api/common/forward.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "La fonction forward() renvoie l'argument passé sans le modifier. Utile pour homogénéiser une API qui attend une fonction, ou pour améliorer la lisibilité dans un pipeline." prev: - text: "asyncInnerPipe" - link: "/fr/v1/api/common/asyncInnerPipe" + text: "prepareAsyncPipe" + link: "/fr/v1/api/common/prepareAsyncPipe" next: text: "forwardLog" link: "/fr/v1/api/common/forwardLog" diff --git a/docs/fr/v1/api/common/index.md b/docs/fr/v1/api/common/index.md index 8f2a5cce8..348d4ecd6 100644 --- a/docs/fr/v1/api/common/index.md +++ b/docs/fr/v1/api/common/index.md @@ -32,12 +32,18 @@ Compose des fonctions synchrones en chaînant un même input. ### [innerPipe](/fr/v1/api/common/innerPipe) Prépare un pipe réutilisable qui retourne une fonction à appliquer plus tard. +### [preparePipe](/fr/v1/api/common/preparePipe) +Déclare des étapes synchrones réutilisables avec inférence contextuelle de l’entrée-sortie. + ### [asyncPipe](/fr/v1/api/common/asyncPipe) Compose des promesses ou des `FutureEither` de façon séquentielle en renvoyant un `Promise`. ### [asyncInnerPipe](/fr/v1/api/common/asyncInnerPipe) Version curried d'`asyncPipe` qui renvoie une fonction prête à accepter une valeur (sync ou async). +### [prepareAsyncPipe](/fr/v1/api/common/prepareAsyncPipe) +Déclare des étapes synchrones ou asynchrones réutilisables avec inférence contextuelle de l’entrée-sortie. + ### [forward](/fr/v1/api/common/forward) Fonction identité pratique dans un chainage pour passer la valeur telle quelle. diff --git a/docs/fr/v1/api/common/innerPipe.md b/docs/fr/v1/api/common/innerPipe.md index cd162ee1a..7f5bb6cda 100644 --- a/docs/fr/v1/api/common/innerPipe.md +++ b/docs/fr/v1/api/common/innerPipe.md @@ -5,8 +5,8 @@ prev: text: "pipe" link: "/fr/v1/api/common/pipe" next: - text: "asyncPipe" - link: "/fr/v1/api/common/asyncPipe" + text: "preparePipe" + link: "/fr/v1/api/common/preparePipe" --- # innerPipe diff --git a/docs/fr/v1/api/common/prepareAsyncPipe.md b/docs/fr/v1/api/common/prepareAsyncPipe.md new file mode 100644 index 000000000..05f10fef0 --- /dev/null +++ b/docs/fr/v1/api/common/prepareAsyncPipe.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "prepareAsyncPipe() déclare une seule fois les étapes synchrones ou asynchrones d’un pipeline et retourne une fonction réutilisable avec inférence contextuelle de l’entrée." +prev: + text: "asyncInnerPipe" + link: "/fr/v1/api/common/asyncInnerPipe" +next: + text: "forward" + link: "/fr/v1/api/common/forward" +--- + +# prepareAsyncPipe + +`prepareAsyncPipe()` est l’équivalent asynchrone de `preparePipe()`. Il déclare une seule fois jusqu’à quinze transformations, accepte des étapes synchrones ou retournant une promesse, puis produit une fonction asynchrone réutilisable. + +La fonction préparée accepte une entrée directe ou une promesse. Sa signature contextuelle d’entrée-sortie peut piloter l’inférence de l’entrée locale, notamment lorsque le pipeline participe à une fonction asynchrone récursive. + +## Exemple interactif + + + +## Syntaxe + +### Type d’entrée explicite + +```typescript +const prepared = prepareAsyncPipe()( + pipe1, + pipe2 +); +``` + +### Type contextuel d’entrée-sortie + +```typescript +const prepared: (input: Input) => Promise = prepareAsyncPipe()( + pipe1, + pipe2 +); +``` + +## Paramètres + +- `Input` : contrainte supérieure optionnelle de l’entrée directe ou promise. +- `pipe1, pipe2, ...` : une à quinze transformations retournant une valeur ou une promesse. + +## Valeur de retour + +Une fonction réutilisable acceptant `Input | PromiseLike` et retournant une promesse de la sortie finale. + +## Voir aussi + +- [`preparePipe`](/fr/v1/api/common/preparePipe) - Variante préparée synchrone. +- [`asyncInnerPipe`](/fr/v1/api/common/asyncInnerPipe) - Composition asynchrone réutilisable inférée directement depuis ses étapes. diff --git a/docs/fr/v1/api/common/preparePipe.md b/docs/fr/v1/api/common/preparePipe.md new file mode 100644 index 000000000..f65d71ea7 --- /dev/null +++ b/docs/fr/v1/api/common/preparePipe.md @@ -0,0 +1,60 @@ +--- +outline: [2, 3] +description: "preparePipe() déclare une seule fois les étapes d’un pipeline synchrone et retourne une fonction réutilisable dont l’entrée peut être inférée depuis son contrat contextuel." +prev: + text: "innerPipe" + link: "/fr/v1/api/common/innerPipe" +next: + text: "asyncPipe" + link: "/fr/v1/api/common/asyncPipe" +--- + +# preparePipe + +`preparePipe()` déclare une seule fois jusqu’à quinze transformations synchrones et retourne la fonction qui les exécute. Réutiliser cette fonction évite de reconstruire la liste des étapes à chaque appel. + +Contrairement à un pipe directement inféré, la fonction retournée peut fournir son entrée par une signature contextuelle. `preparePipe()` peut ainsi inférer l’entrée locale et toutes les sorties intermédiaires sans argument générique explicite. Ce pattern est particulièrement utile pour les fonctions récursives, dont le contrat complet entrée-sortie est généralement déclaré en premier. + +## Exemple interactif + + + +## Syntaxe + +### Type d’entrée explicite + +```typescript +const prepared = preparePipe()( + pipe1, + pipe2 +); +``` + +### Type contextuel d’entrée-sortie + +```typescript +const prepared: (input: Input) => Output = preparePipe()( + pipe1, + pipe2 +); +``` + +## Paramètres + +- `Input` : contrainte supérieure optionnelle de l’entrée acceptée par la fonction préparée. +- `pipe1, pipe2, ...` : une à quinze transformations. Chacune reçoit la sortie précédente. + +Le générique local d’entrée est inféré depuis le type contextuel de la fonction résultante. Il est utilisé par la première étape et par la fonction retournée. + +## Valeur de retour + +Une fonction réutilisable qui accepte l’entrée inférée et retourne synchroniquement la sortie finale du pipeline. + +## Voir aussi + +- [`innerPipe`](/fr/v1/api/common/innerPipe) - Construit directement un pipe réutilisable depuis ses étapes. +- [`prepareAsyncPipe`](/fr/v1/api/common/prepareAsyncPipe) - Variante asynchrone compatible avec les promesses. diff --git a/docs/fr/v1/api/either/index.md b/docs/fr/v1/api/either/index.md index 70112117b..121ec4da3 100644 --- a/docs/fr/v1/api/either/index.md +++ b/docs/fr/v1/api/either/index.md @@ -110,6 +110,9 @@ Type guard basé sur l'information littérale pour cibler précisément un cas m ### [whenHasInformation](/fr/v1/api/either/whenHasInformation) Pattern matching qui déclenche une fonction quand l'information (ou une liste d'infos) correspond. +### [whenIsSelected](/fr/v1/api/either/whenIsSelected) +Exécute un callback pour les informations sélectionnées avec `true` dans un sélecteur exhaustif et relaie les autres entrées inchangées. + ### [matchInformation](/fr/v1/api/either/matchInformation) Pattern matching exhaustif par information où chaque cas Either doit être traité. diff --git a/docs/fr/v1/api/either/matchInformation.md b/docs/fr/v1/api/either/matchInformation.md index 64f199373..26f085bb7 100644 --- a/docs/fr/v1/api/either/matchInformation.md +++ b/docs/fr/v1/api/either/matchInformation.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "Pattern matching exhaustif sur l'information d'un Either. Chaque information possible doit être traitée." prev: - text: "whenHasInformation" - link: "/fr/v1/api/either/whenHasInformation" + text: "whenIsSelected" + link: "/fr/v1/api/either/whenIsSelected" next: text: "matchInformationOtherwise" link: "/fr/v1/api/either/matchInformationOtherwise" diff --git a/docs/fr/v1/api/either/whenHasInformation.md b/docs/fr/v1/api/either/whenHasInformation.md index 5e1c8a195..65abc2e53 100644 --- a/docs/fr/v1/api/either/whenHasInformation.md +++ b/docs/fr/v1/api/either/whenHasInformation.md @@ -5,8 +5,8 @@ prev: text: "hasInformation" link: "/fr/v1/api/either/hasInformation" next: - text: "matchInformation" - link: "/fr/v1/api/either/matchInformation" + text: "whenIsSelected" + link: "/fr/v1/api/either/whenIsSelected" --- # whenHasInformation diff --git a/docs/fr/v1/api/either/whenIsSelected.md b/docs/fr/v1/api/either/whenIsSelected.md new file mode 100644 index 000000000..d5e0a6a87 --- /dev/null +++ b/docs/fr/v1/api/either/whenIsSelected.md @@ -0,0 +1,71 @@ +--- +outline: [2, 3] +description: "Exécute un callback sur les payloads Either sélectionnés par un sélecteur exhaustif d'informations et relaie les autres entrées inchangées." +prev: + text: "whenHasInformation" + link: "/fr/v1/api/either/whenHasInformation" +next: + text: "matchInformation" + link: "/fr/v1/api/either/matchInformation" +--- + +# whenIsSelected + +Exécute un callback sur les payloads `Either` sélectionnés par un sélecteur exhaustif d'informations et relaie les autres entrées inchangées. + +Le sélecteur associe chaque information possible de l'union d'entrée à `true` ou `false`. Une entrée `true` exécute le callback avec le payload unwrap ; une entrée `false` conserve l'`Either` original. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function whenIsSelected< + GenericInput extends unknown, + GenericSelector extends Record, + GenericOutput, +>( + input: GenericInput, + selector: GenericSelector, + theFunction: (value: UnwrappedSelectedInputs) => GenericOutput, +): GenericOutput | UnselectedInputs +``` + +### Signature currifiée + +```typescript +function whenIsSelected< + GenericInput extends unknown, + GenericSelector extends Record, + GenericOutput, +>( + selector: GenericSelector, + theFunction: (value: UnwrappedSelectedInputs) => GenericOutput, +): (input: GenericInput) => GenericOutput | UnselectedInputs +``` + +## Paramètres + +- `selector` : Objet exhaustif associant chaque information possible de l'entrée à `true` ou `false`. +- `theFunction` : Callback recevant le payload unwrap des informations sélectionnées. +- `input` : Valeur à traiter immédiatement, ou plus tard via la forme currifiée. + +## Valeur de retour + +Retourne le résultat du callback lorsque l'information courante est sélectionnée avec `true`. Sinon, renvoie l'entrée originale inchangée. + +Quand une entrée du sélecteur est typée `boolean`, le type de retour inclut à la fois le résultat du callback et l'`Either` original pour cette information. + +## Voir aussi + +- [`whenHasInformation`](/fr/v1/api/either/whenHasInformation) - Sélectionne une ou plusieurs informations sans sélecteur exhaustif. +- [`unwrapSelection`](/fr/v1/api/either/unwrapSelection) - Unwrap les payloads sélectionnés sans appliquer de callback. +- [`matchInformation`](/fr/v1/api/either/matchInformation) - Matching exhaustif par information avec callbacks. diff --git a/docs/fr/v1/api/pattern/index.md b/docs/fr/v1/api/pattern/index.md index 32119363f..aa006768a 100644 --- a/docs/fr/v1/api/pattern/index.md +++ b/docs/fr/v1/api/pattern/index.md @@ -28,6 +28,18 @@ import * as P from "@duplojs/utils/pattern"; ### [match](/fr/v1/api/pattern/match) Associe un motif et une fonction de transformation. Retourne un `PatternResult` quand l'entrée correspond exactement au motif (primitive, tuple, objet, union...). +### [matchWithString](/fr/v1/api/pattern/matchWithString) +Distribue exhaustivement une union de chaînes littérales vers des handlers typés. + +### [matchWithStringOtherwise](/fr/v1/api/pattern/matchWithStringOtherwise) +Traite certaines chaînes littérales et transmet les cas restants à un fallback typé. + +### [matchWithNumber](/fr/v1/api/pattern/matchWithNumber) +Distribue exhaustivement une union de nombres littéraux vers des handlers typés. + +### [matchWithNumberOtherwise](/fr/v1/api/pattern/matchWithNumberOtherwise) +Traite certains nombres littéraux et transmet les cas restants à un fallback typé. + ### [when](/fr/v1/api/pattern/when) Ajoute une garde (type predicate ou boolean) dans un pipeline. Si la condition est vraie, la fonction associée est exécutée et son résultat est encapsulé. diff --git a/docs/fr/v1/api/pattern/match.md b/docs/fr/v1/api/pattern/match.md index ddde9f46c..f0e57bb0d 100644 --- a/docs/fr/v1/api/pattern/match.md +++ b/docs/fr/v1/api/pattern/match.md @@ -5,8 +5,8 @@ prev: text: "Pattern" link: "/fr/v1/api/pattern/" next: - text: "when" - link: "/fr/v1/api/pattern/when" + text: "matchWithString" + link: "/fr/v1/api/pattern/matchWithString" --- # match diff --git a/docs/fr/v1/api/pattern/matchWithNumber.md b/docs/fr/v1/api/pattern/matchWithNumber.md new file mode 100644 index 000000000..dd1317217 --- /dev/null +++ b/docs/fr/v1/api/pattern/matchWithNumber.md @@ -0,0 +1,55 @@ +--- +outline: [2, 3] +description: "matchWithNumber() effectue un pattern matching exhaustif sur une union de nombres littéraux et garantit un traitement pour chaque valeur possible." +prev: + text: "matchWithStringOtherwise" + link: "/fr/v1/api/pattern/matchWithStringOtherwise" +next: + text: "matchWithNumberOtherwise" + link: "/fr/v1/api/pattern/matchWithNumberOtherwise" +--- + +# matchWithNumber + +`matchWithNumber()` effectue un pattern matching exhaustif sur un nombre littéral ou une union de nombres littéraux. Chaque valeur possible doit posséder exactement un handler, ce qui garantit qu’aucun cas ne reste sans traitement. Le callback sélectionné reçoit la valeur littérale correspondant à sa clé, correctement affinée par TypeScript. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithNumber( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Signature currifiée + +```typescript +function matchWithNumber( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Paramètres + +- `input` : un nombre littéral ou une union de nombres littéraux. Le type large `number` est refusé. +- `matcher` : un objet exhaustif dont les clés correspondent exactement aux littéraux d’entrée. Chaque handler reçoit son littéral correctement affiné. + +## Valeur de retour + +Le résultat du handler sélectionné. Son type statique est l’union des retours de tous les handlers. + +## Voir aussi + +- [`matchWithString`](/fr/v1/api/pattern/matchWithString) - Équivalent pour les chaînes. +- [`match`](/fr/v1/api/pattern/match) - Pattern matching généraliste. diff --git a/docs/fr/v1/api/pattern/matchWithNumberOtherwise.md b/docs/fr/v1/api/pattern/matchWithNumberOtherwise.md new file mode 100644 index 000000000..73358e97c --- /dev/null +++ b/docs/fr/v1/api/pattern/matchWithNumberOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithNumberOtherwise() traite partiellement une union de nombres littéraux et transmet précisément les valeurs non traitées à un callback otherwise." +prev: + text: "matchWithNumber" + link: "/fr/v1/api/pattern/matchWithNumber" +next: + text: "when" + link: "/fr/v1/api/pattern/when" +--- + +# matchWithNumberOtherwise + +`matchWithNumberOtherwise()` traite certains membres d’une union de nombres littéraux. Les clés du matcher doivent appartenir à l’union d’entrée, mais certains cas peuvent être omis. Le callback `otherwise` reçoit uniquement les littéraux sans handler. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithNumberOtherwise( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): MatcherResult | Output +``` + +### Signature currifiée + +```typescript +function matchWithNumberOtherwise( + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): (input: Input) => MatcherResult | Output +``` + +## Paramètres + +- `input` : un nombre littéral ou une union de littéraux ; le type large `number` est refusé. +- `matcher` : un objet partiel dont les clés doivent appartenir à `input`. Une propriété peut valoir `undefined` pour rediriger ce cas vers `otherwise`. +- `otherwise` : reçoit l’union exacte des cas sans handler. + +## Valeur de retour + +Le résultat du handler sélectionné, ou celui d’`otherwise` pour une valeur non traitée. + +## Voir aussi + +- [`matchWithNumber`](/fr/v1/api/pattern/matchWithNumber) - Matching exhaustif de nombres. +- [`matchWithStringOtherwise`](/fr/v1/api/pattern/matchWithStringOtherwise) - Matching partiel de chaînes. diff --git a/docs/fr/v1/api/pattern/matchWithString.md b/docs/fr/v1/api/pattern/matchWithString.md new file mode 100644 index 000000000..bf6ce6fa5 --- /dev/null +++ b/docs/fr/v1/api/pattern/matchWithString.md @@ -0,0 +1,55 @@ +--- +outline: [2, 3] +description: "matchWithString() effectue un pattern matching exhaustif sur une union de chaînes littérales et garantit un traitement pour chaque valeur possible." +prev: + text: "match" + link: "/fr/v1/api/pattern/match" +next: + text: "matchWithStringOtherwise" + link: "/fr/v1/api/pattern/matchWithStringOtherwise" +--- + +# matchWithString + +`matchWithString()` effectue un pattern matching exhaustif sur une chaîne littérale ou une union de chaînes littérales. Chaque valeur possible doit posséder exactement un handler, ce qui garantit qu’aucun cas ne reste sans traitement. Le callback sélectionné reçoit la valeur littérale correspondant à sa clé, correctement affinée par TypeScript. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithString( + input: Input, + matcher: Matcher +): ReturnType +``` + +### Signature currifiée + +```typescript +function matchWithString( + matcher: Matcher +): (input: Input) => ReturnType +``` + +## Paramètres + +- `input` : une chaîne littérale ou une union de chaînes littérales. Le type large `string` est refusé. +- `matcher` : un objet exhaustif dont les clés correspondent exactement aux littéraux d’entrée. Chaque handler reçoit son littéral correctement affiné. + +## Valeur de retour + +Le résultat du handler sélectionné. Son type statique est l’union des retours de tous les handlers. + +## Voir aussi + +- [`matchWithNumber`](/fr/v1/api/pattern/matchWithNumber) - Équivalent numérique. +- [`match`](/fr/v1/api/pattern/match) - Pattern matching généraliste. diff --git a/docs/fr/v1/api/pattern/matchWithStringOtherwise.md b/docs/fr/v1/api/pattern/matchWithStringOtherwise.md new file mode 100644 index 000000000..59ee7aa40 --- /dev/null +++ b/docs/fr/v1/api/pattern/matchWithStringOtherwise.md @@ -0,0 +1,58 @@ +--- +outline: [2, 3] +description: "matchWithStringOtherwise() traite partiellement une union de chaînes littérales et transmet précisément les valeurs non traitées à un callback otherwise." +prev: + text: "matchWithString" + link: "/fr/v1/api/pattern/matchWithString" +next: + text: "matchWithNumber" + link: "/fr/v1/api/pattern/matchWithNumber" +--- + +# matchWithStringOtherwise + +`matchWithStringOtherwise()` traite certains membres d’une union de chaînes littérales. Les clés du matcher doivent appartenir à l’union d’entrée, mais certains cas peuvent être omis. Le callback `otherwise` reçoit uniquement les littéraux sans handler. + +## Exemple interactif + + + +## Syntaxe + +### Signature classique + +```typescript +function matchWithStringOtherwise( + input: Input, + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): MatcherResult | Output +``` + +### Signature currifiée + +```typescript +function matchWithStringOtherwise( + matcher: Matcher, + otherwise: (value: UnhandledValues) => Output +): (input: Input) => MatcherResult | Output +``` + +## Paramètres + +- `input` : une chaîne littérale ou une union de littéraux ; le type large `string` est refusé. +- `matcher` : un objet partiel dont les clés doivent appartenir à `input`. Une propriété peut valoir `undefined` pour rediriger ce cas vers `otherwise`. +- `otherwise` : reçoit l’union exacte des cas sans handler. + +## Valeur de retour + +Le résultat du handler sélectionné, ou celui d’`otherwise` pour une valeur non traitée. + +## Voir aussi + +- [`matchWithString`](/fr/v1/api/pattern/matchWithString) - Matching exhaustif de chaînes. +- [`matchWithNumberOtherwise`](/fr/v1/api/pattern/matchWithNumberOtherwise) - Matching partiel numérique. diff --git a/docs/fr/v1/api/pattern/when.md b/docs/fr/v1/api/pattern/when.md index 32775cc6e..405388224 100644 --- a/docs/fr/v1/api/pattern/when.md +++ b/docs/fr/v1/api/pattern/when.md @@ -2,8 +2,8 @@ outline: [2, 3] description: "when() ajoute une garde dans le pipeline de pattern matching. Dès que predicate retourne true, la fonction associée est exécutée et son résultat est encapsulé dans un PatternResult. Avec un type guard, la branche est automatiquement typée avec la forme prédicatée." prev: - text: "match" - link: "/fr/v1/api/pattern/match" + text: "matchWithNumberOtherwise" + link: "/fr/v1/api/pattern/matchWithNumberOtherwise" next: text: "whenNot" link: "/fr/v1/api/pattern/whenNot" diff --git a/docs/public/libs/v1/clean/evidence.cjs b/docs/public/libs/v1/clean/evidence.cjs index 5e65e75a9..02baee8e4 100644 --- a/docs/public/libs/v1/clean/evidence.cjs +++ b/docs/public/libs/v1/clean/evidence.cjs @@ -1,8 +1,15 @@ 'use strict'; var kind = require('./kind.cjs'); +var kindClass = require('../common/kindClass.cjs'); const evidenceKind = kind.createCleanKind("evidence"); +class ArrayWithEvidence extends kindClass.kindClass(evidenceKind, Array) { + constructor(array, evidence) { + super(evidence, ...array); + } + static [Symbol.species] = Array; +} function appendEvidence(...args) { if (args.length === 1) { const [evidenceName] = args; @@ -13,6 +20,9 @@ function appendEvidence(...args) { ...(evidenceKind.has(input) && evidenceKind.getValue(input)), [evidenceName]: null, }; + if (input instanceof Array) { + return new ArrayWithEvidence(input, evidence); + } return evidenceKind.addTo(input, evidence); } function hasEvidence(...args) { @@ -36,6 +46,7 @@ function hasEvidence(...args) { return false; } +exports.ArrayWithEvidence = ArrayWithEvidence; exports.appendEvidence = appendEvidence; exports.evidenceKind = evidenceKind; exports.hasEvidence = hasEvidence; diff --git a/docs/public/libs/v1/clean/evidence.d.ts b/docs/public/libs/v1/clean/evidence.d.ts index 79090d801..c497cf0ab 100644 --- a/docs/public/libs/v1/clean/evidence.d.ts +++ b/docs/public/libs/v1/clean/evidence.d.ts @@ -3,6 +3,11 @@ import type * as DEither from "../either"; export declare const evidenceKind: import("../common").KindHandler>>; export interface Evidence extends Kind> { } +declare const ArrayWithEvidence_base: import("../common").KindClass>>, ArrayConstructor>; +export declare class ArrayWithEvidence extends ArrayWithEvidence_base> { + constructor(array: readonly GenericElement[], evidence: Record); + static [Symbol.species]: ArrayConstructor; +} /** * Appends an evidence trait on an object value to mark that a business step was completed. * @@ -110,3 +115,4 @@ export declare function appendEvidence>>, string>>(evidenceName: GenericEvidenceName | AnyTuple): (input: GenericInput) => input is Extract : never>; export declare function hasEvidence>>, string>>(input: GenericInput, evidenceName: GenericEvidenceName | AnyTuple): input is Extract : never>; export type GetEvidenceResult> extends infer InferredResult ? InferredResult extends Evidence ? InferredResult : InferredResult extends DEither.Right | DEither.Left ? Unwrap : InferredResult : never, Evidence>>>, string>> = Extract> extends infer InferredResult ? InferredResult extends Evidence ? InferredResult : InferredResult extends DEither.Right | DEither.Left ? Unwrap : InferredResult : never, EvidenceName extends any ? Evidence : never>; +export {}; diff --git a/docs/public/libs/v1/clean/evidence.mjs b/docs/public/libs/v1/clean/evidence.mjs index 166a4f2a1..cce7aa898 100644 --- a/docs/public/libs/v1/clean/evidence.mjs +++ b/docs/public/libs/v1/clean/evidence.mjs @@ -1,6 +1,13 @@ import { createCleanKind } from './kind.mjs'; +import { kindClass } from '../common/kindClass.mjs'; const evidenceKind = createCleanKind("evidence"); +class ArrayWithEvidence extends kindClass(evidenceKind, Array) { + constructor(array, evidence) { + super(evidence, ...array); + } + static [Symbol.species] = Array; +} function appendEvidence(...args) { if (args.length === 1) { const [evidenceName] = args; @@ -11,6 +18,9 @@ function appendEvidence(...args) { ...(evidenceKind.has(input) && evidenceKind.getValue(input)), [evidenceName]: null, }; + if (input instanceof Array) { + return new ArrayWithEvidence(input, evidence); + } return evidenceKind.addTo(input, evidence); } function hasEvidence(...args) { @@ -34,4 +44,4 @@ function hasEvidence(...args) { return false; } -export { appendEvidence, evidenceKind, hasEvidence }; +export { ArrayWithEvidence, appendEvidence, evidenceKind, hasEvidence }; diff --git a/docs/public/libs/v1/clean/flag.d.ts b/docs/public/libs/v1/clean/flag.d.ts index f54b71669..9176e598a 100644 --- a/docs/public/libs/v1/clean/flag.d.ts +++ b/docs/public/libs/v1/clean/flag.d.ts @@ -32,17 +32,26 @@ export interface FlagHandler>(entity: GenericInputEntity): GetKindValue[GenericName]; + /** + * Checks whether the entity has this state and narrows its type to include the flag. + * + * ```ts + * User.MajorFlag.has(user); // false + * User.MajorFlag.has(flagged); // true + * ``` + * + */ has(entity: GenericInputEntity): entity is Extract>; } export interface Flag extends Kind> { } /** - * Creates a flag handler that can attach typed metadata to an entity. + * Creates a handler for assigning a typed business state to an entity. * * **Supported call styles:** * - Classic: `createFlag(name)` -> returns a handler * - * Flags let you mark an entity after creation without changing its shape. The mark can optionally carry a value. + * Use flags to make an entity state part of a precise type contract. Functions can require an entity combined with one or more flags, while the same entity keeps its original business properties and can reuse its repositories, mappers, and other supporting code. A flag can optionally carry data associated with its state. * * ```ts * namespace User { @@ -97,10 +106,12 @@ export interface Flag matcher[unwrap.unwrap(input)](input); + } + const [input, matcher] = args; + return matcher[unwrap.unwrap(input)](input); +} + +exports.matchWithNumber = matchWithNumber; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithNumber.d.ts b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumber.d.ts new file mode 100644 index 000000000..1774bbc0c --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumber.d.ts @@ -0,0 +1,55 @@ +import { type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap } from "../../../common"; +import { type Primitive } from "../base"; +import { type OnlyLiteralPrimitiveNumber } from "../../../clean/types/onlyLiteral"; +type ComputeMatcher> = { + [Prop in Unwrap]: (value: GenericInput & Primitive) => unknown; +}; +type ForbiddenMoreKey, GenericMatcher extends ComputeMatcher> = Exclude> extends infer InferredKey ? IsEqual extends true ? unknown : ComputedTypeError<`Key "${Extract}" is forbidden.`> : never; +/** + * Performs exhaustive pattern matching on the value of a Clean number primitive. + * + * **Supported call styles:** + * - Classic: `matchWithNumber(input, matcher)` -> runs the handler matching the wrapped value + * - Curried: `matchWithNumber(matcher)` -> returns a function waiting for the primitive + * + * A Clean primitive cannot be used directly as an object key, so the matcher uses raw number values as keys. Every possible value must have a handler. The selected handler receives the original Clean primitive narrowed to the matching key, preserving its constraints and `NewType` metadata. + * + * ```ts + * const status = C.Number.createOrThrow( + * 200 as 200 | 404, + * ); + * + * C.matchWithNumber(status, { + * 200: () => "success", + * 404: () => "missing", + * }); // "success" | "missing" + * + * pipe( + * status, + * C.matchWithNumber({ + * 200: (value) => value, + * 404: () => status, + * }), + * ); // C.Primitive<200 | 404> + * + * const positiveStatus = C.Positive.createOrThrow( + * 200 as 200 | 404, + * ); + * + * C.matchWithNumber(positiveStatus, { + * 200: (value) => value, + * 404: (value) => value, + * }); // C.ConstrainedType<"positive", 200 | 404> + * ``` + * + * @remarks A broad `Primitive` is supported with a `Record` matcher. Literal unions reject missing and additional keys. + * + * @see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumber + * @see [`C.matchWithString`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithString) + * + * @namespace C + * + */ +export declare function matchWithNumber, GenericMatcher extends ComputeMatcher>(matcher: FixDeepFunctionInfer>, GenericMatcher> & ForbiddenMoreKey, GenericMatcher>): (input: GenericInput & OnlyLiteralPrimitiveNumber) => ReturnType[keyof GenericMatcher]>; +export declare function matchWithNumber, GenericMatcher extends ComputeMatcher>(input: GenericInput & OnlyLiteralPrimitiveNumber, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey): ReturnType; +export {}; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithNumber.mjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumber.mjs new file mode 100644 index 000000000..0886b3ecf --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumber.mjs @@ -0,0 +1,12 @@ +import { unwrap } from '../../../common/unwrap.mjs'; + +function matchWithNumber(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[unwrap(input)](input); + } + const [input, matcher] = args; + return matcher[unwrap(input)](input); +} + +export { matchWithNumber }; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.cjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.cjs new file mode 100644 index 000000000..9d9131cc9 --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.cjs @@ -0,0 +1,18 @@ +'use strict'; + +var unwrap = require('../../../common/unwrap.cjs'); + +function execute(input, matcher, otherwise) { + const callback = matcher[unwrap.unwrap(input)]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithNumberOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +exports.matchWithNumberOtherwise = matchWithNumberOtherwise; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.d.ts b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.d.ts new file mode 100644 index 000000000..fb230ff65 --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.d.ts @@ -0,0 +1,61 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap } from "../../../common"; +import { type GetPropsWithValueExtends } from "../../../object"; +import { type OnlyLiteralPrimitiveNumber } from "../../../clean/types/onlyLiteral"; +import { type Primitive } from "../base"; +type ComputeMatcher> = { + [Prop in Unwrap]?: (value: GenericInput & Primitive) => unknown; +}; +type ForbiddenMoreKey, GenericMatcher extends ComputeMatcher> = Exclude> extends infer InferredKey ? IsEqual extends true ? unknown : ComputedTypeError<`Key "${Extract}" is forbidden.`> : never; +type HandledKeys = Extract, number>; +type OtherwiseValue, GenericMatcher extends object> = Extract, HandledKeys>>, any>; +/** Performs partial pattern matching on a Clean number primitive and delegates unhandled values to an `otherwise` callback. +/** +/** **Supported call styles:** +/** - Classic: `matchWithNumberOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +/** - Curried: `matchWithNumberOtherwise(matcher, otherwise)` -> returns a function waiting for the primitive +/** +/** Raw number values are used as matcher keys. Handlers receive the original primitive narrowed to their key, and `otherwise` receives the same original primitive narrowed to all remaining values. +/** +/** ```ts +/** const status = C.Number.createOrThrow( +/** 200 as 200 | 404 | 500, +/** ); +/** +/** C.matchWithNumberOtherwise( +/** status, +/** { +/** 200: () => "success", +/** }, +/** () => "error", +/** ); // string +/** +/** pipe( +/** status, +/** C.matchWithNumberOtherwise( +/** { +/** 404: () => "missing", +/** }, +/** (value) => value, +/** ), +/** ); // "missing" | C.Primitive<200 | 500> +/** +/** const result = C.matchWithNumberOtherwise( +/** status, +/** { +/** 200: (value) => value, +/** 404: undefined, +/** }, +/** (value) => value, +/** ); // C.Primitive<200 | 404 | 500> +/** ``` +/** +/** @remarks Constraints and `NewType` metadata remain attached to the primitive passed to every callback. +/** +/** @see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise +/** @see [`C.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumber) +/** +/** @namespace C +/** */ +export declare function matchWithNumberOtherwise, GenericMatcher extends ComputeMatcher, GenericOutput>(matcher: FixDeepFunctionInfer>, GenericMatcher> & ForbiddenMoreKey, GenericMatcher>, otherwise: (value: OtherwiseValue) => GenericOutput): (input: GenericInput & OnlyLiteralPrimitiveNumber) => (ReturnType[keyof GenericMatcher], AnyFunction>> | GenericOutput); +export declare function matchWithNumberOtherwise, GenericMatcher extends ComputeMatcher, GenericOutput>(input: GenericInput & OnlyLiteralPrimitiveNumber, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey, otherwise: (value: OtherwiseValue) => GenericOutput): (ReturnType> | GenericOutput); +export {}; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.mjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.mjs new file mode 100644 index 000000000..d3ac2defc --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithNumberOtherwise.mjs @@ -0,0 +1,16 @@ +import { unwrap } from '../../../common/unwrap.mjs'; + +function execute(input, matcher, otherwise) { + const callback = matcher[unwrap(input)]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithNumberOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +export { matchWithNumberOtherwise }; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithString.cjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithString.cjs new file mode 100644 index 000000000..2d70fa24d --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithString.cjs @@ -0,0 +1,14 @@ +'use strict'; + +var unwrap = require('../../../common/unwrap.cjs'); + +function matchWithString(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[unwrap.unwrap(input)](input); + } + const [input, matcher] = args; + return matcher[unwrap.unwrap(input)](input); +} + +exports.matchWithString = matchWithString; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithString.d.ts b/docs/public/libs/v1/clean/primitive/matcher/matchWithString.d.ts new file mode 100644 index 000000000..c72890efd --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithString.d.ts @@ -0,0 +1,55 @@ +import { type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap } from "../../../common"; +import { type Primitive } from "../base"; +import { type OnlyLiteralPrimitiveString } from "../../../clean/types/onlyLiteral"; +type ComputeMatcher> = { + [Prop in Unwrap]: (value: GenericInput & Primitive) => unknown; +}; +type ForbiddenMoreKey, GenericMatcher extends ComputeMatcher> = Exclude> extends infer InferredKey ? IsEqual extends true ? unknown : ComputedTypeError<`Key "${Extract}" is forbidden.`> : never; +/** + * Performs exhaustive pattern matching on the value of a Clean string primitive. + * + * **Supported call styles:** + * - Classic: `matchWithString(input, matcher)` -> runs the handler matching the wrapped value + * - Curried: `matchWithString(matcher)` -> returns a function waiting for the primitive + * + * A Clean primitive cannot be used directly as an object key, so the matcher uses raw string values as keys. Every possible value must have a handler. The selected handler receives the original Clean primitive narrowed to the matching key, preserving its constraints and `NewType` metadata. + * + * ```ts + * const status = C.String.createOrThrow( + * "success" as "success" | "failure", + * ); + * + * C.matchWithString(status, { + * success: () => 200, + * failure: () => 500, + * }); // 200 | 500 + * + * pipe( + * status, + * C.matchWithString({ + * success: (value) => value, + * failure: () => status, + * }), + * ); // C.Primitive<"success" | "failure"> + * + * const noBlankStatus = C.NoBlank.createOrThrow( + * "success" as "success" | "failure", + * ); + * + * C.matchWithString(noBlankStatus, { + * success: (value) => value, + * failure: (value) => value, + * }); // C.ConstrainedType<"no-blank", "success" | "failure"> + * ``` + * + * @remarks A broad `Primitive` is supported with a `Record` matcher. Literal unions reject missing and additional keys. + * + * @see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithString + * @see [`C.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumber) + * + * @namespace C + * + */ +export declare function matchWithString, GenericMatcher extends ComputeMatcher>(matcher: FixDeepFunctionInfer>, GenericMatcher> & ForbiddenMoreKey, GenericMatcher>): (input: GenericInput & OnlyLiteralPrimitiveString) => ReturnType[keyof GenericMatcher]>; +export declare function matchWithString, GenericMatcher extends ComputeMatcher>(input: GenericInput & OnlyLiteralPrimitiveString, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey): ReturnType; +export {}; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithString.mjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithString.mjs new file mode 100644 index 000000000..5eaa0841b --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithString.mjs @@ -0,0 +1,12 @@ +import { unwrap } from '../../../common/unwrap.mjs'; + +function matchWithString(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[unwrap(input)](input); + } + const [input, matcher] = args; + return matcher[unwrap(input)](input); +} + +export { matchWithString }; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.cjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.cjs new file mode 100644 index 000000000..615e6fd09 --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.cjs @@ -0,0 +1,18 @@ +'use strict'; + +var unwrap = require('../../../common/unwrap.cjs'); + +function execute(input, matcher, otherwise) { + const callback = matcher[unwrap.unwrap(input)]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithStringOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +exports.matchWithStringOtherwise = matchWithStringOtherwise; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.d.ts b/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.d.ts new file mode 100644 index 000000000..355adaa11 --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.d.ts @@ -0,0 +1,61 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap } from "../../../common"; +import { type GetPropsWithValueExtends } from "../../../object"; +import { type OnlyLiteralPrimitiveString } from "../../../clean/types/onlyLiteral"; +import { type Primitive } from "../base"; +type ComputeMatcher> = { + [Prop in Unwrap]?: (value: GenericInput & Primitive) => unknown; +}; +type ForbiddenMoreKey, GenericMatcher extends ComputeMatcher> = Exclude> extends infer InferredKey ? IsEqual extends true ? unknown : ComputedTypeError<`Key "${Extract}" is forbidden.`> : never; +type HandledKeys = Extract, string>; +type OtherwiseValue, GenericMatcher extends object> = Extract, HandledKeys>>, any>; +/** Performs partial pattern matching on a Clean string primitive and delegates unhandled values to an `otherwise` callback. +/** +/** **Supported call styles:** +/** - Classic: `matchWithStringOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +/** - Curried: `matchWithStringOtherwise(matcher, otherwise)` -> returns a function waiting for the primitive +/** +/** Raw string values are used as matcher keys. Handlers receive the original primitive narrowed to their key, and `otherwise` receives the same original primitive narrowed to all remaining values. +/** +/** ```ts +/** const status = C.String.createOrThrow( +/** "success" as "success" | "failure" | "pending", +/** ); +/** +/** C.matchWithStringOtherwise( +/** status, +/** { +/** success: () => 200, +/** }, +/** () => 500, +/** ); // number +/** +/** pipe( +/** status, +/** C.matchWithStringOtherwise( +/** { +/** failure: () => "retry", +/** }, +/** (value) => value, +/** ), +/** ); // "retry" | C.Primitive<"success" | "pending"> +/** +/** const result = C.matchWithStringOtherwise( +/** status, +/** { +/** success: (value) => value, +/** failure: undefined, +/** }, +/** (value) => value, +/** ); // C.Primitive<"success" | "failure" | "pending"> +/** ``` +/** +/** @remarks Constraints and `NewType` metadata remain attached to the primitive passed to every callback. +/** +/** @see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithStringOtherwise +/** @see [`C.matchWithString`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithString) +/** +/** @namespace C +/** */ +export declare function matchWithStringOtherwise, GenericMatcher extends ComputeMatcher, GenericOutput>(matcher: FixDeepFunctionInfer>, GenericMatcher> & ForbiddenMoreKey, GenericMatcher>, otherwise: (value: OtherwiseValue) => GenericOutput): (input: GenericInput & OnlyLiteralPrimitiveString) => (ReturnType[keyof GenericMatcher], AnyFunction>> | GenericOutput); +export declare function matchWithStringOtherwise, GenericMatcher extends ComputeMatcher, GenericOutput>(input: GenericInput & OnlyLiteralPrimitiveString, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey, otherwise: (value: OtherwiseValue) => GenericOutput): (ReturnType> | GenericOutput); +export {}; diff --git a/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.mjs b/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.mjs new file mode 100644 index 000000000..e7fa46ba9 --- /dev/null +++ b/docs/public/libs/v1/clean/primitive/matcher/matchWithStringOtherwise.mjs @@ -0,0 +1,16 @@ +import { unwrap } from '../../../common/unwrap.mjs'; + +function execute(input, matcher, otherwise) { + const callback = matcher[unwrap(input)]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithStringOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +export { matchWithStringOtherwise }; diff --git a/docs/public/libs/v1/clean/types/onlyLiteral.d.ts b/docs/public/libs/v1/clean/types/onlyLiteral.d.ts new file mode 100644 index 000000000..691825e75 --- /dev/null +++ b/docs/public/libs/v1/clean/types/onlyLiteral.d.ts @@ -0,0 +1,7 @@ +import { type Unwrap, type ComputedTypeError } from "../../common"; +import { type Primitive } from "../primitive"; +export type OnlyLiteralPrimitiveString = string extends Unwrap ? ComputedTypeError<"Input primitive must be a literal string."> : GenericValue; +export type OnlyLiteralPrimitiveNumber = number extends Unwrap ? ComputedTypeError<"Input primitive must be a literal number."> : GenericValue; +export type OnlyLiteralPrimitiveBigInt = bigint extends Unwrap ? ComputedTypeError<"Input primitive must be a literal bigint."> : GenericValue; +export type OnlyLiteralPrimitiveBoolean = boolean extends Unwrap ? ComputedTypeError<"Input primitive must be a literal boolean."> : GenericValue; +export type OnlyLiteral = (OnlyLiteralPrimitiveString & OnlyLiteralPrimitiveNumber & OnlyLiteralPrimitiveBigInt & OnlyLiteralPrimitiveBoolean); diff --git a/docs/public/libs/v1/common/index.cjs b/docs/public/libs/v1/common/index.cjs index c93590b81..02080fb3b 100644 --- a/docs/public/libs/v1/common/index.cjs +++ b/docs/public/libs/v1/common/index.cjs @@ -62,6 +62,8 @@ var kindClass = require('./kindClass.cjs'); var promiseAll = require('./promiseAll.cjs'); var detachObjectMethod = require('./detachObjectMethod.cjs'); var bindPrototypeMethods = require('./bindPrototypeMethods.cjs'); +var preparePipe = require('./preparePipe.cjs'); +var prepareAsyncPipe = require('./prepareAsyncPipe.cjs'); /** * {@include common/index.md} @@ -154,3 +156,5 @@ exports.kindClass = kindClass.kindClass; exports.promiseAll = promiseAll.promiseAll; exports.detachObjectMethod = detachObjectMethod.detachObjectMethod; exports.bindPrototypeMethods = bindPrototypeMethods.bindPrototypeMethods; +exports.preparePipe = preparePipe.preparePipe; +exports.prepareAsyncPipe = prepareAsyncPipe.prepareAsyncPipe; diff --git a/docs/public/libs/v1/common/index.d.ts b/docs/public/libs/v1/common/index.d.ts index c483a2e03..ac15d986a 100644 --- a/docs/public/libs/v1/common/index.d.ts +++ b/docs/public/libs/v1/common/index.d.ts @@ -86,3 +86,5 @@ export * from "./kindClass"; export * from "./promiseAll"; export * from "./detachObjectMethod"; export * from "./bindPrototypeMethods"; +export * from "./preparePipe"; +export * from "./prepareAsyncPipe"; diff --git a/docs/public/libs/v1/common/index.mjs b/docs/public/libs/v1/common/index.mjs index f8f7f133f..917af8810 100644 --- a/docs/public/libs/v1/common/index.mjs +++ b/docs/public/libs/v1/common/index.mjs @@ -60,6 +60,8 @@ export { kindClass } from './kindClass.mjs'; export { promiseAll } from './promiseAll.mjs'; export { detachObjectMethod } from './detachObjectMethod.mjs'; export { bindPrototypeMethods } from './bindPrototypeMethods.mjs'; +export { preparePipe } from './preparePipe.mjs'; +export { prepareAsyncPipe } from './prepareAsyncPipe.mjs'; /** * {@include common/index.md} diff --git a/docs/public/libs/v1/common/prepareAsyncPipe.cjs b/docs/public/libs/v1/common/prepareAsyncPipe.cjs new file mode 100644 index 000000000..fcdecbd71 --- /dev/null +++ b/docs/public/libs/v1/common/prepareAsyncPipe.cjs @@ -0,0 +1,14 @@ +'use strict'; + +/** {@include common/prepareAsyncPipe/index.md} */ +function prepareAsyncPipe() { + return (...pipes) => async (input) => { + let acc = await input; + for (const pipe of pipes) { + acc = await pipe(acc); + } + return acc; + }; +} + +exports.prepareAsyncPipe = prepareAsyncPipe; diff --git a/docs/public/libs/v1/common/prepareAsyncPipe.d.ts b/docs/public/libs/v1/common/prepareAsyncPipe.d.ts new file mode 100644 index 000000000..f15c9fe6d --- /dev/null +++ b/docs/public/libs/v1/common/prepareAsyncPipe.d.ts @@ -0,0 +1,60 @@ +import { type BreakGenericLink, type MaybePromise } from "./types"; +export interface PrepareAsyncPipe { + (pipe1: (input: BreakGenericLink) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise, pipe10: (input: GenericOutputPipe9) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise, pipe10: (input: GenericOutputPipe9) => MaybePromise, pipe11: (input: GenericOutputPipe10) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise, pipe10: (input: GenericOutputPipe9) => MaybePromise, pipe11: (input: GenericOutputPipe10) => MaybePromise, pipe12: (input: GenericOutputPipe11) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise, pipe10: (input: GenericOutputPipe9) => MaybePromise, pipe11: (input: GenericOutputPipe10) => MaybePromise, pipe12: (input: GenericOutputPipe11) => MaybePromise, pipe13: (input: GenericOutputPipe12) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise, pipe10: (input: GenericOutputPipe9) => MaybePromise, pipe11: (input: GenericOutputPipe10) => MaybePromise, pipe12: (input: GenericOutputPipe11) => MaybePromise, pipe13: (input: GenericOutputPipe12) => MaybePromise, pipe14: (input: GenericOutputPipe13) => MaybePromise): (input: MaybePromise) => Promise; + (pipe1: (input: BreakGenericLink) => MaybePromise, pipe2: (input: GenericOutputPipe1) => MaybePromise, pipe3: (input: GenericOutputPipe2) => MaybePromise, pipe4: (input: GenericOutputPipe3) => MaybePromise, pipe5: (input: GenericOutputPipe4) => MaybePromise, pipe6: (input: GenericOutputPipe5) => MaybePromise, pipe7: (input: GenericOutputPipe6) => MaybePromise, pipe8: (input: GenericOutputPipe7) => MaybePromise, pipe9: (input: GenericOutputPipe8) => MaybePromise, pipe10: (input: GenericOutputPipe9) => MaybePromise, pipe11: (input: GenericOutputPipe10) => MaybePromise, pipe12: (input: GenericOutputPipe11) => MaybePromise, pipe13: (input: GenericOutputPipe12) => MaybePromise, pipe14: (input: GenericOutputPipe13) => MaybePromise, pipe15: (input: GenericOutputPipe14) => MaybePromise): (input: MaybePromise) => Promise; +} +/** Prepares a reusable asynchronous pipeline whose steps are declared once and whose input type can be inferred from the resulting function's context. +/** +/** **Supported call styles:** +/** - Explicit input: `prepareAsyncPipe()(pipe1, pipe2)` -> returns a typed reusable async function +/** - Contextual input: `const fn: (input: Input) => Promise = prepareAsyncPipe()(pipe1, pipe2)` -> infers the local input from the target function type +/** +/** Each step may return a direct value or a promise. Every execution accepts a direct input or a promise, awaits the prepared sequence, and returns a promise of the final output. +/** +/** ```ts +/** const fetchLabel = prepareAsyncPipe()( +/** async(value) => Promise.resolve(value * 2), +/** S.to, +/** ); +/** +/** await fetchLabel(10); // "20" +/** await fetchLabel(Promise.resolve(15)); // "30" +/** +/** const numberToString: ( +/** input: number +/** ) => Promise = prepareAsyncPipe()( +/** S.to, +/** ); +/** +/** await numberToString(42); // "42" +/** +/** const normalizeName = prepareAsyncPipe()( +/** async(value) => Promise.resolve(value.trim()), +/** (value) => value.toUpperCase(), +/** ); +/** +/** await pipe( +/** " Ada ", +/** normalizeName, +/** ); // "ADA" +/** ``` +/** +/** @remarks Contextual input inference is useful when declaring recursive or mutually recursive async functions because the complete input-to-output contract can be written before the pipeline is built. +/** +/** @see https://utils.duplojs.dev/en/v1/api/common/prepareAsyncPipe +/** @see [`asyncInnerPipe`](https://utils.duplojs.dev/en/v1/api/common/asyncInnerPipe) +/** */ +export declare function prepareAsyncPipe(): PrepareAsyncPipe; diff --git a/docs/public/libs/v1/common/prepareAsyncPipe.mjs b/docs/public/libs/v1/common/prepareAsyncPipe.mjs new file mode 100644 index 000000000..d73ba6264 --- /dev/null +++ b/docs/public/libs/v1/common/prepareAsyncPipe.mjs @@ -0,0 +1,12 @@ +/** {@include common/prepareAsyncPipe/index.md} */ +function prepareAsyncPipe() { + return (...pipes) => async (input) => { + let acc = await input; + for (const pipe of pipes) { + acc = await pipe(acc); + } + return acc; + }; +} + +export { prepareAsyncPipe }; diff --git a/docs/public/libs/v1/common/preparePipe.cjs b/docs/public/libs/v1/common/preparePipe.cjs new file mode 100644 index 000000000..1effdb966 --- /dev/null +++ b/docs/public/libs/v1/common/preparePipe.cjs @@ -0,0 +1,14 @@ +'use strict'; + +/** {@include common/preparePipe/index.md} */ +function preparePipe() { + return (...pipes) => (input) => { + let acc = input; + for (const pipe of pipes) { + acc = pipe(acc); + } + return acc; + }; +} + +exports.preparePipe = preparePipe; diff --git a/docs/public/libs/v1/common/preparePipe.d.ts b/docs/public/libs/v1/common/preparePipe.d.ts new file mode 100644 index 000000000..8a0e82da3 --- /dev/null +++ b/docs/public/libs/v1/common/preparePipe.d.ts @@ -0,0 +1,58 @@ +import { type BreakGenericLink } from "./types"; +export interface PreparePipe { + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1): (input: GenericLocalInput) => GenericOutputPipe1; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2): (input: GenericLocalInput) => GenericOutputPipe2; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3): (input: GenericLocalInput) => GenericOutputPipe3; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4): (input: GenericLocalInput) => GenericOutputPipe4; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5): (input: GenericLocalInput) => GenericOutputPipe5; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6): (input: GenericLocalInput) => GenericOutputPipe6; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7): (input: GenericLocalInput) => GenericOutputPipe7; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8): (input: GenericLocalInput) => GenericOutputPipe8; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9): (input: GenericLocalInput) => GenericOutputPipe9; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10): (input: GenericLocalInput) => GenericOutputPipe10; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11): (input: GenericLocalInput) => GenericOutputPipe11; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12): (input: GenericLocalInput) => GenericOutputPipe12; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12, pipe13: (input: GenericOutputPipe12) => GenericOutputPipe13): (input: GenericLocalInput) => GenericOutputPipe13; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12, pipe13: (input: GenericOutputPipe12) => GenericOutputPipe13, pipe14: (input: GenericOutputPipe13) => GenericOutputPipe14): (input: GenericLocalInput) => GenericOutputPipe14; + (pipe1: (input: BreakGenericLink) => GenericOutputPipe1, pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12, pipe13: (input: GenericOutputPipe12) => GenericOutputPipe13, pipe14: (input: GenericOutputPipe13) => GenericOutputPipe14, pipe15: (input: GenericOutputPipe14) => GenericOutputPipe15): (input: GenericLocalInput) => GenericOutputPipe15; +} +/** Prepares a reusable synchronous pipeline whose steps are declared once and whose input type can be inferred from the resulting function's context. +/** +/** **Supported call styles:** +/** - Explicit input: `preparePipe()(pipe1, pipe2)` -> returns a typed reusable function +/** - Contextual input: `const fn: (input: Input) => Output = preparePipe()(pipe1, pipe2)` -> infers the local input from the target function type +/** +/** Each execution only calls the already prepared function. Up to fifteen steps are supported, and every intermediate output becomes the next step's input. +/** +/** ```ts +/** const formatPrice = preparePipe()( +/** N.multiply(2), +/** S.to, +/** ); +/** +/** formatPrice(10); // "20" +/** formatPrice(15); // "30" +/** +/** const numberToString: (input: number) => string = preparePipe()( +/** S.to, +/** ); +/** +/** numberToString(42); // "42" +/** +/** const normalizeName = preparePipe()( +/** (value) => value.trim(), +/** (value) => value.toUpperCase(), +/** ); +/** +/** pipe( +/** " Ada ", +/** normalizeName, +/** ); // "ADA" +/** ``` +/** +/** @remarks Contextual input inference is useful when declaring recursive or mutually recursive functions because the complete input-to-output contract can be written before the pipeline is built. +/** +/** @see https://utils.duplojs.dev/en/v1/api/common/preparePipe +/** @see [`innerPipe`](https://utils.duplojs.dev/en/v1/api/common/innerPipe) +/** */ +export declare function preparePipe(): PreparePipe; diff --git a/docs/public/libs/v1/common/preparePipe.mjs b/docs/public/libs/v1/common/preparePipe.mjs new file mode 100644 index 000000000..cfc3cddba --- /dev/null +++ b/docs/public/libs/v1/common/preparePipe.mjs @@ -0,0 +1,12 @@ +/** {@include common/preparePipe/index.md} */ +function preparePipe() { + return (...pipes) => (input) => { + let acc = input; + for (const pipe of pipes) { + acc = pipe(acc); + } + return acc; + }; +} + +export { preparePipe }; diff --git a/docs/public/libs/v1/common/types/CleanObjectEntry.d.ts b/docs/public/libs/v1/common/types/cleanObjectEntry.d.ts similarity index 100% rename from docs/public/libs/v1/common/types/CleanObjectEntry.d.ts rename to docs/public/libs/v1/common/types/cleanObjectEntry.d.ts diff --git a/docs/public/libs/v1/common/types/ComputedTypeError.d.ts b/docs/public/libs/v1/common/types/computedTypeError.d.ts similarity index 100% rename from docs/public/libs/v1/common/types/ComputedTypeError.d.ts rename to docs/public/libs/v1/common/types/computedTypeError.d.ts diff --git a/docs/public/libs/v1/common/types/forbiddenTupleWithUnknownLength.d.ts b/docs/public/libs/v1/common/types/forbiddenTupleWithUnknownLength.d.ts index fe6ba0a0f..e97abff0e 100644 --- a/docs/public/libs/v1/common/types/forbiddenTupleWithUnknownLength.d.ts +++ b/docs/public/libs/v1/common/types/forbiddenTupleWithUnknownLength.d.ts @@ -1,3 +1,3 @@ import { type AnyTuple } from "./anyTuple"; -import { type ComputedTypeError } from "./ComputedTypeError"; +import { type ComputedTypeError } from "./computedTypeError"; export type ForbiddenTupleWithUnknownLength> = (number extends GenericTuple["length"] ? ComputedTypeError<"Tuple must have finite length."> : unknown); diff --git a/docs/public/libs/v1/common/types/index.d.ts b/docs/public/libs/v1/common/types/index.d.ts index e31e70a57..c6e31c8b3 100644 --- a/docs/public/libs/v1/common/types/index.d.ts +++ b/docs/public/libs/v1/common/types/index.d.ts @@ -48,8 +48,8 @@ export * from "./json"; export * from "./predicate"; export * from "./bivariantFunction"; export * from "./maybeAsyncGenerator"; -export * from "./ComputedTypeError"; -export * from "./CleanObjectEntry"; +export * from "./computedTypeError"; +export * from "./cleanObjectEntry"; export * from "./requireConstructor"; export * from "./removeAbstractFromConstructor"; export * from "./clearClassKeys"; diff --git a/docs/public/libs/v1/common/types/onlyLiteral.d.ts b/docs/public/libs/v1/common/types/onlyLiteral.d.ts index 9eca67501..a86fadfba 100644 --- a/docs/public/libs/v1/common/types/onlyLiteral.d.ts +++ b/docs/public/libs/v1/common/types/onlyLiteral.d.ts @@ -1,4 +1,4 @@ -import { type ComputedTypeError } from "./ComputedTypeError"; +import { type ComputedTypeError } from "./computedTypeError"; export type OnlyLiteralString = string extends GenericValue ? ComputedTypeError<"Input must be a literal string."> : GenericValue; export type OnlyLiteralNumber = number extends GenericValue ? ComputedTypeError<"Input must be a literal number."> : GenericValue; export type OnlyLiteralSymbol = symbol extends GenericValue ? ComputedTypeError<"Input must be a literal symbol."> : GenericValue; diff --git a/docs/public/libs/v1/common/types/requireConstructor.d.ts b/docs/public/libs/v1/common/types/requireConstructor.d.ts index 4b1d72a6e..2776da77f 100644 --- a/docs/public/libs/v1/common/types/requireConstructor.d.ts +++ b/docs/public/libs/v1/common/types/requireConstructor.d.ts @@ -1,3 +1,3 @@ import { type AnyAbstractConstructor } from "./anyConstructor"; -import { type ComputedTypeError } from "./ComputedTypeError"; +import { type ComputedTypeError } from "./computedTypeError"; export type RequireConstructor = GenericInput extends GenericConstructor ? GenericInput : ComputedTypeError<"Require constructor.">; diff --git a/docs/public/libs/v1/either/bool/create.d.ts b/docs/public/libs/v1/either/bool/create.d.ts index 124fbdf9e..6e4ff5b3b 100644 --- a/docs/public/libs/v1/either/bool/create.d.ts +++ b/docs/public/libs/v1/either/bool/create.d.ts @@ -12,11 +12,11 @@ import { type BoolTruthy } from "./truthy"; * const input = ["duplo"]; * const result = pipe( * input, - * A.find(equal("nest")), + * A.find(equal("duplo")), * E.bool, * ); * - * // type: E.BoolFalsy | E.BoolTruthy<"nest"> + * // type: E.BoolFalsy | E.BoolTruthy<"duplo"> * ``` * * @see https://utils.duplojs.dev/en/v1/api/either/bool diff --git a/docs/public/libs/v1/either/index.cjs b/docs/public/libs/v1/either/index.cjs index 29b662908..2102aec16 100644 --- a/docs/public/libs/v1/either/index.cjs +++ b/docs/public/libs/v1/either/index.cjs @@ -2,6 +2,7 @@ var hasInformation = require('./hasInformation.cjs'); var whenHasInformation = require('./whenHasInformation.cjs'); +var whenIsSelected = require('./whenIsSelected.cjs'); var kind = require('./kind.cjs'); var safeCallback = require('./safeCallback.cjs'); var expect = require('./expect.cjs'); @@ -59,6 +60,7 @@ var unwrap$1 = require('./right/unwrap.cjs'); exports.hasInformation = hasInformation.hasInformation; exports.whenHasInformation = whenHasInformation.whenHasInformation; +exports.whenIsSelected = whenIsSelected.whenIsSelected; exports.createEitherKind = kind.createEitherKind; exports.eitherInformationKind = kind.eitherInformationKind; exports.informationKind = kind.informationKind; diff --git a/docs/public/libs/v1/either/index.d.ts b/docs/public/libs/v1/either/index.d.ts index 62d647b30..b1a3845bb 100644 --- a/docs/public/libs/v1/either/index.d.ts +++ b/docs/public/libs/v1/either/index.d.ts @@ -31,6 +31,7 @@ export * from "./optional"; export * from "./right"; export * from "./hasInformation"; export * from "./whenHasInformation"; +export * from "./whenIsSelected"; export * from "./kind"; export * from "./safeCallback"; export * from "./expect"; diff --git a/docs/public/libs/v1/either/index.mjs b/docs/public/libs/v1/either/index.mjs index 54f837c4d..e23465050 100644 --- a/docs/public/libs/v1/either/index.mjs +++ b/docs/public/libs/v1/either/index.mjs @@ -1,5 +1,6 @@ export { hasInformation } from './hasInformation.mjs'; export { whenHasInformation } from './whenHasInformation.mjs'; +export { whenIsSelected } from './whenIsSelected.mjs'; export { createEitherKind, eitherInformationKind, informationKind } from './kind.mjs'; export { safeCallback } from './safeCallback.mjs'; export { expect } from './expect.mjs'; diff --git a/docs/public/libs/v1/either/matchInformationOtherwise.d.ts b/docs/public/libs/v1/either/matchInformationOtherwise.d.ts index 98f3197d5..4a9fbdc85 100644 --- a/docs/public/libs/v1/either/matchInformationOtherwise.d.ts +++ b/docs/public/libs/v1/either/matchInformationOtherwise.d.ts @@ -2,11 +2,12 @@ import { type AnyFunction, type FixDeepFunctionInfer, type GetKindValue, type Ki import { informationKind } from "./kind"; import { type Right } from "./right"; import { type Left } from "./left"; -import { type GetPropsWithValueExtends } from "../object"; +import { type ForbiddenKey, type GetPropsWithValueExtends } from "../object"; type Either = Right | Left; type ComputeMatcher = Extract<{ [Prop in GetKindValue]?: (value: Unwrap>>) => unknown; }, any>; +type ForbiddenMoreKey>> = ForbiddenKey>>, string>>; /** * Non-exhaustive pattern matching based on Either information. Unhandled cases are delegated to an `otherwise` callback. * @@ -60,6 +61,6 @@ type ComputeMatcher = Extract<{ * @namespace E * */ -export declare function matchInformationOtherwise>, GenericOutput extends unknown>(matcher: (ComputeMatcher, Either>> & GenericMatcher), otherwise: (value: Exclude, string>>>) => GenericOutput): (input: GenericInput) => (ReturnType>> | GenericOutput); -export declare function matchInformationOtherwise>, GenericOutput extends unknown>(input: GenericInput, matcher: FixDeepFunctionInfer>, GenericMatcher>, otherwise: (value: Exclude, string>>>) => GenericOutput): (ReturnType>> | GenericOutput); +export declare function matchInformationOtherwise>, GenericOutput extends unknown, GenericError extends ForbiddenMoreKey>(matcher: (ComputeMatcher, Either>> & GenericMatcher & NoInfer), otherwise: (value: Exclude, string>>>) => GenericOutput): (input: GenericInput) => (ReturnType>> | GenericOutput); +export declare function matchInformationOtherwise>, GenericOutput extends unknown>(input: GenericInput, matcher: FixDeepFunctionInfer>, GenericMatcher> & ForbiddenMoreKey, otherwise: (value: Exclude, string>>>) => GenericOutput): (ReturnType>> | GenericOutput); export {}; diff --git a/docs/public/libs/v1/either/safeCallback.d.ts b/docs/public/libs/v1/either/safeCallback.d.ts index f4a245cee..891100023 100644 --- a/docs/public/libs/v1/either/safeCallback.d.ts +++ b/docs/public/libs/v1/either/safeCallback.d.ts @@ -5,7 +5,7 @@ export type SafeCallbackError = Left<"safe-callback-error", unknown>; type Either = Right | Left; type ComputeSafeCallbackResult = ((GenericOutput extends Either ? GenericOutput : GenericOutput extends Promise ? Promise> : SafeCallbackSuccess) | SafeCallbackError); /** - * Runs a callback in a safe block. If the callback throws or returns a rejected promise, the function returns a "safe-callback-error" typed Left instead of propagating the error. + * Runs a callback in a safe block. If the callback throws or returns a rejected promise, the function returns or resolves to a "safe-callback-error" typed Left instead of propagating the error. * If the callback returns an Either, it is returned as-is; otherwise the value is wrapped in a Right. Promise results are handled the same way after resolution. * * Signature: `safeCallback(theFunction)` → returns a value or promise @@ -39,8 +39,8 @@ type ComputeSafeCallbackResult = ((GenericOutput * ``` * * @remarks - * - Catches exceptions thrown by the callback and rejected promises, then wraps them in a `Left<"safe-callback-error">` - * - Keeps an `Left` or `Right` returned by the callback untouched + * - Catches exceptions thrown by the callback and rejected promises, then returns or resolves to a `Left<"safe-callback-error">` + * - Keeps a `Left` or `Right` returned by the callback untouched * - Wraps successful non-Either values in a `Right<"safe-callback-success">` * - Useful for working in an unsafe environment (3rd party libraries, user code, etc.) * diff --git a/docs/public/libs/v1/either/unwrapSelection.d.ts b/docs/public/libs/v1/either/unwrapSelection.d.ts index 7734c4ed6..1b4d827f4 100644 --- a/docs/public/libs/v1/either/unwrapSelection.d.ts +++ b/docs/public/libs/v1/either/unwrapSelection.d.ts @@ -2,8 +2,9 @@ import { type Right } from "./right"; import { type Left } from "./left"; import { type Unwrap, type GetKindValue, type Kind } from "../common"; import { informationKind } from "./kind"; -import { type GetPropsWithValue } from "../object"; +import { type ForbiddenKey, type GetPropsWithValue } from "../object"; type Either = Right | Left; +type ForbiddenMoreKey> = ForbiddenKey>>, string>>; /** * Unwraps selected `Either` payloads according to an exhaustive information selector. Non-selected informations keep flowing as their original `Either`. * @@ -70,6 +71,6 @@ type Either = Right | Left; * @namespace E * */ -export declare function unwrapSelection>, boolean>>(input: GenericInput, selector: GenericSelector): (Unwrap | GetPropsWithValue, string>>>> | Exclude, string>>>); -export declare function unwrapSelection>, boolean>>(selector: GenericSelector): (input: GenericInput) => (Unwrap | GetPropsWithValue, string>>>> | Exclude, string>>>); +export declare function unwrapSelection>, boolean>>(selector: GenericSelector & ForbiddenMoreKey): (input: GenericInput) => (Unwrap | GetPropsWithValue, string>>>> | Exclude, string>>>); +export declare function unwrapSelection>, boolean>>(input: GenericInput, selector: GenericSelector & ForbiddenMoreKey): (Unwrap | GetPropsWithValue, string>>>> | Exclude, string>>>); export {}; diff --git a/docs/public/libs/v1/either/unwrapSelectionOrThrow.d.ts b/docs/public/libs/v1/either/unwrapSelectionOrThrow.d.ts index 593e2d129..60794e57f 100644 --- a/docs/public/libs/v1/either/unwrapSelectionOrThrow.d.ts +++ b/docs/public/libs/v1/either/unwrapSelectionOrThrow.d.ts @@ -1,5 +1,5 @@ import { type Unwrap, type GetKindValue, type Kind } from "../common"; -import { type GetPropsWithValue } from "../object"; +import { type ForbiddenKey, type GetPropsWithValue } from "../object"; import { type Left } from "./left"; import { type Right } from "./right"; import { informationKind } from "./kind"; @@ -10,6 +10,7 @@ export declare class HasNotSelectedInformationError extends HasNotSelectedInform constructor(value: unknown, selector: Record); } type Either = Right | Left; +type ForbiddenMoreKey> = ForbiddenKey>>, string>>; /** * Unwraps selected `Either` payloads according to an exhaustive information selector, and throws when the current information is not selected. * @@ -74,6 +75,6 @@ type Either = Right | Left; * @namespace E * */ -export declare function unwrapSelectionOrThrow>, boolean>>(input: GenericInput, selector: GenericSelector): Unwrap | GetPropsWithValue, string>>>>; -export declare function unwrapSelectionOrThrow>, boolean>>(selector: GenericSelector): (input: GenericInput) => Unwrap | GetPropsWithValue, string>>>>; +export declare function unwrapSelectionOrThrow>, boolean>>(selector: GenericSelector & ForbiddenMoreKey): (input: GenericInput) => Unwrap | GetPropsWithValue, string>>>>; +export declare function unwrapSelectionOrThrow>, boolean>>(input: GenericInput, selector: GenericSelector & ForbiddenMoreKey): Unwrap | GetPropsWithValue, string>>>>; export {}; diff --git a/docs/public/libs/v1/either/whenHasInformation.d.ts b/docs/public/libs/v1/either/whenHasInformation.d.ts index 60afe654a..ca977b6df 100644 --- a/docs/public/libs/v1/either/whenHasInformation.d.ts +++ b/docs/public/libs/v1/either/whenHasInformation.d.ts @@ -1,4 +1,4 @@ -import { type Kind, type WrappedValue, type AnyValue, type Unwrap, type BreakGenericLink } from "../common"; +import { type Kind, type WrappedValue, type Unwrap, type BreakGenericLink } from "../common"; import { type Right } from "./right"; import { type Left } from "./left"; import { informationKind } from "./kind"; @@ -39,6 +39,6 @@ type Either = Right | Left; * @namespace E * */ -export declare function whenHasInformation> : never), const GenericOutput extends AnyValue>(information: GenericInformation | GenericInformation[], theFunction: (value: Unwrap, Kind & WrappedValue>>) => GenericOutput): (input: GenericInput) => GenericOutput | Exclude, Kind>; -export declare function whenHasInformation> : never), const GenericOutput extends AnyValue>(input: GenericInput, information: GenericInformation | GenericInformation[], theFunction: (value: Unwrap, Kind & WrappedValue>>) => GenericOutput): GenericOutput | Exclude>; +export declare function whenHasInformation> : never), const GenericOutput extends unknown>(information: GenericInformation | GenericInformation[], theFunction: (value: Unwrap, Kind & WrappedValue>>) => GenericOutput): (input: GenericInput) => GenericOutput | Exclude, Kind>; +export declare function whenHasInformation> : never), const GenericOutput extends unknown>(input: GenericInput, information: GenericInformation | GenericInformation[], theFunction: (value: Unwrap, Kind & WrappedValue>>) => GenericOutput): GenericOutput | Exclude>; export {}; diff --git a/docs/public/libs/v1/either/whenIsSelected.cjs b/docs/public/libs/v1/either/whenIsSelected.cjs new file mode 100644 index 000000000..54f6c88c5 --- /dev/null +++ b/docs/public/libs/v1/either/whenIsSelected.cjs @@ -0,0 +1,19 @@ +'use strict'; + +var kind = require('./kind.cjs'); +var unwrap = require('../common/unwrap.cjs'); + +function whenIsSelected(...args) { + if (args.length === 2) { + const [selector, theFunction] = args; + return (input) => whenIsSelected(input, selector, theFunction); + } + const [input, selector, theFunction] = args; + if (kind.informationKind.has(input) + && selector[kind.informationKind.getValue(input)] === true) { + return theFunction(unwrap.unwrap(input)); + } + return input; +} + +exports.whenIsSelected = whenIsSelected; diff --git a/docs/public/libs/v1/either/whenIsSelected.d.ts b/docs/public/libs/v1/either/whenIsSelected.d.ts new file mode 100644 index 000000000..76186c194 --- /dev/null +++ b/docs/public/libs/v1/either/whenIsSelected.d.ts @@ -0,0 +1,85 @@ +import { type Kind, type Unwrap, type GetKindValue } from "../common"; +import { type Left } from "./left"; +import { type Right } from "./right"; +import { informationKind } from "./kind"; +import { type ForbiddenKey, type GetPropsWithValue } from "../object"; +type Either = Right | Left; +type ForbiddenMoreKey> = ForbiddenKey>>, string>>; +/** + * Runs a callback on the unwrapped payloads selected by an exhaustive `Either` information selector. + * + * **Supported call styles:** + * - Classic: `whenIsSelected(input, selector, theFunction)` -> returns the callback result or the original input + * - Curried: `whenIsSelected(selector, theFunction)` -> returns a function waiting for the input + * + * The selector must map every possible information to `true` or `false`. A `true` entry unwraps the matching payload and passes it to the callback; a `false` entry forwards the original `Either` unchanged. + * + * ```ts + * const payment = true + * ? E.right("payment.accepted", 120 as const) + * : E.left("payment.rejected", "insufficient funds" as const); + * + * const formattedPayment = E.whenIsSelected( + * payment, + * { + * "payment.accepted": true, + * "payment.rejected": false, + * }, + * (amount) => `paid:${amount}` as const, + * ); + * + * type formattedPaymentCheck = ExpectType< + * typeof formattedPayment, + * "paid:120" | E.Left<"payment.rejected", "insufficient funds">, + * "strict" + * >; + * + * const rejectedPayment = E.left( + * "payment.rejected", + * "insufficient funds" as const, + * ); + * + * const unchangedPayment = E.whenIsSelected( + * rejectedPayment, + * { + * "payment.rejected": false, + * }, + * () => 0, + * ); + * + * type unchangedPaymentCheck = ExpectType< + * typeof unchangedPayment, + * number | E.Left<"payment.rejected", "insufficient funds">, + * "strict" + * >; + * + * const pipedPayment = pipe( + * payment, + * E.whenIsSelected( + * { + * "payment.accepted": true, + * "payment.rejected": false, + * }, + * (amount) => ({ amount }), + * ), + * ); + * + * type pipedPaymentCheck = ExpectType< + * typeof pipedPayment, + * { readonly amount: 120 } | E.Left<"payment.rejected", "insufficient funds">, + * "strict" + * >; + * ``` + * + * @remarks A selector value typed as `boolean` keeps both the transformed and original `Either` branches in the return type. At runtime, only `true` executes the callback. + * + * @see https://utils.duplojs.dev/en/v1/api/either/whenIsSelected + * @see [`E.whenHasInformation`](https://utils.duplojs.dev/en/v1/api/either/whenHasInformation) + * @see [`E.unwrapSelection`](https://utils.duplojs.dev/en/v1/api/either/unwrapSelection) + * + * @namespace E + * + */ +export declare function whenIsSelected>, boolean>, const GenericOutput extends unknown>(selector: GenericSelector & ForbiddenMoreKey, theFunction: (value: Unwrap | GetPropsWithValue, string>>>>) => GenericOutput): (input: GenericInput) => (GenericOutput | Exclude, string>>>); +export declare function whenIsSelected>, boolean>, const GenericOutput extends unknown>(input: GenericInput, selector: GenericSelector & ForbiddenMoreKey, theFunction: (value: Unwrap | GetPropsWithValue, string>>>>) => GenericOutput): (GenericOutput | Exclude, string>>>); +export {}; diff --git a/docs/public/libs/v1/either/whenIsSelected.mjs b/docs/public/libs/v1/either/whenIsSelected.mjs new file mode 100644 index 000000000..c04fdce66 --- /dev/null +++ b/docs/public/libs/v1/either/whenIsSelected.mjs @@ -0,0 +1,17 @@ +import { informationKind } from './kind.mjs'; +import { unwrap } from '../common/unwrap.mjs'; + +function whenIsSelected(...args) { + if (args.length === 2) { + const [selector, theFunction] = args; + return (input) => whenIsSelected(input, selector, theFunction); + } + const [input, selector, theFunction] = args; + if (informationKind.has(input) + && selector[informationKind.getValue(input)] === true) { + return theFunction(unwrap(input)); + } + return input; +} + +export { whenIsSelected }; diff --git a/docs/public/libs/v1/index.cjs b/docs/public/libs/v1/index.cjs index 077a21e85..b5318290e 100644 --- a/docs/public/libs/v1/index.cjs +++ b/docs/public/libs/v1/index.cjs @@ -76,6 +76,8 @@ var kindClass = require('./common/kindClass.cjs'); var promiseAll = require('./common/promiseAll.cjs'); var detachObjectMethod = require('./common/detachObjectMethod.cjs'); var bindPrototypeMethods = require('./common/bindPrototypeMethods.cjs'); +var preparePipe = require('./common/preparePipe.cjs'); +var prepareAsyncPipe = require('./common/prepareAsyncPipe.cjs'); @@ -193,3 +195,5 @@ exports.kindClass = kindClass.kindClass; exports.promiseAll = promiseAll.promiseAll; exports.detachObjectMethod = detachObjectMethod.detachObjectMethod; exports.bindPrototypeMethods = bindPrototypeMethods.bindPrototypeMethods; +exports.preparePipe = preparePipe.preparePipe; +exports.prepareAsyncPipe = prepareAsyncPipe.prepareAsyncPipe; diff --git a/docs/public/libs/v1/index.mjs b/docs/public/libs/v1/index.mjs index ca46cfbf8..9e4afbb59 100644 --- a/docs/public/libs/v1/index.mjs +++ b/docs/public/libs/v1/index.mjs @@ -101,3 +101,5 @@ export { kindClass } from './common/kindClass.mjs'; export { promiseAll } from './common/promiseAll.mjs'; export { detachObjectMethod } from './common/detachObjectMethod.mjs'; export { bindPrototypeMethods } from './common/bindPrototypeMethods.mjs'; +export { preparePipe } from './common/preparePipe.mjs'; +export { prepareAsyncPipe } from './common/prepareAsyncPipe.mjs'; diff --git a/docs/public/libs/v1/metadata.json b/docs/public/libs/v1/metadata.json index 56cfa0f14..b4580993a 100644 --- a/docs/public/libs/v1/metadata.json +++ b/docs/public/libs/v1/metadata.json @@ -799,6 +799,50 @@ { "name": "primitive", "files": [ + { + "name": "matcher", + "files": [ + { + "name": "index.d.ts" + }, + { + "name": "matchWithNumber.cjs" + }, + { + "name": "matchWithNumber.d.ts" + }, + { + "name": "matchWithNumber.mjs" + }, + { + "name": "matchWithNumberOtherwise.cjs" + }, + { + "name": "matchWithNumberOtherwise.d.ts" + }, + { + "name": "matchWithNumberOtherwise.mjs" + }, + { + "name": "matchWithString.cjs" + }, + { + "name": "matchWithString.d.ts" + }, + { + "name": "matchWithString.mjs" + }, + { + "name": "matchWithStringOtherwise.cjs" + }, + { + "name": "matchWithStringOtherwise.d.ts" + }, + { + "name": "matchWithStringOtherwise.mjs" + } + ] + }, { "name": "operations", "files": [ @@ -1086,6 +1130,9 @@ }, { "name": "index.d.ts" + }, + { + "name": "onlyLiteral.d.ts" } ] }, @@ -1230,13 +1277,13 @@ "name": "breakGenericLink.d.ts" }, { - "name": "CleanObjectEntry.d.ts" + "name": "cleanObjectEntry.d.ts" }, { "name": "clearClassKeys.d.ts" }, { - "name": "ComputedTypeError.d.ts" + "name": "computedTypeError.d.ts" }, { "name": "deepPartial.d.ts" @@ -1765,6 +1812,24 @@ { "name": "pipeCall.mjs" }, + { + "name": "prepareAsyncPipe.cjs" + }, + { + "name": "prepareAsyncPipe.d.ts" + }, + { + "name": "prepareAsyncPipe.mjs" + }, + { + "name": "preparePipe.cjs" + }, + { + "name": "preparePipe.d.ts" + }, + { + "name": "preparePipe.mjs" + }, { "name": "printer.cjs" }, @@ -4304,6 +4369,15 @@ }, { "name": "whenHasInformation.mjs" + }, + { + "name": "whenIsSelected.cjs" + }, + { + "name": "whenIsSelected.d.ts" + }, + { + "name": "whenIsSelected.mjs" } ] }, @@ -5434,6 +5508,42 @@ { "name": "isMatch.mjs" }, + { + "name": "matchWithNumber.cjs" + }, + { + "name": "matchWithNumber.d.ts" + }, + { + "name": "matchWithNumber.mjs" + }, + { + "name": "matchWithNumberOtherwise.cjs" + }, + { + "name": "matchWithNumberOtherwise.d.ts" + }, + { + "name": "matchWithNumberOtherwise.mjs" + }, + { + "name": "matchWithString.cjs" + }, + { + "name": "matchWithString.d.ts" + }, + { + "name": "matchWithString.mjs" + }, + { + "name": "matchWithStringOtherwise.cjs" + }, + { + "name": "matchWithStringOtherwise.d.ts" + }, + { + "name": "matchWithStringOtherwise.mjs" + }, { "name": "otherwise.cjs" }, diff --git a/docs/public/libs/v1/pattern/index.cjs b/docs/public/libs/v1/pattern/index.cjs index a044b2bf4..63f943c2e 100644 --- a/docs/public/libs/v1/pattern/index.cjs +++ b/docs/public/libs/v1/pattern/index.cjs @@ -8,6 +8,10 @@ var isMatch = require('./isMatch.cjs'); var union = require('./union.cjs'); var when = require('./when.cjs'); var whenNot = require('./whenNot.cjs'); +var matchWithString = require('./matchWithString.cjs'); +var matchWithNumber = require('./matchWithNumber.cjs'); +var matchWithStringOtherwise = require('./matchWithStringOtherwise.cjs'); +var matchWithNumberOtherwise = require('./matchWithNumberOtherwise.cjs'); var builder = require('./match/builder.cjs'); var pattern = require('./types/pattern.cjs'); @@ -24,6 +28,10 @@ exports.isMatch = isMatch.isMatch; exports.union = union.union; exports.when = when.when; exports.whenNot = whenNot.whenNot; +exports.matchWithString = matchWithString.matchWithString; +exports.matchWithNumber = matchWithNumber.matchWithNumber; +exports.matchWithStringOtherwise = matchWithStringOtherwise.matchWithStringOtherwise; +exports.matchWithNumberOtherwise = matchWithNumberOtherwise.matchWithNumberOtherwise; exports.InvalidExhaustivePatternError = builder.InvalidExhaustivePatternError; exports.matchBuilder = builder.matchBuilder; exports.SymbolToolPatternFunctionLabel = pattern.SymbolToolPatternFunctionLabel; diff --git a/docs/public/libs/v1/pattern/index.d.ts b/docs/public/libs/v1/pattern/index.d.ts index 04220c4c5..c13426525 100644 --- a/docs/public/libs/v1/pattern/index.d.ts +++ b/docs/public/libs/v1/pattern/index.d.ts @@ -31,3 +31,7 @@ export * from "./types"; export * from "./union"; export * from "./when"; export * from "./whenNot"; +export * from "./matchWithString"; +export * from "./matchWithNumber"; +export * from "./matchWithStringOtherwise"; +export * from "./matchWithNumberOtherwise"; diff --git a/docs/public/libs/v1/pattern/index.mjs b/docs/public/libs/v1/pattern/index.mjs index c48ddfd7a..e978312b5 100644 --- a/docs/public/libs/v1/pattern/index.mjs +++ b/docs/public/libs/v1/pattern/index.mjs @@ -6,6 +6,10 @@ export { isMatch } from './isMatch.mjs'; export { union } from './union.mjs'; export { when } from './when.mjs'; export { whenNot } from './whenNot.mjs'; +export { matchWithString } from './matchWithString.mjs'; +export { matchWithNumber } from './matchWithNumber.mjs'; +export { matchWithStringOtherwise } from './matchWithStringOtherwise.mjs'; +export { matchWithNumberOtherwise } from './matchWithNumberOtherwise.mjs'; export { InvalidExhaustivePatternError, matchBuilder } from './match/builder.mjs'; export { SymbolToolPatternFunctionLabel } from './types/pattern.mjs'; diff --git a/docs/public/libs/v1/pattern/matchWithNumber.cjs b/docs/public/libs/v1/pattern/matchWithNumber.cjs new file mode 100644 index 000000000..e88632d8c --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithNumber.cjs @@ -0,0 +1,12 @@ +'use strict'; + +function matchWithNumber(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[input](input); + } + const [input, matcher] = args; + return matcher[input](input); +} + +exports.matchWithNumber = matchWithNumber; diff --git a/docs/public/libs/v1/pattern/matchWithNumber.d.ts b/docs/public/libs/v1/pattern/matchWithNumber.d.ts new file mode 100644 index 000000000..9a522924d --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithNumber.d.ts @@ -0,0 +1,50 @@ +import { type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type OnlyLiteralNumber } from "../common"; +type ComputeMatcher = { + [Prop in GenericInput]: (value: Prop) => unknown; +}; +type ForbiddenMoreKey> = Exclude extends infer InferredKey ? IsEqual extends true ? unknown : ComputedTypeError<`Key "${Extract}" is forbidden.`> : never; +/** + * Performs exhaustive pattern matching on a number literal union. + * + * **Supported call styles:** + * - Classic: `matchWithNumber(input, matcher)` -> runs the handler matching the input + * - Curried: `matchWithNumber(matcher)` -> returns a function waiting for the input + * + * Every possible literal must have exactly one matcher key, guaranteeing that no case is left unprocessed. The selected handler receives the number literal corresponding to its key, and the result type is the union of every handler return type. + * + * ```ts + * const status = 200 as 200 | 404; + * + * P.matchWithNumber(status, { + * 200: () => "success", + * 404: () => "missing", + * }); // "success" | "missing" + * + * pipe( + * status, + * P.matchWithNumber({ + * 200: (value) => `code: ${value}`, + * 404: () => "not found", + * }), + * ); // string + * + * const retry = P.matchWithNumber( + * status, + * { + * 200: () => false, + * 404: (value) => value === 404, + * }, + * ); // boolean + * ``` + * + * @remarks Non-literal `number` inputs are rejected because a finite matcher cannot be exhaustive for them. + * + * @see https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumber + * @see [`P.matchWithString`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithString) + * + * @namespace P + * + */ +export declare function matchWithNumber>(matcher: GenericMatcher & ComputeMatcher> & ForbiddenMoreKey, GenericMatcher>): (input: GenericInput & OnlyLiteralNumber) => ReturnType[keyof GenericMatcher]>; +export declare function matchWithNumber>(input: GenericInput & OnlyLiteralNumber, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey): ReturnType; +export {}; diff --git a/docs/public/libs/v1/pattern/matchWithNumber.mjs b/docs/public/libs/v1/pattern/matchWithNumber.mjs new file mode 100644 index 000000000..bbf8464c9 --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithNumber.mjs @@ -0,0 +1,10 @@ +function matchWithNumber(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[input](input); + } + const [input, matcher] = args; + return matcher[input](input); +} + +export { matchWithNumber }; diff --git a/docs/public/libs/v1/pattern/matchWithNumberOtherwise.cjs b/docs/public/libs/v1/pattern/matchWithNumberOtherwise.cjs new file mode 100644 index 000000000..9cb02f91d --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithNumberOtherwise.cjs @@ -0,0 +1,16 @@ +'use strict'; + +function execute(input, matcher, otherwise) { + const callback = matcher[input]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithNumberOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +exports.matchWithNumberOtherwise = matchWithNumberOtherwise; diff --git a/docs/public/libs/v1/pattern/matchWithNumberOtherwise.d.ts b/docs/public/libs/v1/pattern/matchWithNumberOtherwise.d.ts new file mode 100644 index 000000000..4958d817d --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithNumberOtherwise.d.ts @@ -0,0 +1,56 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type OnlyLiteralNumber } from "../common"; +import { type GetPropsWithValueExtends } from "../object"; +type ComputeMatcher = { + [Prop in GenericInput]?: (value: Prop) => unknown; +}; +type ForbiddenMoreKey> = Exclude extends infer InferredKey ? IsEqual extends true ? unknown : ComputedTypeError<`Key "${Extract}" is forbidden.`> : never; +type HandledKeys = Extract, number>; +/** Performs partial pattern matching on a number literal union and delegates unhandled values to an `otherwise` callback. +/** +/** **Supported call styles:** +/** - Classic: `matchWithNumberOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +/** - Curried: `matchWithNumberOtherwise(matcher, otherwise)` -> returns a function waiting for the input +/** +/** Matcher keys must belong to the input union, but they do not need to cover it exhaustively. Each handler receives its matching literal, while `otherwise` receives the precise union of unhandled literals. +/** +/** ```ts +/** const status = 200 as 200 | 404 | 500; +/** +/** P.matchWithNumberOtherwise( +/** status, +/** { +/** 200: () => "success", +/** }, +/** (value) => `unhandled: ${value}`, +/** ); // string +/** +/** pipe( +/** status, +/** P.matchWithNumberOtherwise( +/** { +/** 404: () => "missing", +/** }, +/** (value) => value, +/** ), +/** ); // "missing" | 200 | 500 +/** +/** const message = P.matchWithNumberOtherwise( +/** status, +/** { +/** 200: (value) => `handled: ${value}`, +/** 404: undefined, +/** }, +/** (value) => `fallback: ${value}`, +/** ); // string +/** ``` +/** +/** @remarks Non-literal `number` inputs are rejected because their remaining cases cannot be represented as a finite literal union. +/** +/** @see https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumberOtherwise +/** @see [`P.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumber) +/** +/** @namespace P +/** */ +export declare function matchWithNumberOtherwise, GenericOutput>(matcher: GenericMatcher & ComputeMatcher> & ForbiddenMoreKey, GenericMatcher>, otherwise: (value: Exclude>) => GenericOutput): (input: GenericInput & OnlyLiteralNumber) => (ReturnType[keyof GenericMatcher], AnyFunction>> | GenericOutput); +export declare function matchWithNumberOtherwise, GenericOutput>(input: GenericInput & OnlyLiteralNumber, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey, otherwise: (value: Exclude>) => GenericOutput): (ReturnType> | GenericOutput); +export {}; diff --git a/docs/public/libs/v1/pattern/matchWithNumberOtherwise.mjs b/docs/public/libs/v1/pattern/matchWithNumberOtherwise.mjs new file mode 100644 index 000000000..45d0b9e9a --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithNumberOtherwise.mjs @@ -0,0 +1,14 @@ +function execute(input, matcher, otherwise) { + const callback = matcher[input]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithNumberOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +export { matchWithNumberOtherwise }; diff --git a/docs/public/libs/v1/pattern/matchWithString.cjs b/docs/public/libs/v1/pattern/matchWithString.cjs new file mode 100644 index 000000000..ebd954389 --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithString.cjs @@ -0,0 +1,12 @@ +'use strict'; + +function matchWithString(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[input](input); + } + const [input, matcher] = args; + return matcher[input](input); +} + +exports.matchWithString = matchWithString; diff --git a/docs/public/libs/v1/pattern/matchWithString.d.ts b/docs/public/libs/v1/pattern/matchWithString.d.ts new file mode 100644 index 000000000..260d62b22 --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithString.d.ts @@ -0,0 +1,51 @@ +import { type FixDeepFunctionInfer, type OnlyLiteralString } from "../common"; +import { type ForbiddenKey } from "../object"; +type ComputeMatcher = { + [Prop in GenericInput]: (value: Prop) => unknown; +}; +type ForbiddenMoreKey> = ForbiddenKey, string>>; +/** + * Performs exhaustive pattern matching on a string literal union. + * + * **Supported call styles:** + * - Classic: `matchWithString(input, matcher)` -> runs the handler matching the input + * - Curried: `matchWithString(matcher)` -> returns a function waiting for the input + * + * Every possible literal must have exactly one matcher key, guaranteeing that no case is left unprocessed. The selected handler receives the string literal corresponding to its key, and the result type is the union of every handler return type. + * + * ```ts + * const status = "success" as "success" | "failure"; + * + * P.matchWithString(status, { + * success: () => 200, + * failure: () => 500, + * }); // 200 | 500 + * + * pipe( + * status, + * P.matchWithString({ + * success: (value) => value.toUpperCase(), + * failure: () => "retry", + * }), + * ); // string + * + * const message = P.matchWithString( + * status, + * { + * success: (value) => `received: ${value}`, + * failure: (value) => `received: ${value}`, + * }, + * ); // string + * ``` + * + * @remarks Non-literal `string` inputs are rejected because a finite matcher cannot be exhaustive for them. + * + * @see https://utils.duplojs.dev/en/v1/api/pattern/matchWithString + * @see [`P.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumber) + * + * @namespace P + * + */ +export declare function matchWithString>(matcher: GenericMatcher & ComputeMatcher> & ForbiddenMoreKey, GenericMatcher>): (input: GenericInput & OnlyLiteralString) => ReturnType[keyof GenericMatcher]>; +export declare function matchWithString>(input: GenericInput & OnlyLiteralString, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey): ReturnType; +export {}; diff --git a/docs/public/libs/v1/pattern/matchWithString.mjs b/docs/public/libs/v1/pattern/matchWithString.mjs new file mode 100644 index 000000000..b95808a8d --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithString.mjs @@ -0,0 +1,10 @@ +function matchWithString(...args) { + if (args.length === 1) { + const [matcher] = args; + return (input) => matcher[input](input); + } + const [input, matcher] = args; + return matcher[input](input); +} + +export { matchWithString }; diff --git a/docs/public/libs/v1/pattern/matchWithStringOtherwise.cjs b/docs/public/libs/v1/pattern/matchWithStringOtherwise.cjs new file mode 100644 index 000000000..e17e04955 --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithStringOtherwise.cjs @@ -0,0 +1,16 @@ +'use strict'; + +function execute(input, matcher, otherwise) { + const callback = matcher[input]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithStringOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +exports.matchWithStringOtherwise = matchWithStringOtherwise; diff --git a/docs/public/libs/v1/pattern/matchWithStringOtherwise.d.ts b/docs/public/libs/v1/pattern/matchWithStringOtherwise.d.ts new file mode 100644 index 000000000..f890b90bf --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithStringOtherwise.d.ts @@ -0,0 +1,56 @@ +import { type AnyFunction, type FixDeepFunctionInfer, type OnlyLiteralString } from "../common"; +import { type ForbiddenKey, type GetPropsWithValueExtends } from "../object"; +type ComputeMatcher = { + [Prop in GenericInput]?: (value: Prop) => unknown; +}; +type ForbiddenMoreKey> = ForbiddenKey, string>>; +type HandledKeys = Extract, string>; +/** Performs partial pattern matching on a string literal union and delegates unhandled values to an `otherwise` callback. +/** +/** **Supported call styles:** +/** - Classic: `matchWithStringOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +/** - Curried: `matchWithStringOtherwise(matcher, otherwise)` -> returns a function waiting for the input +/** +/** Matcher keys must belong to the input union, but they do not need to cover it exhaustively. Each handler receives its matching literal, while `otherwise` receives the precise union of unhandled literals. +/** +/** ```ts +/** const status = "success" as "success" | "failure" | "pending"; +/** +/** P.matchWithStringOtherwise( +/** status, +/** { +/** success: () => 200, +/** }, +/** (value) => `unhandled: ${value}`, +/** ); // number | string +/** +/** pipe( +/** status, +/** P.matchWithStringOtherwise( +/** { +/** failure: () => "retry", +/** }, +/** (value) => value, +/** ), +/** ); // "retry" | "success" | "pending" +/** +/** const message = P.matchWithStringOtherwise( +/** status, +/** { +/** success: (value) => `handled: ${value}`, +/** failure: undefined, +/** }, +/** (value) => `fallback: ${value}`, +/** ); // string +/** ``` +/** +/** @remarks Non-literal `string` inputs are rejected because their remaining cases cannot be represented as a finite literal union. +/** +/** @see https://utils.duplojs.dev/en/v1/api/pattern/matchWithStringOtherwise +/** @see [`P.matchWithString`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithString) +/** +/** @namespace P +/** */ +export declare function matchWithStringOtherwise, GenericOutput>(matcher: GenericMatcher & ComputeMatcher> & ForbiddenMoreKey, GenericMatcher>, otherwise: (value: Exclude>) => GenericOutput): (input: GenericInput & OnlyLiteralString) => (ReturnType[keyof GenericMatcher], AnyFunction>> | GenericOutput); +export declare function matchWithStringOtherwise, GenericOutput>(input: GenericInput & OnlyLiteralString, matcher: FixDeepFunctionInfer, GenericMatcher> & ForbiddenMoreKey, otherwise: (value: Exclude>) => GenericOutput): (ReturnType> | GenericOutput); +export {}; diff --git a/docs/public/libs/v1/pattern/matchWithStringOtherwise.mjs b/docs/public/libs/v1/pattern/matchWithStringOtherwise.mjs new file mode 100644 index 000000000..7f3bd68d4 --- /dev/null +++ b/docs/public/libs/v1/pattern/matchWithStringOtherwise.mjs @@ -0,0 +1,14 @@ +function execute(input, matcher, otherwise) { + const callback = matcher[input]; + return callback === undefined ? otherwise(input) : callback(input); +} +function matchWithStringOtherwise(...args) { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input) => execute(input, matcher, otherwise); + } + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} + +export { matchWithStringOtherwise }; diff --git a/eslint.config.js b/eslint.config.js index def406d89..60e2b8f4c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -41,6 +41,6 @@ export default [ }, }, { - ignores: ["coverage", "dist", "docs/public/*", "docs/.vitepress/cache/*", "docs/.vitepress/dist/*"] + ignores: ["coverage", "dist", "docs/public/*", "docs/.vitepress/cache/*", "docs/.vitepress/dist/*", "docs/.vitepress/.temp/*"] } ]; diff --git a/jsDoc/clean/matchWithNumber/example.ts b/jsDoc/clean/matchWithNumber/example.ts new file mode 100644 index 000000000..cee57cc51 --- /dev/null +++ b/jsDoc/clean/matchWithNumber/example.ts @@ -0,0 +1,27 @@ +import { C, pipe } from "@scripts"; + +const status = C.Number.createOrThrow( + 200 as 200 | 404, +); + +C.matchWithNumber(status, { + 200: () => "success", + 404: () => "missing", +}); // "success" | "missing" + +pipe( + status, + C.matchWithNumber({ + 200: (value) => value, + 404: () => status, + }), +); // C.Primitive<200 | 404> + +const positiveStatus = C.Positive.createOrThrow( + 200 as 200 | 404, +); + +C.matchWithNumber(positiveStatus, { + 200: (value) => value, + 404: (value) => value, +}); // C.ConstrainedType<"positive", 200 | 404> diff --git a/jsDoc/clean/matchWithNumber/index.md b/jsDoc/clean/matchWithNumber/index.md new file mode 100644 index 000000000..00752509e --- /dev/null +++ b/jsDoc/clean/matchWithNumber/index.md @@ -0,0 +1,18 @@ +Performs exhaustive pattern matching on the value of a Clean number primitive. + +**Supported call styles:** +- Classic: `matchWithNumber(input, matcher)` -> runs the handler matching the wrapped value +- Curried: `matchWithNumber(matcher)` -> returns a function waiting for the primitive + +A Clean primitive cannot be used directly as an object key, so the matcher uses raw number values as keys. Every possible value must have a handler. The selected handler receives the original Clean primitive narrowed to the matching key, preserving its constraints and `NewType` metadata. + +```ts +{@include clean/matchWithNumber/example.ts[3,27]} +``` + +@remarks A broad `Primitive` is supported with a `Record` matcher. Literal unions reject missing and additional keys. + +@see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumber +@see [`C.matchWithString`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithString) + +@namespace C diff --git a/jsDoc/clean/matchWithNumberOtherwise/example.ts b/jsDoc/clean/matchWithNumberOtherwise/example.ts new file mode 100644 index 000000000..bcae525a1 --- /dev/null +++ b/jsDoc/clean/matchWithNumberOtherwise/example.ts @@ -0,0 +1,32 @@ +import { C, pipe } from "@scripts"; + +const status = C.Number.createOrThrow( + 200 as 200 | 404 | 500, +); + +C.matchWithNumberOtherwise( + status, + { + 200: () => "success", + }, + () => "error", +); // string + +pipe( + status, + C.matchWithNumberOtherwise( + { + 404: () => "missing", + }, + (value) => value, + ), +); // "missing" | C.Primitive<200 | 500> + +const result = C.matchWithNumberOtherwise( + status, + { + 200: (value) => value, + 404: undefined, + }, + (value) => value, +); // C.Primitive<200 | 404 | 500> diff --git a/jsDoc/clean/matchWithNumberOtherwise/index.md b/jsDoc/clean/matchWithNumberOtherwise/index.md new file mode 100644 index 000000000..0feb65d0a --- /dev/null +++ b/jsDoc/clean/matchWithNumberOtherwise/index.md @@ -0,0 +1,18 @@ +Performs partial pattern matching on a Clean number primitive and delegates unhandled values to an `otherwise` callback. + +**Supported call styles:** +- Classic: `matchWithNumberOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +- Curried: `matchWithNumberOtherwise(matcher, otherwise)` -> returns a function waiting for the primitive + +Raw number values are used as matcher keys. Handlers receive the original primitive narrowed to their key, and `otherwise` receives the same original primitive narrowed to all remaining values. + +```ts +{@include clean/matchWithNumberOtherwise/example.ts[3,32]} +``` + +@remarks Constraints and `NewType` metadata remain attached to the primitive passed to every callback. + +@see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumberOtherwise +@see [`C.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumber) + +@namespace C diff --git a/jsDoc/clean/matchWithString/example.ts b/jsDoc/clean/matchWithString/example.ts new file mode 100644 index 000000000..5d6926bd5 --- /dev/null +++ b/jsDoc/clean/matchWithString/example.ts @@ -0,0 +1,27 @@ +import { C, pipe } from "@scripts"; + +const status = C.String.createOrThrow( + "success" as "success" | "failure", +); + +C.matchWithString(status, { + success: () => 200, + failure: () => 500, +}); // 200 | 500 + +pipe( + status, + C.matchWithString({ + success: (value) => value, + failure: () => status, + }), +); // C.Primitive<"success" | "failure"> + +const noBlankStatus = C.NoBlank.createOrThrow( + "success" as "success" | "failure", +); + +C.matchWithString(noBlankStatus, { + success: (value) => value, + failure: (value) => value, +}); // C.ConstrainedType<"no-blank", "success" | "failure"> diff --git a/jsDoc/clean/matchWithString/index.md b/jsDoc/clean/matchWithString/index.md new file mode 100644 index 000000000..61af15b1e --- /dev/null +++ b/jsDoc/clean/matchWithString/index.md @@ -0,0 +1,18 @@ +Performs exhaustive pattern matching on the value of a Clean string primitive. + +**Supported call styles:** +- Classic: `matchWithString(input, matcher)` -> runs the handler matching the wrapped value +- Curried: `matchWithString(matcher)` -> returns a function waiting for the primitive + +A Clean primitive cannot be used directly as an object key, so the matcher uses raw string values as keys. Every possible value must have a handler. The selected handler receives the original Clean primitive narrowed to the matching key, preserving its constraints and `NewType` metadata. + +```ts +{@include clean/matchWithString/example.ts[3,27]} +``` + +@remarks A broad `Primitive` is supported with a `Record` matcher. Literal unions reject missing and additional keys. + +@see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithString +@see [`C.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithNumber) + +@namespace C diff --git a/jsDoc/clean/matchWithStringOtherwise/example.ts b/jsDoc/clean/matchWithStringOtherwise/example.ts new file mode 100644 index 000000000..c57d06e24 --- /dev/null +++ b/jsDoc/clean/matchWithStringOtherwise/example.ts @@ -0,0 +1,32 @@ +import { C, pipe } from "@scripts"; + +const status = C.String.createOrThrow( + "success" as "success" | "failure" | "pending", +); + +C.matchWithStringOtherwise( + status, + { + success: () => 200, + }, + () => 500, +); // number + +pipe( + status, + C.matchWithStringOtherwise( + { + failure: () => "retry", + }, + (value) => value, + ), +); // "retry" | C.Primitive<"success" | "pending"> + +const result = C.matchWithStringOtherwise( + status, + { + success: (value) => value, + failure: undefined, + }, + (value) => value, +); // C.Primitive<"success" | "failure" | "pending"> diff --git a/jsDoc/clean/matchWithStringOtherwise/index.md b/jsDoc/clean/matchWithStringOtherwise/index.md new file mode 100644 index 000000000..55349c863 --- /dev/null +++ b/jsDoc/clean/matchWithStringOtherwise/index.md @@ -0,0 +1,18 @@ +Performs partial pattern matching on a Clean string primitive and delegates unhandled values to an `otherwise` callback. + +**Supported call styles:** +- Classic: `matchWithStringOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +- Curried: `matchWithStringOtherwise(matcher, otherwise)` -> returns a function waiting for the primitive + +Raw string values are used as matcher keys. Handlers receive the original primitive narrowed to their key, and `otherwise` receives the same original primitive narrowed to all remaining values. + +```ts +{@include clean/matchWithStringOtherwise/example.ts[3,32]} +``` + +@remarks Constraints and `NewType` metadata remain attached to the primitive passed to every callback. + +@see https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithStringOtherwise +@see [`C.matchWithString`](https://utils.duplojs.dev/en/v1/api/clean/primitives/operators/matchWithString) + +@namespace C diff --git a/jsDoc/common/prepareAsyncPipe/example.ts b/jsDoc/common/prepareAsyncPipe/example.ts new file mode 100644 index 000000000..a76932d92 --- /dev/null +++ b/jsDoc/common/prepareAsyncPipe/example.ts @@ -0,0 +1,27 @@ +import { pipe, prepareAsyncPipe, S } from "@scripts"; + +const fetchLabel = prepareAsyncPipe()( + async(value) => Promise.resolve(value * 2), + S.to, +); + +await fetchLabel(10); // "20" +await fetchLabel(Promise.resolve(15)); // "30" + +const numberToString: ( + input: number +) => Promise = prepareAsyncPipe()( + S.to, +); + +await numberToString(42); // "42" + +const normalizeName = prepareAsyncPipe()( + async(value) => Promise.resolve(value.trim()), + (value) => value.toUpperCase(), +); + +await pipe( + " Ada ", + normalizeName, +); // "ADA" diff --git a/jsDoc/common/prepareAsyncPipe/index.md b/jsDoc/common/prepareAsyncPipe/index.md new file mode 100644 index 000000000..e70502846 --- /dev/null +++ b/jsDoc/common/prepareAsyncPipe/index.md @@ -0,0 +1,16 @@ +Prepares a reusable asynchronous pipeline whose steps are declared once and whose input type can be inferred from the resulting function's context. + +**Supported call styles:** +- Explicit input: `prepareAsyncPipe()(pipe1, pipe2)` -> returns a typed reusable async function +- Contextual input: `const fn: (input: Input) => Promise = prepareAsyncPipe()(pipe1, pipe2)` -> infers the local input from the target function type + +Each step may return a direct value or a promise. Every execution accepts a direct input or a promise, awaits the prepared sequence, and returns a promise of the final output. + +```ts +{@include common/prepareAsyncPipe/example.ts[3,27]} +``` + +@remarks Contextual input inference is useful when declaring recursive or mutually recursive async functions because the complete input-to-output contract can be written before the pipeline is built. + +@see https://utils.duplojs.dev/en/v1/api/common/prepareAsyncPipe +@see [`asyncInnerPipe`](https://utils.duplojs.dev/en/v1/api/common/asyncInnerPipe) diff --git a/jsDoc/common/preparePipe/example.ts b/jsDoc/common/preparePipe/example.ts new file mode 100644 index 000000000..3e2bb4392 --- /dev/null +++ b/jsDoc/common/preparePipe/example.ts @@ -0,0 +1,25 @@ +import { N, pipe, preparePipe, S } from "@scripts"; + +const formatPrice = preparePipe()( + N.multiply(2), + S.to, +); + +formatPrice(10); // "20" +formatPrice(15); // "30" + +const numberToString: (input: number) => string = preparePipe()( + S.to, +); + +numberToString(42); // "42" + +const normalizeName = preparePipe()( + (value) => value.trim(), + (value) => value.toUpperCase(), +); + +pipe( + " Ada ", + normalizeName, +); // "ADA" diff --git a/jsDoc/common/preparePipe/index.md b/jsDoc/common/preparePipe/index.md new file mode 100644 index 000000000..fa1441e56 --- /dev/null +++ b/jsDoc/common/preparePipe/index.md @@ -0,0 +1,16 @@ +Prepares a reusable synchronous pipeline whose steps are declared once and whose input type can be inferred from the resulting function's context. + +**Supported call styles:** +- Explicit input: `preparePipe()(pipe1, pipe2)` -> returns a typed reusable function +- Contextual input: `const fn: (input: Input) => Output = preparePipe()(pipe1, pipe2)` -> infers the local input from the target function type + +Each execution only calls the already prepared function. Up to fifteen steps are supported, and every intermediate output becomes the next step's input. + +```ts +{@include common/preparePipe/example.ts[3,25]} +``` + +@remarks Contextual input inference is useful when declaring recursive or mutually recursive functions because the complete input-to-output contract can be written before the pipeline is built. + +@see https://utils.duplojs.dev/en/v1/api/common/preparePipe +@see [`innerPipe`](https://utils.duplojs.dev/en/v1/api/common/innerPipe) diff --git a/jsDoc/either/whenIsSelected/example.ts b/jsDoc/either/whenIsSelected/example.ts new file mode 100644 index 000000000..4b5c04f59 --- /dev/null +++ b/jsDoc/either/whenIsSelected/example.ts @@ -0,0 +1,56 @@ +import { E, pipe, type ExpectType } from "@scripts"; + +const payment = true + ? E.right("payment.accepted", 120 as const) + : E.left("payment.rejected", "insufficient funds" as const); + +const formattedPayment = E.whenIsSelected( + payment, + { + "payment.accepted": true, + "payment.rejected": false, + }, + (amount) => `paid:${amount}` as const, +); + +type formattedPaymentCheck = ExpectType< + typeof formattedPayment, + "paid:120" | E.Left<"payment.rejected", "insufficient funds">, + "strict" +>; + +const rejectedPayment = E.left( + "payment.rejected", + "insufficient funds" as const, +); + +const unchangedPayment = E.whenIsSelected( + rejectedPayment, + { + "payment.rejected": false, + }, + () => 0, +); + +type unchangedPaymentCheck = ExpectType< + typeof unchangedPayment, + number | E.Left<"payment.rejected", "insufficient funds">, + "strict" +>; + +const pipedPayment = pipe( + payment, + E.whenIsSelected( + { + "payment.accepted": true, + "payment.rejected": false, + }, + (amount) => ({ amount }), + ), +); + +type pipedPaymentCheck = ExpectType< + typeof pipedPayment, + { readonly amount: 120 } | E.Left<"payment.rejected", "insufficient funds">, + "strict" +>; diff --git a/jsDoc/either/whenIsSelected/index.md b/jsDoc/either/whenIsSelected/index.md new file mode 100644 index 000000000..b34746661 --- /dev/null +++ b/jsDoc/either/whenIsSelected/index.md @@ -0,0 +1,19 @@ +Runs a callback on the unwrapped payloads selected by an exhaustive `Either` information selector. + +**Supported call styles:** +- Classic: `whenIsSelected(input, selector, theFunction)` -> returns the callback result or the original input +- Curried: `whenIsSelected(selector, theFunction)` -> returns a function waiting for the input + +The selector must map every possible information to `true` or `false`. A `true` entry unwraps the matching payload and passes it to the callback; a `false` entry forwards the original `Either` unchanged. + +```ts +{@include either/whenIsSelected/example.ts[3,56]} +``` + +@remarks A selector value typed as `boolean` keeps both the transformed and original `Either` branches in the return type. At runtime, only `true` executes the callback. + +@see https://utils.duplojs.dev/en/v1/api/either/whenIsSelected +@see [`E.whenHasInformation`](https://utils.duplojs.dev/en/v1/api/either/whenHasInformation) +@see [`E.unwrapSelection`](https://utils.duplojs.dev/en/v1/api/either/unwrapSelection) + +@namespace E diff --git a/jsDoc/pattern/matchWithNumber/example.ts b/jsDoc/pattern/matchWithNumber/example.ts new file mode 100644 index 000000000..d6da04386 --- /dev/null +++ b/jsDoc/pattern/matchWithNumber/example.ts @@ -0,0 +1,24 @@ +import { P, pipe } from "@scripts"; + +const status = 200 as 200 | 404; + +P.matchWithNumber(status, { + 200: () => "success", + 404: () => "missing", +}); // "success" | "missing" + +pipe( + status, + P.matchWithNumber({ + 200: (value) => `code: ${value}`, + 404: () => "not found", + }), +); // string + +const retry = P.matchWithNumber( + status, + { + 200: () => false, + 404: (value) => value === 404, + }, +); // boolean diff --git a/jsDoc/pattern/matchWithNumber/index.md b/jsDoc/pattern/matchWithNumber/index.md new file mode 100644 index 000000000..918f73f53 --- /dev/null +++ b/jsDoc/pattern/matchWithNumber/index.md @@ -0,0 +1,18 @@ +Performs exhaustive pattern matching on a number literal union. + +**Supported call styles:** +- Classic: `matchWithNumber(input, matcher)` -> runs the handler matching the input +- Curried: `matchWithNumber(matcher)` -> returns a function waiting for the input + +Every possible literal must have exactly one matcher key, guaranteeing that no case is left unprocessed. The selected handler receives the number literal corresponding to its key, and the result type is the union of every handler return type. + +```ts +{@include pattern/matchWithNumber/example.ts[3,24]} +``` + +@remarks Non-literal `number` inputs are rejected because a finite matcher cannot be exhaustive for them. + +@see https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumber +@see [`P.matchWithString`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithString) + +@namespace P diff --git a/jsDoc/pattern/matchWithNumberOtherwise/example.ts b/jsDoc/pattern/matchWithNumberOtherwise/example.ts new file mode 100644 index 000000000..a99e1ec51 --- /dev/null +++ b/jsDoc/pattern/matchWithNumberOtherwise/example.ts @@ -0,0 +1,30 @@ +import { P, pipe } from "@scripts"; + +const status = 200 as 200 | 404 | 500; + +P.matchWithNumberOtherwise( + status, + { + 200: () => "success", + }, + (value) => `unhandled: ${value}`, +); // string + +pipe( + status, + P.matchWithNumberOtherwise( + { + 404: () => "missing", + }, + (value) => value, + ), +); // "missing" | 200 | 500 + +const message = P.matchWithNumberOtherwise( + status, + { + 200: (value) => `handled: ${value}`, + 404: undefined, + }, + (value) => `fallback: ${value}`, +); // string diff --git a/jsDoc/pattern/matchWithNumberOtherwise/index.md b/jsDoc/pattern/matchWithNumberOtherwise/index.md new file mode 100644 index 000000000..df07b0c1f --- /dev/null +++ b/jsDoc/pattern/matchWithNumberOtherwise/index.md @@ -0,0 +1,18 @@ +Performs partial pattern matching on a number literal union and delegates unhandled values to an `otherwise` callback. + +**Supported call styles:** +- Classic: `matchWithNumberOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +- Curried: `matchWithNumberOtherwise(matcher, otherwise)` -> returns a function waiting for the input + +Matcher keys must belong to the input union, but they do not need to cover it exhaustively. Each handler receives its matching literal, while `otherwise` receives the precise union of unhandled literals. + +```ts +{@include pattern/matchWithNumberOtherwise/example.ts[3,30]} +``` + +@remarks Non-literal `number` inputs are rejected because their remaining cases cannot be represented as a finite literal union. + +@see https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumberOtherwise +@see [`P.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumber) + +@namespace P diff --git a/jsDoc/pattern/matchWithString/example.ts b/jsDoc/pattern/matchWithString/example.ts new file mode 100644 index 000000000..0aadff9d3 --- /dev/null +++ b/jsDoc/pattern/matchWithString/example.ts @@ -0,0 +1,24 @@ +import { P, pipe } from "@scripts"; + +const status = "success" as "success" | "failure"; + +P.matchWithString(status, { + success: () => 200, + failure: () => 500, +}); // 200 | 500 + +pipe( + status, + P.matchWithString({ + success: (value) => value.toUpperCase(), + failure: () => "retry", + }), +); // string + +const message = P.matchWithString( + status, + { + success: (value) => `received: ${value}`, + failure: (value) => `received: ${value}`, + }, +); // string diff --git a/jsDoc/pattern/matchWithString/index.md b/jsDoc/pattern/matchWithString/index.md new file mode 100644 index 000000000..190738b08 --- /dev/null +++ b/jsDoc/pattern/matchWithString/index.md @@ -0,0 +1,18 @@ +Performs exhaustive pattern matching on a string literal union. + +**Supported call styles:** +- Classic: `matchWithString(input, matcher)` -> runs the handler matching the input +- Curried: `matchWithString(matcher)` -> returns a function waiting for the input + +Every possible literal must have exactly one matcher key, guaranteeing that no case is left unprocessed. The selected handler receives the string literal corresponding to its key, and the result type is the union of every handler return type. + +```ts +{@include pattern/matchWithString/example.ts[3,24]} +``` + +@remarks Non-literal `string` inputs are rejected because a finite matcher cannot be exhaustive for them. + +@see https://utils.duplojs.dev/en/v1/api/pattern/matchWithString +@see [`P.matchWithNumber`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithNumber) + +@namespace P diff --git a/jsDoc/pattern/matchWithStringOtherwise/example.ts b/jsDoc/pattern/matchWithStringOtherwise/example.ts new file mode 100644 index 000000000..ac97778eb --- /dev/null +++ b/jsDoc/pattern/matchWithStringOtherwise/example.ts @@ -0,0 +1,30 @@ +import { P, pipe } from "@scripts"; + +const status = "success" as "success" | "failure" | "pending"; + +P.matchWithStringOtherwise( + status, + { + success: () => 200, + }, + (value) => `unhandled: ${value}`, +); // number | string + +pipe( + status, + P.matchWithStringOtherwise( + { + failure: () => "retry", + }, + (value) => value, + ), +); // "retry" | "success" | "pending" + +const message = P.matchWithStringOtherwise( + status, + { + success: (value) => `handled: ${value}`, + failure: undefined, + }, + (value) => `fallback: ${value}`, +); // string diff --git a/jsDoc/pattern/matchWithStringOtherwise/index.md b/jsDoc/pattern/matchWithStringOtherwise/index.md new file mode 100644 index 000000000..f9c057109 --- /dev/null +++ b/jsDoc/pattern/matchWithStringOtherwise/index.md @@ -0,0 +1,18 @@ +Performs partial pattern matching on a string literal union and delegates unhandled values to an `otherwise` callback. + +**Supported call styles:** +- Classic: `matchWithStringOtherwise(input, matcher, otherwise)` -> runs a matching handler or the fallback +- Curried: `matchWithStringOtherwise(matcher, otherwise)` -> returns a function waiting for the input + +Matcher keys must belong to the input union, but they do not need to cover it exhaustively. Each handler receives its matching literal, while `otherwise` receives the precise union of unhandled literals. + +```ts +{@include pattern/matchWithStringOtherwise/example.ts[3,30]} +``` + +@remarks Non-literal `string` inputs are rejected because their remaining cases cannot be represented as a finite literal union. + +@see https://utils.duplojs.dev/en/v1/api/pattern/matchWithStringOtherwise +@see [`P.matchWithString`](https://utils.duplojs.dev/en/v1/api/pattern/matchWithString) + +@namespace P diff --git a/package.json b/package.json index 7390c050e..f7b9b1130 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "test:tu:watch": "vitest --coverage --watch", "test:tu:update": "vitest --coverage --update", "test:types": "tsc -p tsconfig.test.json && npm -w docs run test:types", - "test:lint": "eslint --quiet", + "test:lint": "./.commands/test-lint.sh", "test:lint:fix": "eslint --fix --quiet", "prepare": "husky" }, diff --git a/scripts/clean/primitive/index.ts b/scripts/clean/primitive/index.ts index bff47ff87..8f4d045bf 100644 --- a/scripts/clean/primitive/index.ts +++ b/scripts/clean/primitive/index.ts @@ -1,2 +1,3 @@ export * from "./base"; export * from "./operations"; +export * from "./matcher"; diff --git a/scripts/clean/primitive/matcher/index.ts b/scripts/clean/primitive/matcher/index.ts new file mode 100644 index 000000000..8cfd5501b --- /dev/null +++ b/scripts/clean/primitive/matcher/index.ts @@ -0,0 +1,4 @@ +export * from "./matchWithString"; +export * from "./matchWithNumber"; +export * from "./matchWithStringOtherwise"; +export * from "./matchWithNumberOtherwise"; diff --git a/scripts/clean/primitive/matcher/matchWithNumber.ts b/scripts/clean/primitive/matcher/matchWithNumber.ts new file mode 100644 index 000000000..deda31384 --- /dev/null +++ b/scripts/clean/primitive/matcher/matchWithNumber.ts @@ -0,0 +1,67 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap, unwrap } from "@scripts/common"; +import { type Primitive } from "../base"; +import { type OnlyLiteralPrimitiveNumber } from "@scripts/clean/types/onlyLiteral"; + +type ComputeMatcher< + GenericInput extends Primitive, +> = { + [Prop in Unwrap]: ( + value: GenericInput & Primitive + ) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +> = Exclude> extends infer InferredKey + ? IsEqual extends true + ? unknown + : ComputedTypeError< + `Key "${Extract}" is forbidden.` + > + : never; + +/** + * {@include clean/matchWithNumber/index.md} + */ +export function matchWithNumber< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +>( + matcher: FixDeepFunctionInfer< + ComputeMatcher>, + GenericMatcher + > + & ForbiddenMoreKey, GenericMatcher> +): (input: GenericInput & OnlyLiteralPrimitiveNumber) => ReturnType< + NoInfer[keyof GenericMatcher] +>; + +export function matchWithNumber< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +>( + input: GenericInput & OnlyLiteralPrimitiveNumber, + matcher: FixDeepFunctionInfer< + ComputeMatcher, + GenericMatcher + > + & ForbiddenMoreKey +): ReturnType< + GenericMatcher[keyof GenericMatcher] +>; + +export function matchWithNumber( + ...args: [Primitive, Record] + | [Record] +): unknown { + if (args.length === 1) { + const [matcher] = args; + + return (input: Primitive) => matcher[unwrap(input)]!(input); + } + + const [input, matcher] = args; + + return matcher[unwrap(input)]!(input); +} diff --git a/scripts/clean/primitive/matcher/matchWithNumberOtherwise.ts b/scripts/clean/primitive/matcher/matchWithNumberOtherwise.ts new file mode 100644 index 000000000..9176d9192 --- /dev/null +++ b/scripts/clean/primitive/matcher/matchWithNumberOtherwise.ts @@ -0,0 +1,83 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap, unwrap } from "@scripts/common"; +import { type GetPropsWithValueExtends } from "@scripts/object"; +import { type OnlyLiteralPrimitiveNumber } from "@scripts/clean/types/onlyLiteral"; +import { type Primitive } from "../base"; + +type ComputeMatcher> = { + [Prop in Unwrap]?: ( + value: GenericInput & Primitive + ) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +> = Exclude> extends infer InferredKey + ? IsEqual extends true + ? unknown + : ComputedTypeError<`Key "${Extract}" is forbidden.`> + : never; + +type HandledKeys = Extract< + GetPropsWithValueExtends, + number +>; + +type OtherwiseValue< + GenericInput extends Primitive, + GenericMatcher extends object, +> = Extract< + & GenericInput + & Primitive, HandledKeys>>, + any +>; + +function execute( + input: Primitive, + matcher: Record, + otherwise: AnyFunction, +) { + const callback = matcher[unwrap(input)]; + return callback === undefined ? otherwise(input) : callback(input); +} + +/** {@include clean/matchWithNumberOtherwise/index.md} */ +export function matchWithNumberOtherwise< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + matcher: FixDeepFunctionInfer>, GenericMatcher> + & ForbiddenMoreKey, GenericMatcher>, + otherwise: (value: OtherwiseValue) => GenericOutput +): (input: GenericInput & OnlyLiteralPrimitiveNumber) => ( + | ReturnType[keyof GenericMatcher], AnyFunction>> + | GenericOutput +); + +export function matchWithNumberOtherwise< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + input: GenericInput & OnlyLiteralPrimitiveNumber, + matcher: FixDeepFunctionInfer, GenericMatcher> + & ForbiddenMoreKey, + otherwise: (value: OtherwiseValue) => GenericOutput +): ( + | ReturnType> + | GenericOutput +); + +export function matchWithNumberOtherwise( + ...args: [Primitive, Record, AnyFunction] + | [Record, AnyFunction] +): unknown { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input: Primitive) => execute(input, matcher, otherwise); + } + + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} diff --git a/scripts/clean/primitive/matcher/matchWithString.ts b/scripts/clean/primitive/matcher/matchWithString.ts new file mode 100644 index 000000000..ae3e22d47 --- /dev/null +++ b/scripts/clean/primitive/matcher/matchWithString.ts @@ -0,0 +1,67 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap, unwrap } from "@scripts/common"; +import { type Primitive } from "../base"; +import { type OnlyLiteralPrimitiveString } from "@scripts/clean/types/onlyLiteral"; + +type ComputeMatcher< + GenericInput extends Primitive, +> = { + [Prop in Unwrap]: ( + value: GenericInput & Primitive + ) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +> = Exclude> extends infer InferredKey + ? IsEqual extends true + ? unknown + : ComputedTypeError< + `Key "${Extract}" is forbidden.` + > + : never; + +/** + * {@include clean/matchWithString/index.md} + */ +export function matchWithString< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +>( + matcher: FixDeepFunctionInfer< + ComputeMatcher>, + GenericMatcher + > + & ForbiddenMoreKey, GenericMatcher> +): (input: GenericInput & OnlyLiteralPrimitiveString) => ReturnType< + NoInfer[keyof GenericMatcher] +>; + +export function matchWithString< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +>( + input: GenericInput & OnlyLiteralPrimitiveString, + matcher: FixDeepFunctionInfer< + ComputeMatcher, + GenericMatcher + > + & ForbiddenMoreKey +): ReturnType< + GenericMatcher[keyof GenericMatcher] +>; + +export function matchWithString( + ...args: [Primitive, Record] + | [Record] +): unknown { + if (args.length === 1) { + const [matcher] = args; + + return (input: Primitive) => matcher[unwrap(input)]!(input); + } + + const [input, matcher] = args; + + return matcher[unwrap(input)]!(input); +} diff --git a/scripts/clean/primitive/matcher/matchWithStringOtherwise.ts b/scripts/clean/primitive/matcher/matchWithStringOtherwise.ts new file mode 100644 index 000000000..7ca8e3ee3 --- /dev/null +++ b/scripts/clean/primitive/matcher/matchWithStringOtherwise.ts @@ -0,0 +1,83 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type Unwrap, unwrap } from "@scripts/common"; +import { type GetPropsWithValueExtends } from "@scripts/object"; +import { type OnlyLiteralPrimitiveString } from "@scripts/clean/types/onlyLiteral"; +import { type Primitive } from "../base"; + +type ComputeMatcher> = { + [Prop in Unwrap]?: ( + value: GenericInput & Primitive + ) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, +> = Exclude> extends infer InferredKey + ? IsEqual extends true + ? unknown + : ComputedTypeError<`Key "${Extract}" is forbidden.`> + : never; + +type HandledKeys = Extract< + GetPropsWithValueExtends, + string +>; + +type OtherwiseValue< + GenericInput extends Primitive, + GenericMatcher extends object, +> = Extract< + & GenericInput + & Primitive, HandledKeys>>, + any +>; + +function execute( + input: Primitive, + matcher: Record, + otherwise: AnyFunction, +) { + const callback = matcher[unwrap(input)]; + return callback === undefined ? otherwise(input) : callback(input); +} + +/** {@include clean/matchWithStringOtherwise/index.md} */ +export function matchWithStringOtherwise< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + matcher: FixDeepFunctionInfer>, GenericMatcher> + & ForbiddenMoreKey, GenericMatcher>, + otherwise: (value: OtherwiseValue) => GenericOutput +): (input: GenericInput & OnlyLiteralPrimitiveString) => ( + | ReturnType[keyof GenericMatcher], AnyFunction>> + | GenericOutput +); + +export function matchWithStringOtherwise< + GenericInput extends Primitive, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + input: GenericInput & OnlyLiteralPrimitiveString, + matcher: FixDeepFunctionInfer, GenericMatcher> + & ForbiddenMoreKey, + otherwise: (value: OtherwiseValue) => GenericOutput +): ( + | ReturnType> + | GenericOutput +); + +export function matchWithStringOtherwise( + ...args: [Primitive, Record, AnyFunction] + | [Record, AnyFunction] +): unknown { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input: Primitive) => execute(input, matcher, otherwise); + } + + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} diff --git a/scripts/clean/types/onlyLiteral.ts b/scripts/clean/types/onlyLiteral.ts new file mode 100644 index 000000000..a45459676 --- /dev/null +++ b/scripts/clean/types/onlyLiteral.ts @@ -0,0 +1,35 @@ +import { type Unwrap, type ComputedTypeError } from "@scripts/common"; +import { type Primitive } from "../primitive"; + +export type OnlyLiteralPrimitiveString< + GenericValue extends Primitive, +> = string extends Unwrap + ? ComputedTypeError<"Input primitive must be a literal string."> + : GenericValue; + +export type OnlyLiteralPrimitiveNumber< + GenericValue extends Primitive, +> = number extends Unwrap + ? ComputedTypeError<"Input primitive must be a literal number."> + : GenericValue; + +export type OnlyLiteralPrimitiveBigInt< + GenericValue extends Primitive, +> = bigint extends Unwrap + ? ComputedTypeError<"Input primitive must be a literal bigint."> + : GenericValue; + +export type OnlyLiteralPrimitiveBoolean< + GenericValue extends Primitive, +> = boolean extends Unwrap + ? ComputedTypeError<"Input primitive must be a literal boolean."> + : GenericValue; + +export type OnlyLiteral< + GenericValue extends Primitive, +> = ( + & OnlyLiteralPrimitiveString + & OnlyLiteralPrimitiveNumber + & OnlyLiteralPrimitiveBigInt + & OnlyLiteralPrimitiveBoolean +); diff --git a/scripts/common/index.ts b/scripts/common/index.ts index a6337c919..64ddd5679 100644 --- a/scripts/common/index.ts +++ b/scripts/common/index.ts @@ -65,3 +65,5 @@ export * from "./kindClass"; export * from "./promiseAll"; export * from "./detachObjectMethod"; export * from "./bindPrototypeMethods"; +export * from "./preparePipe"; +export * from "./prepareAsyncPipe"; diff --git a/scripts/common/prepareAsyncPipe.ts b/scripts/common/prepareAsyncPipe.ts new file mode 100644 index 000000000..86ce5bcd6 --- /dev/null +++ b/scripts/common/prepareAsyncPipe.ts @@ -0,0 +1,321 @@ +import { type BreakGenericLink, type AnyFunction, type MaybePromise } from "./types"; + +export interface PrepareAsyncPipe< + GenericInput extends unknown = undefined, +> { + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise, + pipe10: (input: GenericOutputPipe9) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise, + pipe10: (input: GenericOutputPipe9) => MaybePromise, + pipe11: (input: GenericOutputPipe10) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise, + pipe10: (input: GenericOutputPipe9) => MaybePromise, + pipe11: (input: GenericOutputPipe10) => MaybePromise, + pipe12: (input: GenericOutputPipe11) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + const GenericOutputPipe13 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise, + pipe10: (input: GenericOutputPipe9) => MaybePromise, + pipe11: (input: GenericOutputPipe10) => MaybePromise, + pipe12: (input: GenericOutputPipe11) => MaybePromise, + pipe13: (input: GenericOutputPipe12) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + const GenericOutputPipe13 extends unknown, + const GenericOutputPipe14 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise, + pipe10: (input: GenericOutputPipe9) => MaybePromise, + pipe11: (input: GenericOutputPipe10) => MaybePromise, + pipe12: (input: GenericOutputPipe11) => MaybePromise, + pipe13: (input: GenericOutputPipe12) => MaybePromise, + pipe14: (input: GenericOutputPipe13) => MaybePromise + ): (input: MaybePromise) => Promise; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + const GenericOutputPipe13 extends unknown, + const GenericOutputPipe14 extends unknown, + const GenericOutputPipe15 extends unknown, + >( + pipe1: (input: BreakGenericLink) => MaybePromise, + pipe2: (input: GenericOutputPipe1) => MaybePromise, + pipe3: (input: GenericOutputPipe2) => MaybePromise, + pipe4: (input: GenericOutputPipe3) => MaybePromise, + pipe5: (input: GenericOutputPipe4) => MaybePromise, + pipe6: (input: GenericOutputPipe5) => MaybePromise, + pipe7: (input: GenericOutputPipe6) => MaybePromise, + pipe8: (input: GenericOutputPipe7) => MaybePromise, + pipe9: (input: GenericOutputPipe8) => MaybePromise, + pipe10: (input: GenericOutputPipe9) => MaybePromise, + pipe11: (input: GenericOutputPipe10) => MaybePromise, + pipe12: (input: GenericOutputPipe11) => MaybePromise, + pipe13: (input: GenericOutputPipe12) => MaybePromise, + pipe14: (input: GenericOutputPipe13) => MaybePromise, + pipe15: (input: GenericOutputPipe14) => MaybePromise + ): (input: MaybePromise) => Promise; +} + +/** {@include common/prepareAsyncPipe/index.md} */ +export function prepareAsyncPipe< + GenericInput extends unknown = unknown, +>(): PrepareAsyncPipe { + return (...pipes: AnyFunction[]) => async(input: MaybePromise) => { + let acc = await input; + + for (const pipe of pipes) { + acc = await pipe(acc); + } + + return acc; + }; +} diff --git a/scripts/common/preparePipe.ts b/scripts/common/preparePipe.ts new file mode 100644 index 000000000..d5ec01b2a --- /dev/null +++ b/scripts/common/preparePipe.ts @@ -0,0 +1,321 @@ +import { type BreakGenericLink, type AnyFunction } from "./types"; + +export interface PreparePipe< + GenericInput extends unknown = undefined, +> { + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1 + ): (input: GenericLocalInput) => GenericOutputPipe1; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2 + ): (input: GenericLocalInput) => GenericOutputPipe2; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3 + ): (input: GenericLocalInput) => GenericOutputPipe3; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4 + ): (input: GenericLocalInput) => GenericOutputPipe4; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5 + ): (input: GenericLocalInput) => GenericOutputPipe5; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6 + ): (input: GenericLocalInput) => GenericOutputPipe6; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7 + ): (input: GenericLocalInput) => GenericOutputPipe7; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8 + ): (input: GenericLocalInput) => GenericOutputPipe8; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9 + ): (input: GenericLocalInput) => GenericOutputPipe9; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, + pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10 + ): (input: GenericLocalInput) => GenericOutputPipe10; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, + pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, + pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11 + ): (input: GenericLocalInput) => GenericOutputPipe11; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, + pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, + pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, + pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12 + ): (input: GenericLocalInput) => GenericOutputPipe12; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + const GenericOutputPipe13 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, + pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, + pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, + pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12, + pipe13: (input: GenericOutputPipe12) => GenericOutputPipe13 + ): (input: GenericLocalInput) => GenericOutputPipe13; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + const GenericOutputPipe13 extends unknown, + const GenericOutputPipe14 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, + pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, + pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, + pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12, + pipe13: (input: GenericOutputPipe12) => GenericOutputPipe13, + pipe14: (input: GenericOutputPipe13) => GenericOutputPipe14 + ): (input: GenericLocalInput) => GenericOutputPipe14; + < + const GenericLocalInput extends GenericInput, + const GenericOutputPipe1 extends unknown, + const GenericOutputPipe2 extends unknown, + const GenericOutputPipe3 extends unknown, + const GenericOutputPipe4 extends unknown, + const GenericOutputPipe5 extends unknown, + const GenericOutputPipe6 extends unknown, + const GenericOutputPipe7 extends unknown, + const GenericOutputPipe8 extends unknown, + const GenericOutputPipe9 extends unknown, + const GenericOutputPipe10 extends unknown, + const GenericOutputPipe11 extends unknown, + const GenericOutputPipe12 extends unknown, + const GenericOutputPipe13 extends unknown, + const GenericOutputPipe14 extends unknown, + const GenericOutputPipe15 extends unknown, + >( + pipe1: (input: BreakGenericLink) => GenericOutputPipe1, + pipe2: (input: GenericOutputPipe1) => GenericOutputPipe2, + pipe3: (input: GenericOutputPipe2) => GenericOutputPipe3, + pipe4: (input: GenericOutputPipe3) => GenericOutputPipe4, + pipe5: (input: GenericOutputPipe4) => GenericOutputPipe5, + pipe6: (input: GenericOutputPipe5) => GenericOutputPipe6, + pipe7: (input: GenericOutputPipe6) => GenericOutputPipe7, + pipe8: (input: GenericOutputPipe7) => GenericOutputPipe8, + pipe9: (input: GenericOutputPipe8) => GenericOutputPipe9, + pipe10: (input: GenericOutputPipe9) => GenericOutputPipe10, + pipe11: (input: GenericOutputPipe10) => GenericOutputPipe11, + pipe12: (input: GenericOutputPipe11) => GenericOutputPipe12, + pipe13: (input: GenericOutputPipe12) => GenericOutputPipe13, + pipe14: (input: GenericOutputPipe13) => GenericOutputPipe14, + pipe15: (input: GenericOutputPipe14) => GenericOutputPipe15 + ): (input: GenericLocalInput) => GenericOutputPipe15; +} + +/** {@include common/preparePipe/index.md} */ +export function preparePipe< + GenericInput extends unknown = unknown, +>(): PreparePipe { + return (...pipes: AnyFunction[]) => (input: GenericInput) => { + let acc = input; + + for (const pipe of pipes) { + acc = pipe(acc); + } + + return acc; + }; +} diff --git a/scripts/common/types/CleanObjectEntry.ts b/scripts/common/types/cleanObjectEntry.ts similarity index 100% rename from scripts/common/types/CleanObjectEntry.ts rename to scripts/common/types/cleanObjectEntry.ts diff --git a/scripts/common/types/ComputedTypeError.ts b/scripts/common/types/computedTypeError.ts similarity index 100% rename from scripts/common/types/ComputedTypeError.ts rename to scripts/common/types/computedTypeError.ts diff --git a/scripts/common/types/forbiddenTupleWithUnknownLength.ts b/scripts/common/types/forbiddenTupleWithUnknownLength.ts index af7752346..63e8e56d9 100644 --- a/scripts/common/types/forbiddenTupleWithUnknownLength.ts +++ b/scripts/common/types/forbiddenTupleWithUnknownLength.ts @@ -1,5 +1,5 @@ import { type AnyTuple } from "./anyTuple"; -import { type ComputedTypeError } from "./ComputedTypeError"; +import { type ComputedTypeError } from "./computedTypeError"; export type ForbiddenTupleWithUnknownLength< GenericTuple extends AnyTuple, diff --git a/scripts/common/types/index.ts b/scripts/common/types/index.ts index e31e70a57..c6e31c8b3 100644 --- a/scripts/common/types/index.ts +++ b/scripts/common/types/index.ts @@ -48,8 +48,8 @@ export * from "./json"; export * from "./predicate"; export * from "./bivariantFunction"; export * from "./maybeAsyncGenerator"; -export * from "./ComputedTypeError"; -export * from "./CleanObjectEntry"; +export * from "./computedTypeError"; +export * from "./cleanObjectEntry"; export * from "./requireConstructor"; export * from "./removeAbstractFromConstructor"; export * from "./clearClassKeys"; diff --git a/scripts/common/types/onlyLiteral.ts b/scripts/common/types/onlyLiteral.ts index ac5aa64fb..9db44d9cd 100644 --- a/scripts/common/types/onlyLiteral.ts +++ b/scripts/common/types/onlyLiteral.ts @@ -1,4 +1,4 @@ -import { type ComputedTypeError } from "./ComputedTypeError"; +import { type ComputedTypeError } from "./computedTypeError"; export type OnlyLiteralString< GenericValue extends unknown, diff --git a/scripts/common/types/requireConstructor.ts b/scripts/common/types/requireConstructor.ts index 8b374e9c1..bb4462a7b 100644 --- a/scripts/common/types/requireConstructor.ts +++ b/scripts/common/types/requireConstructor.ts @@ -1,5 +1,5 @@ import { type AnyAbstractConstructor } from "./anyConstructor"; -import { type ComputedTypeError } from "./ComputedTypeError"; +import { type ComputedTypeError } from "./computedTypeError"; export type RequireConstructor< GenericInput extends unknown, diff --git a/scripts/either/index.ts b/scripts/either/index.ts index 38cf080bc..bb4193f14 100644 --- a/scripts/either/index.ts +++ b/scripts/either/index.ts @@ -10,6 +10,7 @@ export * from "./optional"; export * from "./right"; export * from "./hasInformation"; export * from "./whenHasInformation"; +export * from "./whenIsSelected"; export * from "./kind"; export * from "./safeCallback"; export * from "./expect"; diff --git a/scripts/either/matchInformationOtherwise.ts b/scripts/either/matchInformationOtherwise.ts index 2940b4c43..f8c80db84 100644 --- a/scripts/either/matchInformationOtherwise.ts +++ b/scripts/either/matchInformationOtherwise.ts @@ -2,7 +2,7 @@ import { type AnyFunction, unwrap, type FixDeepFunctionInfer, type GetKindValue, import { informationKind } from "./kind"; import { type Right } from "./right"; import { type Left } from "./left"; -import { type GetPropsWithValueExtends } from "@scripts/object"; +import { type ForbiddenKey, type GetPropsWithValueExtends } from "@scripts/object"; type Either = Right | Left; @@ -30,6 +30,25 @@ type ComputeMatcher< any >; +type ForbiddenMoreKey< + GenericInput extends unknown, + GenericMatcher extends ComputeMatcher< + Extract + >, +> = ForbiddenKey< + GenericMatcher, + Extract< + Exclude< + keyof GenericMatcher, + GetKindValue< + typeof informationKind, + Extract + > + >, + string + > +>; + /** * {@include either/matchInformationOtherwise/index.md} */ @@ -39,12 +58,14 @@ export function matchInformationOtherwise< Extract >, GenericOutput extends unknown, + GenericError extends ForbiddenMoreKey, >( matcher: ( & ComputeMatcher< Extract, Either> > & GenericMatcher + & NoInfer ), otherwise: ( value: Exclude< @@ -86,7 +107,8 @@ export function matchInformationOtherwise< Extract >, GenericMatcher - >, + > + & ForbiddenMoreKey, otherwise: ( value: Exclude< GenericInput, diff --git a/scripts/either/unwrapSelection.ts b/scripts/either/unwrapSelection.ts index 0acee6c6f..addc0b090 100644 --- a/scripts/either/unwrapSelection.ts +++ b/scripts/either/unwrapSelection.ts @@ -2,16 +2,33 @@ import { type Right } from "./right"; import { type Left } from "./left"; import { unwrap, type Unwrap, type GetKindValue, type Kind } from "@scripts/common"; import { informationKind } from "./kind"; -import { type GetPropsWithValue } from "@scripts/object"; +import { type ForbiddenKey, type GetPropsWithValue } from "@scripts/object"; type Either = Right | Left; +type ForbiddenMoreKey< + GenericInput extends unknown, + GenericSelector extends Record, +> = ForbiddenKey< + GenericSelector, + Extract< + Exclude< + keyof GenericSelector, + GetKindValue< + typeof informationKind, + Extract + > + >, + string + > +>; + /** * {@include either/unwrapSelection/index.md} */ export function unwrapSelection< GenericInput extends unknown, - const GenericSelector extends Record< + GenericSelector extends Record< GetKindValue< typeof informationKind, Extract< @@ -22,9 +39,8 @@ export function unwrapSelection< boolean >, >( - input: GenericInput, - selector: GenericSelector, -): ( + selector: GenericSelector & ForbiddenMoreKey, +): (input: GenericInput) => ( | Unwrap< Extract< GenericInput, @@ -52,7 +68,7 @@ export function unwrapSelection< export function unwrapSelection< GenericInput extends unknown, - GenericSelector extends Record< + const GenericSelector extends Record< GetKindValue< typeof informationKind, Extract< @@ -63,8 +79,9 @@ export function unwrapSelection< boolean >, >( - selector: GenericSelector, -): (input: GenericInput) => ( + input: GenericInput, + selector: GenericSelector & ForbiddenMoreKey, +): ( | Unwrap< Extract< GenericInput, @@ -96,7 +113,7 @@ export function unwrapSelection( if (args.length === 1) { const [selector] = args; - return (input: unknown) => unwrapSelection(input, selector); + return (input: unknown) => unwrapSelection(input, selector as never); } const [input, selector] = args; diff --git a/scripts/either/unwrapSelectionOrThrow.ts b/scripts/either/unwrapSelectionOrThrow.ts index 8928b265e..052bd4f65 100644 --- a/scripts/either/unwrapSelectionOrThrow.ts +++ b/scripts/either/unwrapSelectionOrThrow.ts @@ -1,7 +1,7 @@ import { unwrap, type Unwrap, type GetKindValue, type Kind } from "@scripts/common"; import { createErrorKind } from "@scripts/common/errorKindNamespace"; import { kindClass } from "@scripts/common/kindClass"; -import { type GetPropsWithValue } from "@scripts/object"; +import { type ForbiddenKey, type GetPropsWithValue } from "@scripts/object"; import { type Left } from "./left"; import { type Right } from "./right"; import { informationKind } from "./kind"; @@ -20,12 +20,29 @@ export class HasNotSelectedInformationError extends kindClass( type Either = Right | Left; +type ForbiddenMoreKey< + GenericInput extends unknown, + GenericSelector extends Record, +> = ForbiddenKey< + GenericSelector, + Extract< + Exclude< + keyof GenericSelector, + GetKindValue< + typeof informationKind, + Extract + > + >, + string + > +>; + /** * {@include either/unwrapSelectionOrThrow/index.md} */ export function unwrapSelectionOrThrow< GenericInput extends unknown, - const GenericSelector extends Record< + GenericSelector extends Record< GetKindValue< typeof informationKind, Extract< @@ -36,9 +53,8 @@ export function unwrapSelectionOrThrow< boolean >, >( - input: GenericInput, - selector: GenericSelector, -): Unwrap< + selector: GenericSelector & ForbiddenMoreKey, +): (input: GenericInput) => Unwrap< Extract< GenericInput, Kind< @@ -54,7 +70,7 @@ export function unwrapSelectionOrThrow< export function unwrapSelectionOrThrow< GenericInput extends unknown, - GenericSelector extends Record< + const GenericSelector extends Record< GetKindValue< typeof informationKind, Extract< @@ -65,8 +81,9 @@ export function unwrapSelectionOrThrow< boolean >, >( - selector: GenericSelector, -): (input: GenericInput) => Unwrap< + input: GenericInput, + selector: GenericSelector & ForbiddenMoreKey, +): Unwrap< Extract< GenericInput, Kind< @@ -86,7 +103,7 @@ export function unwrapSelectionOrThrow( if (args.length === 1) { const [selector] = args; - return (input: unknown) => unwrapSelectionOrThrow(input, selector); + return (input: unknown) => unwrapSelectionOrThrow(input, selector as never); } const [input, selector] = args; diff --git a/scripts/either/whenHasInformation.ts b/scripts/either/whenHasInformation.ts index 9d928ce13..cbcdfb136 100644 --- a/scripts/either/whenHasInformation.ts +++ b/scripts/either/whenHasInformation.ts @@ -1,4 +1,4 @@ -import { type Kind, type WrappedValue, type AnyFunction, type AnyValue, type Unwrap, unwrap, type BreakGenericLink } from "@scripts/common"; +import { type Kind, type WrappedValue, type AnyFunction, type Unwrap, unwrap, type BreakGenericLink } from "@scripts/common"; import { isRight, type Right } from "./right"; import { isLeft, type Left } from "./left"; import { informationKind } from "./kind"; @@ -15,7 +15,7 @@ export function whenHasInformation< ? ReturnType> : never ), - const GenericOutput extends AnyValue, + const GenericOutput extends unknown, >( information: GenericInformation | GenericInformation[], theFunction: ( @@ -40,7 +40,7 @@ export function whenHasInformation< ? ReturnType> : never ), - const GenericOutput extends AnyValue, + const GenericOutput extends unknown, >( input: GenericInput, information: GenericInformation | GenericInformation[], diff --git a/scripts/either/whenIsSelected.ts b/scripts/either/whenIsSelected.ts new file mode 100644 index 000000000..f74650bb0 --- /dev/null +++ b/scripts/either/whenIsSelected.ts @@ -0,0 +1,142 @@ +import { type AnyFunction, type Kind, type Unwrap, type GetKindValue, unwrap } from "@scripts/common"; +import { type Left } from "./left"; +import { type Right } from "./right"; +import { informationKind } from "./kind"; +import { type ForbiddenKey, type GetPropsWithValue } from "@scripts/object"; + +type Either = Right | Left; + +type ForbiddenMoreKey< + GenericInput extends unknown, + GenericSelector extends Record, +> = ForbiddenKey< + GenericSelector, + Extract< + Exclude< + keyof GenericSelector, + GetKindValue< + typeof informationKind, + Extract + > + >, + string + > +>; + +/** + * {@include either/whenIsSelected/index.md} + */ +export function whenIsSelected< + GenericInput extends unknown, + const GenericSelector extends Record< + GetKindValue< + typeof informationKind, + Extract< + GenericInput, + Either + > + >, + boolean + >, + const GenericOutput extends unknown, +>( + selector: GenericSelector & ForbiddenMoreKey, + theFunction: ( + value: Unwrap< + Extract< + GenericInput, + Kind< + typeof informationKind.definition, + Extract< + | GetPropsWithValue + | GetPropsWithValue, + string + > + > + > + > + ) => GenericOutput, +): (input: GenericInput) => ( + | GenericOutput + | Exclude< + GenericInput, + Kind< + typeof informationKind.definition, + Extract< + GetPropsWithValue, + string + > + > + > +); + +export function whenIsSelected< + GenericInput extends unknown, + const GenericSelector extends Record< + GetKindValue< + typeof informationKind, + Extract< + GenericInput, + Either + > + >, + boolean + >, + const GenericOutput extends unknown, +>( + input: GenericInput, + selector: GenericSelector & ForbiddenMoreKey, + theFunction: ( + value: Unwrap< + Extract< + GenericInput, + Kind< + typeof informationKind.definition, + Extract< + | GetPropsWithValue + | GetPropsWithValue, + string + > + > + > + > + ) => GenericOutput, +): ( + | GenericOutput + | Exclude< + GenericInput, + Kind< + typeof informationKind.definition, + Extract< + GetPropsWithValue, + string + > + > + > +); + +export function whenIsSelected( + ...args: [unknown, Record, AnyFunction] + | [Record, AnyFunction] +): any { + if (args.length === 2) { + const [selector, theFunction] = args; + + return (input: unknown) => whenIsSelected( + input, + selector as never, + theFunction, + ); + } + + const [input, selector, theFunction] = args; + + if ( + informationKind.has(input) + && selector[informationKind.getValue(input)] === true + ) { + return theFunction(unwrap(input)); + } + + return input; +} diff --git a/scripts/pattern/index.ts b/scripts/pattern/index.ts index a6d74e79b..2027248a4 100644 --- a/scripts/pattern/index.ts +++ b/scripts/pattern/index.ts @@ -10,3 +10,7 @@ export * from "./types"; export * from "./union"; export * from "./when"; export * from "./whenNot"; +export * from "./matchWithString"; +export * from "./matchWithNumber"; +export * from "./matchWithStringOtherwise"; +export * from "./matchWithNumberOtherwise"; diff --git a/scripts/pattern/matchWithNumber.ts b/scripts/pattern/matchWithNumber.ts new file mode 100644 index 000000000..86c66dda4 --- /dev/null +++ b/scripts/pattern/matchWithNumber.ts @@ -0,0 +1,61 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type OnlyLiteralNumber } from "@scripts/common"; + +type ComputeMatcher< + GenericInput extends number, +> = { + [Prop in GenericInput]: (value: Prop) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends number, + GenericMatcher extends ComputeMatcher, +> = Exclude extends infer InferredKey + ? IsEqual extends true + ? unknown + : ComputedTypeError< + `Key "${Extract}" is forbidden.` + > + : never; + +/** + * {@include pattern/matchWithNumber/index.md} + */ +export function matchWithNumber< + GenericInput extends number, + GenericMatcher extends ComputeMatcher, +>( + matcher: GenericMatcher + & ComputeMatcher> + & ForbiddenMoreKey, GenericMatcher> +): (input: GenericInput & OnlyLiteralNumber) => ReturnType< + NoInfer[keyof GenericMatcher] +>; + +export function matchWithNumber< + GenericInput extends number, + GenericMatcher extends ComputeMatcher, +>( + input: GenericInput & OnlyLiteralNumber, + matcher: FixDeepFunctionInfer< + ComputeMatcher, + GenericMatcher + > + & ForbiddenMoreKey +): ReturnType< + GenericMatcher[keyof GenericMatcher] +>; + +export function matchWithNumber( + ...args: [number, Record] + | [Record] +): unknown { + if (args.length === 1) { + const [matcher] = args; + + return (input: number) => matcher[input]!(input); + } + + const [input, matcher] = args; + + return matcher[input]!(input); +} diff --git a/scripts/pattern/matchWithNumberOtherwise.ts b/scripts/pattern/matchWithNumberOtherwise.ts new file mode 100644 index 000000000..adcc7addd --- /dev/null +++ b/scripts/pattern/matchWithNumberOtherwise.ts @@ -0,0 +1,71 @@ +import { type AnyFunction, type ComputedTypeError, type FixDeepFunctionInfer, type IsEqual, type OnlyLiteralNumber } from "@scripts/common"; +import { type GetPropsWithValueExtends } from "@scripts/object"; + +type ComputeMatcher = { + [Prop in GenericInput]?: (value: Prop) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends number, + GenericMatcher extends ComputeMatcher, +> = Exclude extends infer InferredKey + ? IsEqual extends true + ? unknown + : ComputedTypeError<`Key "${Extract}" is forbidden.`> + : never; + +type HandledKeys = Extract< + GetPropsWithValueExtends, + number +>; + +function execute( + input: number, + matcher: Record, + otherwise: AnyFunction, +) { + const callback = matcher[input]; + return callback === undefined ? otherwise(input) : callback(input); +} + +/** {@include pattern/matchWithNumberOtherwise/index.md} */ +export function matchWithNumberOtherwise< + GenericInput extends number, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + matcher: GenericMatcher + & ComputeMatcher> + & ForbiddenMoreKey, GenericMatcher>, + otherwise: (value: Exclude>) => GenericOutput +): (input: GenericInput & OnlyLiteralNumber) => ( + | ReturnType[keyof GenericMatcher], AnyFunction>> + | GenericOutput +); + +export function matchWithNumberOtherwise< + GenericInput extends number, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + input: GenericInput & OnlyLiteralNumber, + matcher: FixDeepFunctionInfer, GenericMatcher> + & ForbiddenMoreKey, + otherwise: (value: Exclude>) => GenericOutput +): ( + | ReturnType> + | GenericOutput +); + +export function matchWithNumberOtherwise( + ...args: [number, Record, AnyFunction] + | [Record, AnyFunction] +): unknown { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input: number) => execute(input, matcher, otherwise); + } + + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} diff --git a/scripts/pattern/matchWithString.ts b/scripts/pattern/matchWithString.ts new file mode 100644 index 000000000..4caca6c13 --- /dev/null +++ b/scripts/pattern/matchWithString.ts @@ -0,0 +1,59 @@ +import { type AnyFunction, type FixDeepFunctionInfer, type OnlyLiteralString } from "@scripts/common"; +import { type ForbiddenKey } from "@scripts/object"; + +type ComputeMatcher< + GenericInput extends string, +> = { + [Prop in GenericInput]: (value: Prop) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends string, + GenericMatcher extends ComputeMatcher, +> = ForbiddenKey< + GenericMatcher, + Extract, string> +>; + +/** + * {@include pattern/matchWithString/index.md} + */ +export function matchWithString< + GenericInput extends string, + GenericMatcher extends ComputeMatcher, +>( + matcher: GenericMatcher + & ComputeMatcher> + & ForbiddenMoreKey, GenericMatcher> +): (input: GenericInput & OnlyLiteralString) => ReturnType< + NoInfer[keyof GenericMatcher] +>; + +export function matchWithString< + GenericInput extends string, + GenericMatcher extends ComputeMatcher, +>( + input: GenericInput & OnlyLiteralString, + matcher: FixDeepFunctionInfer< + ComputeMatcher, + GenericMatcher + > + & ForbiddenMoreKey +): ReturnType< + GenericMatcher[keyof GenericMatcher] +>; + +export function matchWithString( + ...args: [string, Record] + | [Record] +): unknown { + if (args.length === 1) { + const [matcher] = args; + + return (input: string) => matcher[input]!(input); + } + + const [input, matcher] = args; + + return matcher[input]!(input); +} diff --git a/scripts/pattern/matchWithStringOtherwise.ts b/scripts/pattern/matchWithStringOtherwise.ts new file mode 100644 index 000000000..6861531c6 --- /dev/null +++ b/scripts/pattern/matchWithStringOtherwise.ts @@ -0,0 +1,70 @@ +import { type AnyFunction, type FixDeepFunctionInfer, type OnlyLiteralString } from "@scripts/common"; +import { type ForbiddenKey, type GetPropsWithValueExtends } from "@scripts/object"; + +type ComputeMatcher = { + [Prop in GenericInput]?: (value: Prop) => unknown +}; + +type ForbiddenMoreKey< + GenericInput extends string, + GenericMatcher extends ComputeMatcher, +> = ForbiddenKey< + GenericMatcher, + Extract, string> +>; + +type HandledKeys = Extract< + GetPropsWithValueExtends, + string +>; + +function execute( + input: string, + matcher: Record, + otherwise: AnyFunction, +) { + const callback = matcher[input]; + return callback === undefined ? otherwise(input) : callback(input); +} + +/** {@include pattern/matchWithStringOtherwise/index.md} */ +export function matchWithStringOtherwise< + GenericInput extends string, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + matcher: GenericMatcher + & ComputeMatcher> + & ForbiddenMoreKey, GenericMatcher>, + otherwise: (value: Exclude>) => GenericOutput +): (input: GenericInput & OnlyLiteralString) => ( + | ReturnType[keyof GenericMatcher], AnyFunction>> + | GenericOutput +); + +export function matchWithStringOtherwise< + GenericInput extends string, + GenericMatcher extends ComputeMatcher, + GenericOutput, +>( + input: GenericInput & OnlyLiteralString, + matcher: FixDeepFunctionInfer, GenericMatcher> + & ForbiddenMoreKey, + otherwise: (value: Exclude>) => GenericOutput +): ( + | ReturnType> + | GenericOutput +); + +export function matchWithStringOtherwise( + ...args: [string, Record, AnyFunction] + | [Record, AnyFunction] +): unknown { + if (args.length === 2) { + const [matcher, otherwise] = args; + return (input: string) => execute(input, matcher, otherwise); + } + + const [input, matcher, otherwise] = args; + return execute(input, matcher, otherwise); +} diff --git a/tests/clean/primitive/matcher/matchWithNumber.test.ts b/tests/clean/primitive/matcher/matchWithNumber.test.ts new file mode 100644 index 000000000..c05aae85a --- /dev/null +++ b/tests/clean/primitive/matcher/matchWithNumber.test.ts @@ -0,0 +1,166 @@ +import { DClean, DDataParser, forward, pipe, type ExpectType } from "@scripts"; + +describe("matchWithNumber", () => { + it("should discriminate a union of number primitives", () => { + const input = forward< + | DClean.Primitive<1> + | DClean.Primitive<2> + >(DClean.Number.createOrThrow(2)); + const result = DClean.matchWithNumber(input, { + 1: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<1>, + "strict" + >; + + return 42 as const; + }, + 2: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<2>, + "strict" + >; + + expect(value).toBe(input); + return "failed" as const; + }, + }); + + expect(result).toBe("failed"); + + type check = ExpectType; + }); + + it("should discriminate literal values contained in one primitive in pipe", () => { + const input = forward< + DClean.Primitive<1 | 2> + >( + DClean.Number.createOrThrow(1), + ); + const result = pipe( + input, + DClean.matchWithNumber({ + 1: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<1 | 2> & DClean.Primitive<1>, + "strict" + >; + + expect(value).toBe(input); + return 42 as const; + }, + 2: () => "failed" as const, + }), + ); + + expect(result).toBe(42); + + type check = ExpectType; + }); + + it("should reject a non-literal number primitive in classic and curried forms", () => { + const input = DClean.Number.createOrThrow(1 as number); + const matcher: Record< + number, + (value: DClean.Primitive) => string + > = { + 1: (value) => { + expect(value).toBe(input); + return "matched"; + }, + }; + + // @ts-expect-error input primitive must contain number literal values + DClean.matchWithNumber(input, matcher); + + pipe( + // @ts-expect-error curried matcher only accepts number literal primitives + input, + DClean.matchWithNumber(matcher), + ); + + expect(true).toBe(true); + }); + + it("should preserve constrained number types in each branch", () => { + const input = forward< + | DClean.ConstrainedType<"positive", 1> + | DClean.ConstrainedType<"positive", 2> + >(DClean.Positive.createOrThrow(2)); + const result = DClean.matchWithNumber(input, { + 1: (value) => { + type check = ExpectType< + typeof value, + DClean.ConstrainedType<"positive", 1> + & DClean.Primitive<1>, + "strict" + >; + + return 42 as const; + }, + 2: (value) => { + type check = ExpectType< + typeof value, + DClean.ConstrainedType<"positive", 2> + & DClean.Primitive<2>, + "strict" + >; + + expect(value).toBe(input); + return "failed" as const; + }, + }); + + expect(result).toBe("failed"); + }); + + it("should preserve a number new type when discriminating in pipe", () => { + const StatusCode = DClean.createNewType( + "StatusCode", + DDataParser.number(), + ); + const input = forward< + DClean.NewType<"StatusCode", 1 | 2, never> + >(StatusCode.createOrThrow(1)); + const result = pipe( + input, + DClean.matchWithNumber({ + 1: (value) => { + type check = ExpectType< + typeof value, + DClean.NewType<"StatusCode", 1 | 2, never> + & DClean.Primitive<1>, + "strict" + >; + + expect(value).toBe(input); + return 42 as const; + }, + 2: () => "failed" as const, + }), + ); + + expect(result).toBe(42); + }); + + it("should reject missing and additional literal matcher keys", () => { + const input = DClean.Number.createOrThrow(1 as 1 | 2); + + // @ts-expect-error matcher must handle every wrapped literal + DClean.matchWithNumber(input, { + 1: () => 42, + }); + + // @ts-expect-error matcher cannot contain keys outside the wrapped union + DClean.matchWithNumber(input, { + 1: () => 42, + 2: () => "failed", + 3: () => false, + }); + + expect(true).toBe(true); + }); +}); diff --git a/tests/clean/primitive/matcher/matchWithNumberOtherwise.test.ts b/tests/clean/primitive/matcher/matchWithNumberOtherwise.test.ts new file mode 100644 index 000000000..640b1a762 --- /dev/null +++ b/tests/clean/primitive/matcher/matchWithNumberOtherwise.test.ts @@ -0,0 +1,41 @@ +import { DClean, forward, pipe, type ExpectType } from "@scripts"; + +describe("matchWithNumberOtherwise", () => { + it("should match a handled wrapped number and narrow both callbacks", () => { + const input = forward>(DClean.Number.createOrThrow(1)); + const result = DClean.matchWithNumberOtherwise(input, { + 1: (value) => { + type check = ExpectType & DClean.Primitive<1>, "strict">; + return "one" as const; + }, + }, (value) => { + type check = ExpectType & DClean.Primitive<2>, "strict">; + return "fallback" as const; + }); + expect(result).toBe("one"); + type check = ExpectType; + }); + + it("should delegate the original wrapped number in pipe", () => { + const input = forward>(DClean.Number.createOrThrow(2)); + const result = pipe(input, DClean.matchWithNumberOtherwise({ 1: () => "one" as const }, (value) => { + expect(value).toBe(input); + return "fallback" as const; + })); + expect(result).toBe("fallback"); + }); + + it("should reject matcher keys outside the wrapped union", () => { + const input = DClean.Number.createOrThrow(1 as 1 | 2); + DClean.matchWithNumberOtherwise( + input, + // @ts-expect-error matcher cannot contain unknown wrapped cases + { + 1: () => "one", + 3: () => "three", + }, + () => "fallback", + ); + expect(true).toBe(true); + }); +}); diff --git a/tests/clean/primitive/matcher/matchWithString.test.ts b/tests/clean/primitive/matcher/matchWithString.test.ts new file mode 100644 index 000000000..fe7994105 --- /dev/null +++ b/tests/clean/primitive/matcher/matchWithString.test.ts @@ -0,0 +1,170 @@ +import { DClean, DDataParser, forward, pipe, type ExpectType } from "@scripts"; + +describe("matchWithString", () => { + it("should discriminate a union of string primitives", () => { + const input = forward< + | DClean.Primitive<"success"> + | DClean.Primitive<"failure"> + >(DClean.String.createOrThrow("failure")); + const result = DClean.matchWithString(input, { + success: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<"success">, + "strict" + >; + + return 42 as const; + }, + failure: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<"failure">, + "strict" + >; + + expect(value).toBe(input); + return "failed" as const; + }, + }); + + expect(result).toBe("failed"); + + type check = ExpectType; + }); + + it("should discriminate literal values contained in one primitive in pipe", () => { + const input = forward< + DClean.Primitive<"success" | "failure"> + >( + DClean.String.createOrThrow("success"), + ); + const result = pipe( + input, + DClean.matchWithString({ + success: (value) => { + type check = ExpectType< + typeof value, + DClean.Primitive<"success" | "failure"> + & DClean.Primitive<"success">, + "strict" + >; + + expect(value).toBe(input); + return 42 as const; + }, + failure: () => "failed" as const, + }), + ); + + expect(result).toBe(42); + + type check = ExpectType; + }); + + it("should reject a non-literal string primitive in classic and curried forms", () => { + const input = DClean.String.createOrThrow("success" as string); + const matcher: Record< + string, + (value: DClean.Primitive) => string + > = { + success: (value) => { + expect(value).toBe(input); + return "matched"; + }, + }; + + // @ts-expect-error input primitive must contain string literal values + DClean.matchWithString(input, matcher); + + pipe( + // @ts-expect-error curried matcher only accepts string literal primitives + input, + DClean.matchWithString(matcher), + ); + + expect(true).toBe(true); + }); + + it("should preserve constrained string types in each branch", () => { + const constraint = DClean.StringMin(1); + const input = forward< + | DClean.ConstrainedType<"string-min-10", "success"> + | DClean.ConstrainedType<"string-min-1", "failure"> + >(constraint.createOrThrow("failure")); + const result = DClean.matchWithString(input, { + success: (value) => { + type check = ExpectType< + typeof value, + DClean.ConstrainedType<"string-min-10", "success"> + & DClean.Primitive<"success">, + "strict" + >; + + return 42 as const; + }, + failure: (value) => { + type check = ExpectType< + typeof value, + DClean.ConstrainedType<"string-min-1", "failure"> + & DClean.Primitive<"failure">, + "strict" + >; + + expect(value).toBe(input); + return "failed" as const; + }, + }); + + expect(result).toBe("failed"); + }); + + it("should preserve a string new type when discriminating in pipe", () => { + const Status = DClean.createNewType( + "Status", + DDataParser.string(), + ); + const input = forward< + DClean.NewType<"Status", "success" | "failure", never> + >(Status.createOrThrow("success")); + const result = pipe( + input, + DClean.matchWithString({ + success: (value) => { + type check = ExpectType< + typeof value, + DClean.NewType<"Status", "success" | "failure", never> + & DClean.Primitive<"success">, + "strict" + >; + + expect(value).toBe(input); + return 42 as const; + }, + failure: () => "failed" as const, + }), + ); + + expect(result).toBe(42); + }); + + it("should reject missing and additional literal matcher keys", () => { + const input = DClean.String.createOrThrow( + "success" as "success" | "failure", + ); + + // @ts-expect-error matcher must handle every wrapped literal + DClean.matchWithString(input, { + success: () => 42, + }); + + // @ts-expect-error matcher cannot contain keys outside the wrapped union + DClean.matchWithString(input, { + success: () => 42, + failure: () => "failed", + unexpected: () => false, + }); + + expect(true).toBe(true); + }); +}); diff --git a/tests/clean/primitive/matcher/matchWithStringOtherwise.test.ts b/tests/clean/primitive/matcher/matchWithStringOtherwise.test.ts new file mode 100644 index 000000000..9391a4445 --- /dev/null +++ b/tests/clean/primitive/matcher/matchWithStringOtherwise.test.ts @@ -0,0 +1,41 @@ +import { DClean, forward, pipe, type ExpectType } from "@scripts"; + +describe("matchWithStringOtherwise", () => { + it("should match a handled wrapped string and narrow both callbacks", () => { + const input = forward>(DClean.String.createOrThrow("success")); + const result = DClean.matchWithStringOtherwise(input, { + success: (value) => { + type check = ExpectType & DClean.Primitive<"success">, "strict">; + return 42 as const; + }, + }, (value) => { + type check = ExpectType & DClean.Primitive<"failure">, "strict">; + return "fallback" as const; + }); + expect(result).toBe(42); + type check = ExpectType; + }); + + it("should delegate the original wrapped string in pipe", () => { + const input = forward>(DClean.String.createOrThrow("failure")); + const result = pipe(input, DClean.matchWithStringOtherwise({ success: () => 42 as const }, (value) => { + expect(value).toBe(input); + return "fallback" as const; + })); + expect(result).toBe("fallback"); + }); + + it("should reject matcher keys outside the wrapped union", () => { + const input = DClean.String.createOrThrow("success" as "success" | "failure"); + DClean.matchWithStringOtherwise( + input, + // @ts-expect-error matcher cannot contain unknown wrapped cases + { + success: () => 42, + unexpected: () => false, + }, + () => "fallback", + ); + expect(true).toBe(true); + }); +}); diff --git a/tests/common/prepareAsyncPipe.test.ts b/tests/common/prepareAsyncPipe.test.ts new file mode 100644 index 000000000..d1cead7ab --- /dev/null +++ b/tests/common/prepareAsyncPipe.test.ts @@ -0,0 +1,85 @@ +import { DString, type ExpectType, pipe, prepareAsyncPipe } from "@scripts"; + +describe("prepareAsyncPipe", () => { + it("should prepare reusable synchronous and asynchronous steps once", async() => { + let callCount = 0; + const preparedPipe = prepareAsyncPipe()( + (value) => { + callCount++; + return value + 1; + }, + async(value) => { + callCount++; + await Promise.resolve(); + return `value:${value}`; + }, + ); + + const firstResult = await preparedPipe(1); + const secondResult = await preparedPipe(Promise.resolve(4)); + + expect(firstResult).toBe("value:2"); + expect(secondResult).toBe("value:5"); + expect(callCount).toBe(4); + + type check = ExpectType< + typeof firstResult, + `value:${number}`, + "strict" + >; + }); + + it("should work as one asynchronous function in pipe", async() => { + const preparedPipe = prepareAsyncPipe()( + (value) => Promise.resolve(value * 2), + (value) => ({ value }), + ); + const resultPromise = pipe(3, preparedPipe); + const result = await resultPromise; + + expect(result).toStrictEqual({ value: 6 }); + type promiseCheck = ExpectType< + typeof resultPromise, + Promise<{ readonly value: number }>, + "strict" + >; + type resultCheck = ExpectType< + typeof result, + { readonly value: number }, + "strict" + >; + }); + + it("should support fifteen prepared maybe-promise steps", async() => { + const preparedPipe = prepareAsyncPipe()( + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + (value) => value + 1, + (value) => Promise.resolve(value + 1), + ); + const result = await preparedPipe(Promise.resolve(0)); + + expect(result).toBe(15); + type check = ExpectType; + }); + + it("should preserve generic input-to-output inference across asynchronous executions", async() => { + const preparedPipe: (input: number) => Promise = prepareAsyncPipe()( + DString.to, + ); + const stringResult = await preparedPipe(1); + + expect(stringResult).toBe("1"); + }); +}); diff --git a/tests/common/preparePipe.test.ts b/tests/common/preparePipe.test.ts new file mode 100644 index 000000000..83fc97dc2 --- /dev/null +++ b/tests/common/preparePipe.test.ts @@ -0,0 +1,78 @@ +import { DString, type ExpectType, pipe, preparePipe, S } from "@scripts"; + +describe("preparePipe", () => { + it("should prepare reusable synchronous steps once", () => { + let callCount = 0; + const preparedPipe = preparePipe()( + (value) => { + callCount++; + return value + 1; + }, + (value) => { + callCount++; + return `value:${value}`; + }, + ); + + const firstResult = preparedPipe(1); + const secondResult = preparedPipe(4); + + expect(firstResult).toBe("value:2"); + expect(secondResult).toBe("value:5"); + expect(callCount).toBe(4); + + type check = ExpectType< + typeof firstResult, + `value:${number}`, + "strict" + >; + }); + + it("should work as one function in pipe", () => { + const preparedPipe = preparePipe()( + (value) => value * 2, + (value) => ({ value }), + ); + const result = pipe(3, preparedPipe); + + expect(result).toStrictEqual({ value: 6 }); + type check = ExpectType< + typeof result, + { readonly value: number }, + "strict" + >; + }); + + it("should support fifteen prepared steps", () => { + const preparedPipe = preparePipe()( + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + (value) => value + 1, + ); + const result = preparedPipe(0); + + expect(result).toBe(15); + type check = ExpectType; + }); + + it("should preserve generic input-to-output inference across executions", () => { + const preparedPipe: (input: number) => string = preparePipe()( + DString.to, + ); + const stringResult = preparedPipe(1); + + expect(stringResult).toBe("1"); + }); +}); diff --git a/tests/either/matchInformation.test.ts b/tests/either/matchInformation.test.ts index 85e9fdaf5..f5fc7aab6 100644 --- a/tests/either/matchInformation.test.ts +++ b/tests/either/matchInformation.test.ts @@ -68,7 +68,7 @@ describe("matchInformation", () => { >; }); - it("requires an exhaustive matcher for either information", () => { + it("rejects missing and additional matcher keys", () => { const either = true ? DEither.ok() : DEither.fail(); @@ -78,6 +78,33 @@ describe("matchInformation", () => { ok: () => 1, }); + // @ts-expect-error matcher cannot contain unknown information values + DEither.matchInformation(either, { + ok: () => 1, + fail: () => 2, + unexpected: () => 3, + }); + + pipe( + either, + // @ts-expect-error curried matcher must handle all information values + DEither.matchInformation({ + ok: () => 1, + }), + ); + + pipe( + either, + DEither.matchInformation( + // @ts-expect-error curried matcher cannot contain unknown information values + { + ok: () => 1, + fail: () => 2, + unexpected: () => 3, + }, + ), + ); + expect(true).toBe(true); }); }); diff --git a/tests/either/matchInformationOtherwise.test.ts b/tests/either/matchInformationOtherwise.test.ts index 5f0e8fb96..c15f38ed5 100644 --- a/tests/either/matchInformationOtherwise.test.ts +++ b/tests/either/matchInformationOtherwise.test.ts @@ -118,4 +118,36 @@ describe("matchInformationOtherwise", () => { "strict" >; }); + + it("rejects additional matcher keys", () => { + const input = true + ? DEither.ok() + : DEither.fail(); + + DEither.matchInformationOtherwise( + input, + // @ts-expect-error matcher cannot contain unknown information values + { + ok: () => "ok", + fail: undefined, + unexpected: () => "unexpected", + }, + () => "otherwise", + ); + + pipe( + input, + DEither.matchInformationOtherwise( + // @ts-expect-error curried matcher cannot contain unknown information values + { + ok: () => "ok", + fail: undefined, + unexpected: () => "unexpected", + }, + () => "otherwise", + ), + ); + + expect(true).toBe(true); + }); }); diff --git a/tests/either/unwrapSelection.test.ts b/tests/either/unwrapSelection.test.ts index fad6ede15..e3ede1f7d 100644 --- a/tests/either/unwrapSelection.test.ts +++ b/tests/either/unwrapSelection.test.ts @@ -116,7 +116,7 @@ describe("unwrapSelection", () => { >; }); - it("requires the selector to handle every input information", () => { + it("requires exactly one selector entry for every input information", () => { const input = true ? DEither.right("success", 42) : DEither.left("failure", "error"); @@ -125,6 +125,25 @@ describe("unwrapSelection", () => { DEither.unwrapSelection(input, { success: true, }); + + // @ts-expect-error selector cannot contain unknown information values + DEither.unwrapSelection(input, { + success: true, + failure: false, + unexpected: true, + }); + + pipe( + input, + DEither.unwrapSelection( + // @ts-expect-error curried selector cannot contain unknown information values + { + success: true, + failure: false, + unexpected: true, + }, + ), + ); }); it("works in a pipe chain with the curried signature", () => { diff --git a/tests/either/unwrapSelectionOrThrow.test.ts b/tests/either/unwrapSelectionOrThrow.test.ts index 896a247e3..11cc7a757 100644 --- a/tests/either/unwrapSelectionOrThrow.test.ts +++ b/tests/either/unwrapSelectionOrThrow.test.ts @@ -105,7 +105,7 @@ describe("unwrapSelectionOrThrow", () => { })).toThrow(DEither.HasNotSelectedInformationError); }); - it("requires the selector to handle every input information", () => { + it("requires exactly one selector entry for every input information", () => { const input = true ? DEither.right("success", 42) : DEither.left("failure", "error"); @@ -114,6 +114,25 @@ describe("unwrapSelectionOrThrow", () => { DEither.unwrapSelectionOrThrow(input, { success: true, }); + + // @ts-expect-error selector cannot contain unknown information values + DEither.unwrapSelectionOrThrow(input, { + success: true, + failure: false, + unexpected: true, + }); + + pipe( + input, + DEither.unwrapSelectionOrThrow( + // @ts-expect-error curried selector cannot contain unknown information values + { + success: true, + failure: false, + unexpected: true, + }, + ), + ); }); it("works in a pipe chain with the curried signature", () => { diff --git a/tests/either/whenHasInformation.test.ts b/tests/either/whenHasInformation.test.ts index bac8ecdf2..de019710b 100644 --- a/tests/either/whenHasInformation.test.ts +++ b/tests/either/whenHasInformation.test.ts @@ -20,7 +20,7 @@ describe("whenHasInformation", () => { type check = ExpectType< typeof result, - 10 | DEither.Left<"left", undefined>, + number | DEither.Left<"left", undefined>, "strict" >; }); diff --git a/tests/either/whenIsSelected.test.ts b/tests/either/whenIsSelected.test.ts new file mode 100644 index 000000000..78fbdc38e --- /dev/null +++ b/tests/either/whenIsSelected.test.ts @@ -0,0 +1,147 @@ +import { DEither, pipe, type ExpectType } from "@scripts"; + +describe("whenIsSelected", () => { + it("should call the callback with the unwrapped selected value", () => { + const input = true + ? DEither.right("success", 42 as const) + : DEither.left("failure", "error" as const); + const result = DEither.whenIsSelected( + input, + { + success: true, + failure: false, + }, + (value) => { + type check = ExpectType; + + return value + 1; + }, + ); + + expect(result).toBe(43); + + type check = ExpectType< + typeof result, + number | DEither.Left<"failure", "error">, + "strict" + >; + }); + + it("should preserve an either whose information is not selected", () => { + const input = true + ? DEither.left("failure", "error" as const) + : DEither.right("success", 42 as const); + const result = DEither.whenIsSelected( + input, + { + success: true, + failure: false, + }, + (value) => value + 1, + ); + + expect(result).toStrictEqual(input); + + type check = ExpectType< + typeof result, + number | DEither.Left<"failure", "error">, + "strict" + >; + }); + + it("should keep runtime boolean selections in both possible output branches", () => { + const shouldSelect = Boolean(1); + const input = true + ? DEither.right("success", 42 as const) + : DEither.left("failure", "error" as const); + const result = DEither.whenIsSelected( + input, + { + success: shouldSelect, + failure: false, + }, + (value) => { + type check = ExpectType; + + return "selected" as const; + }, + ); + + expect(result).toBe("selected"); + + type check = ExpectType< + typeof result, + | "selected" + | DEither.Right<"success", 42> + | DEither.Left<"failure", "error">, + "strict" + >; + }); + + it("should return non-either inputs unchanged", () => { + const result = DEither.whenIsSelected( + "value" as const, + {}, + (value) => value, + ); + + expect(result).toBe("value"); + + type check = ExpectType; + }); + + it("should work in pipe with the curried signature", () => { + const result = pipe( + true + ? DEither.right("success", 42 as const) + : DEither.left("failure", "error" as const), + DEither.whenIsSelected( + { + success: true, + failure: false, + }, + (value) => { + type check = ExpectType; + + return value + 1; + }, + ), + ); + + expect(result).toBe(43); + + type check = ExpectType< + typeof result, + number | DEither.Left<"failure", "error">, + "strict" + >; + }); + + it("should require exactly one selector entry for every information value", () => { + const input = true + ? DEither.right("success", 42) + : DEither.left("failure", "error"); + + DEither.whenIsSelected( + input, + // @ts-expect-error selector must handle every input information value + { + success: true, + }, + (value) => value, + ); + + DEither.whenIsSelected( + input, + // @ts-expect-error selector cannot contain unknown information values + { + success: true, + failure: false, + unexpected: true, + }, + (value) => value, + ); + + expect(true).toBe(true); + }); +}); diff --git a/tests/pattern/matchWithNumber.test.ts b/tests/pattern/matchWithNumber.test.ts new file mode 100644 index 000000000..883918850 --- /dev/null +++ b/tests/pattern/matchWithNumber.test.ts @@ -0,0 +1,126 @@ +import { DPattern, pipe, type ExpectType } from "@scripts"; + +describe("matchWithNumber", () => { + it("should call the matching handler with the narrowed number in classic form", () => { + const input = 2 as 1 | 2; + const result = DPattern.matchWithNumber(input, { + 1: (value) => { + type check = ExpectType< + typeof value, + 1, + "strict" + >; + + return 42 as const; + }, + 2: (value) => { + type check = ExpectType< + typeof value, + 2, + "strict" + >; + + return "failed" as const; + }, + }); + + expect(result).toBe("failed"); + + type check = ExpectType< + typeof result, + 42 | "failed", + "strict" + >; + }); + + it("should work in pipe with the curried form", () => { + const input = 1 as 1 | 2; + const result = pipe( + input, + DPattern.matchWithNumber({ + 1: (value) => { + type check = ExpectType< + typeof value, + 1, + "strict" + >; + + return 42 as const; + }, + 2: (value) => { + type check = ExpectType< + typeof value, + 2, + "strict" + >; + + return "failed" as const; + }, + }), + ); + + expect(result).toBe(42); + + type check = ExpectType< + typeof result, + 42 | "failed", + "strict" + >; + }); + + it("should reject non-literal numbers in classic and curried forms", () => { + const input = 1 as number; + + // @ts-expect-error input must be a number literal union + DPattern.matchWithNumber(input, { + 1: () => 42, + }); + + pipe( + // @ts-expect-error curried matcher only accepts its number literal keys + input, + DPattern.matchWithNumber({ + 1: () => 42, + }), + ); + + expect(true).toBe(true); + }); + + it("should reject matchers with missing or additional keys", () => { + const input = 1 as 1 | 2; + + // @ts-expect-error matcher must handle every input literal + DPattern.matchWithNumber(input, { + 1: () => 42, + }); + + // @ts-expect-error matcher cannot declare keys outside the input union + DPattern.matchWithNumber(input, { + 1: () => 42, + 2: () => "failed", + 3: () => false, + }); + + pipe( + input, + // @ts-expect-error curried matcher must handle every piped input literal + DPattern.matchWithNumber({ + 1: () => 42, + }), + ); + pipe( + input, + DPattern.matchWithNumber( + // @ts-expect-error curried matcher cannot declare keys outside its input union + { + 1: () => 42, + 2: () => "failed", + 3: () => false, + }, + ), + ); + + expect(true).toBe(true); + }); +}); diff --git a/tests/pattern/matchWithNumberOtherwise.test.ts b/tests/pattern/matchWithNumberOtherwise.test.ts new file mode 100644 index 000000000..a525e9ce6 --- /dev/null +++ b/tests/pattern/matchWithNumberOtherwise.test.ts @@ -0,0 +1,44 @@ +import { DPattern, pipe, type ExpectType } from "@scripts"; + +describe("matchWithNumberOtherwise", () => { + it("should match a handled number and narrow both callbacks", () => { + const input = 1 as 1 | 2; + const result = DPattern.matchWithNumberOtherwise(input, { + 1: (value) => { + type check = ExpectType; + return "one" as const; + }, + }, (value) => { + type check = ExpectType; + return value; + }); + + expect(result).toBe("one"); + type check = ExpectType; + }); + + it("should delegate an unhandled number in pipe", () => { + const result = pipe( + 2 as 1 | 2, + DPattern.matchWithNumberOtherwise({ 1: () => "one" as const }, (value) => { + type check = ExpectType; + return value; + }), + ); + expect(result).toBe(2); + type check = ExpectType; + }); + + it("should reject matcher keys outside the input union", () => { + DPattern.matchWithNumberOtherwise( + 1 as 1 | 2, + // @ts-expect-error matcher cannot contain unknown number cases + { + 1: () => "one", + 3: () => "three", + }, + () => "fallback", + ); + expect(true).toBe(true); + }); +}); diff --git a/tests/pattern/matchWithString.test.ts b/tests/pattern/matchWithString.test.ts new file mode 100644 index 000000000..4eee6fd9a --- /dev/null +++ b/tests/pattern/matchWithString.test.ts @@ -0,0 +1,126 @@ +import { DPattern, pipe, type ExpectType } from "@scripts"; + +describe("matchWithString", () => { + it("should call the matching handler with the narrowed string in classic form", () => { + const input = "failure" as "success" | "failure"; + const result = DPattern.matchWithString(input, { + success: (value) => { + type check = ExpectType< + typeof value, + "success", + "strict" + >; + + return 42 as const; + }, + failure: (value) => { + type check = ExpectType< + typeof value, + "failure", + "strict" + >; + + return "failed" as const; + }, + }); + + expect(result).toBe("failed"); + + type check = ExpectType< + typeof result, + 42 | "failed", + "strict" + >; + }); + + it("should work in pipe with the curried form", () => { + const input = "success" as "success" | "failure"; + const result = pipe( + input, + DPattern.matchWithString({ + success: (value) => { + type check = ExpectType< + typeof value, + "success", + "strict" + >; + + return 42 as const; + }, + failure: (value) => { + type check = ExpectType< + typeof value, + "failure", + "strict" + >; + + return "failed" as const; + }, + }), + ); + + expect(result).toBe(42); + + type check = ExpectType< + typeof result, + 42 | "failed", + "strict" + >; + }); + + it("should reject non-literal strings in classic and curried forms", () => { + const input = "success" as string; + + // @ts-expect-error input must be a string literal union + DPattern.matchWithString(input, { + success: () => 42, + }); + + pipe( + // @ts-expect-error curried matcher only accepts its string literal keys + input, + DPattern.matchWithString({ + success: () => 42, + }), + ); + + expect(true).toBe(true); + }); + + it("should reject matchers with missing or additional keys", () => { + const input = "success" as "success" | "failure"; + + // @ts-expect-error matcher must handle every input literal + DPattern.matchWithString(input, { + success: () => 42, + }); + + // @ts-expect-error matcher cannot declare keys outside the input union + DPattern.matchWithString(input, { + success: () => 42, + failure: () => "failed", + unexpected: () => false, + }); + + pipe( + input, + // @ts-expect-error curried matcher must handle every piped input literal + DPattern.matchWithString({ + success: () => 42, + }), + ); + pipe( + input, + DPattern.matchWithString( + // @ts-expect-error curried matcher cannot declare keys outside its input union + { + success: () => 42, + failure: () => "failed", + unexpected: () => false, + }, + ), + ); + + expect(true).toBe(true); + }); +}); diff --git a/tests/pattern/matchWithStringOtherwise.test.ts b/tests/pattern/matchWithStringOtherwise.test.ts new file mode 100644 index 000000000..c6d3eadeb --- /dev/null +++ b/tests/pattern/matchWithStringOtherwise.test.ts @@ -0,0 +1,44 @@ +import { DPattern, pipe, type ExpectType } from "@scripts"; + +describe("matchWithStringOtherwise", () => { + it("should match a handled string and narrow both callbacks", () => { + const input = "success" as "success" | "failure"; + const result = DPattern.matchWithStringOtherwise(input, { + success: (value) => { + type check = ExpectType; + return 42 as const; + }, + }, (value) => { + type check = ExpectType; + return "fallback" as const; + }); + + expect(result).toBe(42); + type check = ExpectType; + }); + + it("should delegate an unhandled string in pipe", () => { + const result = pipe( + "failure" as "success" | "failure", + DPattern.matchWithStringOtherwise({ success: () => 42 as const }, (value) => { + type check = ExpectType; + return value; + }), + ); + expect(result).toBe("failure"); + type check = ExpectType; + }); + + it("should reject matcher keys outside the input union", () => { + DPattern.matchWithStringOtherwise( + "success" as "success" | "failure", + // @ts-expect-error matcher cannot contain unknown string cases + { + success: () => 42, + unexpected: () => false, + }, + () => "fallback", + ); + expect(true).toBe(true); + }); +});