Skip to content

Commit

Permalink
feat: Introduce proper typing for pipelineBreak
Browse files Browse the repository at this point in the history
  • Loading branch information
Iku-turso committed Oct 19, 2023
1 parent 52f2916 commit 6a78b40
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 116 deletions.
111 changes: 1 addition & 110 deletions packages/fp/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,112 +1,5 @@
import { Get } from 'type-fest';

interface Pipeline {
<A, R1, R2, R3, R4, R5, R6, R7, R8, R9>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
f4: (arg: Awaited<R3>) => R4,
f5: (arg: Awaited<R4>) => R5,
f6: (arg: Awaited<R5>) => R6,
f7: (arg: Awaited<R6>) => R7,
f8: (arg: Awaited<R7>) => R8,
f9: (arg: Awaited<R8>) => R9,
): ContainsPromise<[A, R1, R2, R3, R4, R5, R6, R7, R8, R9]> extends true
? Promise<Awaited<R9>>
: R9;

<A, R1, R2, R3, R4, R5, R6, R7, R8>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
f4: (arg: Awaited<R3>) => R4,
f5: (arg: Awaited<R4>) => R5,
f6: (arg: Awaited<R5>) => R6,
f7: (arg: Awaited<R6>) => R7,
f8: (arg: Awaited<R7>) => R8,
): ContainsPromise<[A, R1, R2, R3, R4, R5, R6, R7, R8]> extends true
? Promise<Awaited<R8>>
: R8;

<A, R1, R2, R3, R4, R5, R6, R7>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
f4: (arg: Awaited<R3>) => R4,
f5: (arg: Awaited<R4>) => R5,
f6: (arg: Awaited<R5>) => R6,
f7: (arg: Awaited<R6>) => R7,
): ContainsPromise<[A, R1, R2, R3, R4, R5, R6, R7]> extends true
? Promise<Awaited<R7>>
: R7;

<A, R1, R2, R3, R4, R5, R6>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
f4: (arg: Awaited<R3>) => R4,
f5: (arg: Awaited<R4>) => R5,
f6: (arg: Awaited<R5>) => R6,
): ContainsPromise<[A, R1, R2, R3, R4, R5, R6]> extends true
? Promise<Awaited<R6>>
: R6;

<A, R1, R2, R3, R4, R5>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
f4: (arg: Awaited<R3>) => R4,
f5: (arg: Awaited<R4>) => R5,
): ContainsPromise<[A, R1, R2, R3, R4, R5]> extends true
? Promise<Awaited<R5>>
: R5;

<A, R1, R2, R3, R4>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
f4: (arg: Awaited<R3>) => R4,
): ContainsPromise<[A, R1, R2, R3, R4]> extends true
? Promise<Awaited<R4>>
: R4;

<A, R1, R2, R3>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
f3: (arg: Awaited<R2>) => R3,
): ContainsPromise<[A, R1, R2, R3]> extends true ? Promise<Awaited<R3>> : R3;

<A, R1, R2>(
arg: A,
f1: (arg: Awaited<A>) => R1,
f2: (arg: Awaited<R1>) => R2,
): ContainsPromise<[A, R1, R2]> extends true ? Promise<Awaited<R2>> : R2;

<A, R1>(arg: A, f1: (arg: Awaited<A>) => R1): ContainsPromise<
[A, R1]
> extends true
? Promise<Awaited<R1>>
: R1;
}

type ContainsPromise<T extends [...any[]]> = T extends [
infer Head,
...infer Tail,
]
? Head extends Promise<any>
? true
: ContainsPromise<Tail>
: false;

export const pipeline: Pipeline;

interface GetFrom {
<TDictionary, TPropertyPath extends string>(
dictionary: TDictionary,
Expand All @@ -120,7 +13,5 @@ interface GetFrom {

export const getFrom: GetFrom;
export const getSafeFrom: GetFrom;

export const pipelineBreak: Symbol;

export { firstMatchValue } from './src/firstMatchValue/firstMatchValue';
export { pipeline, pipelineBreak } from './src/pipeline/pipeline';
2 changes: 1 addition & 1 deletion packages/fp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "A collection of low abstraction functions in functional programming flavor",
"files": [
"build",
"index.d.ts"
"**/*.d.ts"
],
"types": "./index.d.ts",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/fp/src/mapValuesDeep/mapValuesDeep.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
toPairs,
} from 'lodash/fp';
import isPrimitive from '../isPrimitive/isPrimitive';
import pipeline from '../pipeline/pipeline';
import { pipeline } from '../pipeline/pipeline';
import isPromise from '../isPromise/isPromise';
import awaitAll from '../awaitAll/awaitAll';

Expand Down
105 changes: 105 additions & 0 deletions packages/fp/src/pipeline/pipeline.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
interface Pipeline {
<A, R1, R2, R3, R4, R5, R6, R7, R8, R9>(
arg: A,
...functions: PipelineFunctions<
MovingWindow<[A, R1, R2, R3, R4, R5, R6, R7, R8, R9]>
>
): PipelineResult<A, [R1, R2, R3, R4, R5, R6, R7, R8], R9>;

<A, R1, R2, R3, R4, R5, R6, R7, R8>(
arg: A,
...functions: PipelineFunctions<
MovingWindow<[A, R1, R2, R3, R4, R5, R6, R7, R8]>
>
): PipelineResult<A, [R1, R2, R3, R4, R5, R6, R7], R8>;

<A, R1, R2, R3, R4, R5, R6, R7>(
arg: A,
...functions: PipelineFunctions<
MovingWindow<[A, R1, R2, R3, R4, R5, R6, R7]>
>
): PipelineResult<A, [R1, R2, R3, R4, R5, R6], R7>;

<A, R1, R2, R3, R4, R5, R6>(
arg: A,
...functions: PipelineFunctions<MovingWindow<[A, R1, R2, R3, R4, R5, R6]>>
): PipelineResult<A, [R1, R2, R3, R4, R5], R6>;

<A, R1, R2, R3, R4, R5>(
arg: A,
...functions: PipelineFunctions<MovingWindow<[A, R1, R2, R3, R4, R5]>>
): PipelineResult<A, [R1, R2, R3, R4], R5>;

<A, R1, R2, R3, R4>(
arg: A,
...functions: PipelineFunctions<MovingWindow<[A, R1, R2, R3, R4]>>
): PipelineResult<A, [R1, R2, R3], R4>;

<A, R1, R2, R3>(
arg: A,
...functions: PipelineFunctions<MovingWindow<[A, R1, R2, R3]>>
): PipelineResult<A, [R1, R2], R3>;

<A, R1, R2>(
arg: A,
...functions: PipelineFunctions<MovingWindow<[A, R1, R2]>>
): PipelineResult<A, [R1], R2>;

<A, R1>(
arg: A,
...functions: PipelineFunctions<MovingWindow<[A, R1]>>
): PipelineResult<A, [], R1>;
}

type MovingWindow<Types extends [...any[]]> = Types extends [
infer Left,
infer Right,
...infer Tail,
]
? [[Left, Right], ...MovingWindow<[Right, ...Tail]>]
: [];

type PipelineFunctions<InputsAndOutputs extends [...any[]]> =
InputsAndOutputs extends [infer Head, ...infer Tail]
? Head extends [infer Input, infer Output]
? [(arg: Argument<Input>) => Output, ...PipelineFunctions<Tail>]
: []
: [];

type PipelineResult<
TInput,
TReturnValues extends [...any[]],
TResult,
> = ContainsPromise<[TInput, ...TReturnValues, TResult]> extends true
? Promise<WithCollectivePipelineBreak<TReturnValues, Awaited<TResult>>>
: WithCollectivePipelineBreak<TReturnValues, TResult>;

export const pipelineBreak: unique symbol;
type Argument<T> = Exclude<Awaited<T>, typeof pipelineBreak>;

type WithCollectivePipelineBreak<
TReturnValues extends [...any[]],
TResult,
> = ContainsPipelineBreak<[...TReturnValues, TResult]> extends true
? TResult | typeof pipelineBreak
: TResult;

type ContainsPromise<T extends [...any[]]> = T extends [
infer Head,
...infer Tail,
]
? Head extends Promise<any>
? true
: ContainsPromise<Tail>
: false;

type ContainsPipelineBreak<T extends [...any[]]> = T extends [
infer Head,
...infer Tail,
]
? typeof pipelineBreak extends Head
? true
: ContainsPipelineBreak<Tail>
: false;

export const pipeline: Pipeline;
7 changes: 5 additions & 2 deletions packages/fp/src/pipeline/pipeline.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import flow from '../flow/flow';
import flow, { pipelineBreak } from '../flow/flow';

export default (firstArgument, ...args) => flow(...args)(firstArgument);
export { pipelineBreak };

export const pipeline = (firstArgument, ...args) =>
flow(...args)(firstArgument);
74 changes: 74 additions & 0 deletions packages/fp/src/pipeline/pipeline.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { expectType, expectNotAssignable } from 'tsd';
import { pipeline, pipelineBreak } from './pipeline';

const pipelineResultWithChanceOfBreak = pipeline(
'some-string',
someParameter => {
expectType<string>(someParameter);

return Math.random() > 0.5 ? pipelineBreak : String('some-other-string');
},
someOtherParameter => {
expectType<string>(someOtherParameter);

return 'some-third-string';
},
);

expectType<string | typeof pipelineBreak>(pipelineResultWithChanceOfBreak);
expectNotAssignable<string>(pipelineResultWithChanceOfBreak);

const pipelineResultWithNoChanceOfBreak = pipeline(
'some-string',
someParameter => {
expectType<string>(someParameter);

return 'some-other-string';
},
someOtherParameter => {
expectType<string>(someOtherParameter);

return 'some-third-string';
},
);

expectType<string>(pipelineResultWithNoChanceOfBreak);
expectNotAssignable<typeof pipelineBreak>(pipelineResultWithNoChanceOfBreak);

const pipelineResultWithAsyncChanceOfBreak = pipeline(
'some-string',
someParameter => {
expectType<string>(someParameter);

return Math.random() > 0.5 ? pipelineBreak : String('some-other-string');
},
async someOtherParameter => {
expectType<string>(someOtherParameter);

return await 'some-third-string';
},
);

expectType<Promise<string | typeof pipelineBreak>>(
pipelineResultWithAsyncChanceOfBreak,
);
expectNotAssignable<Promise<string>>(pipelineResultWithAsyncChanceOfBreak);

const pipelineResultWithNoAsyncChanceOfBreak = pipeline(
'some-string',
async someParameter => {
expectType<string>(someParameter);

return await 'some-other-string';
},
someOtherParameter => {
expectType<string>(someOtherParameter);

return 'some-third-string';
},
);

expectType<Promise<string>>(pipelineResultWithNoAsyncChanceOfBreak);
expectNotAssignable<Promise<typeof pipelineBreak>>(
pipelineResultWithNoAsyncChanceOfBreak,
);
2 changes: 1 addition & 1 deletion packages/fp/src/relationJoin/relationJoin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
reject,
some,
} from 'lodash/fp';
import pipeline from '../pipeline/pipeline';
import { pipeline } from '../pipeline/pipeline';
import awaitAll from '../awaitAll/awaitAll';

const relationJoin = (currentJoinObjects, constraint, ...constraints) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from 'lodash/fp';

import matchAll from '../matchAll/matchAll';
import pipeline from '../pipeline/pipeline';
import { pipeline } from '../pipeline/pipeline';

const replaceTagsWithValues = curry((valuesForTags, oldStringWithTags) => {
const newStringWithTags = pipeline(
Expand Down

0 comments on commit 6a78b40

Please sign in to comment.