Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add branch awareness to TypeScript types (auto-expanding) #435

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
63 changes: 50 additions & 13 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ export interface Nodeback<E, R> {
(err: E | null, value?: R): void
}

export type ConcurrentRejected<T> = ConcurrentFutureInstance<T, never>;

export type ConcurrentResolved<T> = ConcurrentFutureInstance<never, T>;

export interface ConcurrentFutureInstance<L, R> {
sequential: FutureInstance<L, R>
'fantasy-land/ap'<A, B>(this: ConcurrentFutureInstance<L, (value: A) => B>, right: ConcurrentFutureInstance<L, A>): ConcurrentFutureInstance<L, B>
'fantasy-land/map'<RB>(mapper: (value: R) => RB): ConcurrentFutureInstance<L, RB>
'fantasy-land/alt'(right: ConcurrentFutureInstance<L, R>): ConcurrentFutureInstance<L, R>
}

export type Rejected<T> = FutureInstance<T, never>;

export type Resolved<T> = FutureInstance<never, T>;

export interface FutureInstance<L, R> {

/** The Future constructor */
Expand All @@ -51,22 +59,46 @@ export interface FutureInstance<L, R> {
}

/** Creates a Future which resolves after the given duration with the given value. See https://github.com/fluture-js/Fluture#after */
export function after(duration: number): <R>(value: R) => FutureInstance<never, R>
export function after(duration: number): <R>(value: R) => Resolved<R>

/** Logical and for Futures. See https://github.com/fluture-js/Fluture#and */
export function and<L, R>(left: FutureInstance<L, R>): (right: FutureInstance<L, any>) => FutureInstance<L, R>
export function and<LB, RB>(second: FutureInstance<LB, RB>): {
(first: typeof never): typeof never
<LA>(first: Rejected<LA>): Rejected<LA>
(first: Resolved<any>): FutureInstance<LB, RB>
<LA>(first: FutureInstance<LA, any>): FutureInstance<LA | LB, RB>
}

/** Logical or for Futures. See https://github.com/fluture-js/Fluture#alt */
export function alt<L, R>(left: FutureInstance<L, R>): (right: FutureInstance<L, R>) => FutureInstance<L, R>
export function alt<LB, RB>(second: FutureInstance<LB, RB>): {
(first: typeof never): typeof never
(first: Rejected<any>): FutureInstance<LB, RB>
<RA>(first: Resolved<RA>): Resolved<RA>
<RA>(first: FutureInstance<any, RA>): FutureInstance<LB, RA | RB>
}

/** Race two ConcurrentFutures. See https://github.com/fluture-js/Fluture#alt */
export function alt<L, R>(left: ConcurrentFutureInstance<L, R>): (right: ConcurrentFutureInstance<L, R>) => ConcurrentFutureInstance<L, R>
export function alt<LB, RB>(second: ConcurrentFutureInstance<LB, RB>): <LA, RA>(first: ConcurrentFutureInstance<LA, RA>) => ConcurrentFutureInstance<LA | LB, RA | RB>

/** Apply the function in the right Future to the value in the left Future. See https://github.com/fluture-js/Fluture#ap */
export function ap<L, RA>(value: FutureInstance<L, RA>): <RB>(apply: FutureInstance<L, (value: RA) => RB>) => FutureInstance<L, RB>
export function ap<LA>(left: Rejected<LA>): {
(right: typeof never): typeof never
<LB>(right: Rejected<LB>): Rejected<LB>
(right: Resolved<(value: any) => any>): Rejected<LA>
<LB>(right: FutureInstance<LB, (value: any) => any>): Rejected<LA | LB>
}

/** Apply the function in the right Future to the value in the left Future. See https://github.com/fluture-js/Fluture#ap */
export function ap<LA, RA>(left: FutureInstance<LA, RA>): {
(right: typeof never): typeof never
<LB>(right: Rejected<LB>): Rejected<LB>
<RB>(right: Resolved<(value: RA) => RB>): FutureInstance<LA, RB>
<LB, RB>(right: FutureInstance<LB, (value: RA) => RB>): FutureInstance<LA | LB, RB>
}

/** Apply the function in the right ConcurrentFuture to the value in the left ConcurrentFuture. See https://github.com/fluture-js/Fluture#ap */
export function ap<L, RA>(value: ConcurrentFutureInstance<L, RA>): <RB>(apply: ConcurrentFutureInstance<L, (value: RA) => RB>) => ConcurrentFutureInstance<L, RB>
export function ap<LA>(value: ConcurrentRejected<LA>): <LB>(apply: ConcurrentFutureInstance<LB, (value: any) => any>) => ConcurrentRejected<LA | LB>
export function ap<LA, RA>(value: ConcurrentFutureInstance<LA, RA>): <LB, RB>(apply: ConcurrentFutureInstance<LB, (value: RA) => RB>) => ConcurrentFutureInstance<LA | LB, RB>

/** Apply the function in the right Future to the value in the left Future in parallel. See https://github.com/fluture-js/Fluture#pap */
export function pap<L, RA>(value: FutureInstance<L, RA>): <RB>(apply: FutureInstance<L, (value: RA) => RB>) => FutureInstance<L, RB>
Expand All @@ -78,7 +110,12 @@ export function attempt<L, R>(fn: () => R): FutureInstance<L, R>
export function attemptP<L, R>(fn: () => Promise<R>): FutureInstance<L, R>

/** Create a Future using the inner value of the given Future. See https://github.com/fluture-js/Fluture#bichain */
export function bichain<LA, LB, RB>(lmapper: (reason: LA) => FutureInstance<LB, RB>): <RA>(rmapper: (value: RA) => FutureInstance<LB, RB>) => (source: FutureInstance<LA, RA>) => FutureInstance<LB, RB>
export function bichain<LA, LB, RB>(onReject: (reason: LA) => FutureInstance<LB, RB>): <RA, LC, RC>(onResolve: (value: RA) => FutureInstance<LC, RC>) => {
(source: typeof never): typeof never
(source: Rejected<LA>): FutureInstance<LB, RB>
(source: Resolved<RA>): FutureInstance<LC, RC>
(source: FutureInstance<LA, RA>): FutureInstance<LB | LC, RB | RC>
}

/** Map over both branches of the given Bifunctor at once. See https://github.com/fluture-js/Fluture#bimap */
export function bimap<LA, LB>(lmapper: (reason: LA) => LB): <RA, RB>(rmapper: (value: RA) => RB) => (source: FutureInstance<LA, RA>) => FutureInstance<LB, RB>
Expand Down Expand Up @@ -111,7 +148,7 @@ export function extractLeft<L, R>(source: FutureInstance<L, R>): Array<L>
export function extractRight<L, R>(source: FutureInstance<L, R>): Array<R>

/** Coalesce both branches into the resolution branch. See https://github.com/fluture-js/Fluture#coalesce */
export function coalesce<LA, R>(lmapper: (left: LA) => R): <RA>(rmapper: (right: RA) => R) => (source: FutureInstance<LA, RA>) => FutureInstance<never, R>
export function coalesce<LA, R>(lmapper: (left: LA) => R): <RA>(rmapper: (right: RA) => R) => (source: FutureInstance<LA, RA>) => Resolved<R>

/** Fork the given Future into the given continuations. See https://github.com/fluture-js/Fluture#fork */
export function fork<L>(reject: RejectFunction<L>): <R>(resolve: ResolveFunction<R>) => (source: FutureInstance<L, R>) => Cancel
Expand Down Expand Up @@ -146,13 +183,13 @@ export function map<RA, RB>(mapper: (value: RA) => RB): <T extends FutureInstanc
export function mapRej<LA, LB>(mapper: (reason: LA) => LB): <R>(source: FutureInstance<LA, R>) => FutureInstance<LB, R>

/** A Future that never settles. See https://github.com/fluture-js/Fluture#never */
export var never: FutureInstance<never, never>
export var never: Resolved<never>

/** Create a Future using a provided Node-style callback. See https://github.com/fluture-js/Fluture#node */
export function node<L, R>(fn: (done: Nodeback<L, R>) => void): FutureInstance<L, R>

/** Create a Future with the given resolution value. See https://github.com/fluture-js/Fluture#of */
export function resolve<R>(value: R): FutureInstance<never, R>
export function resolve<R>(value: R): Resolved<R>

/** Run an Array of Futures in parallel, under the given concurrency limit. See https://github.com/fluture-js/Fluture#parallel */
export function parallel(concurrency: number): <L, R>(futures: Array<FutureInstance<L, R>>) => FutureInstance<L, Array<R>>
Expand All @@ -164,10 +201,10 @@ export function promise<R>(source: FutureInstance<Error, R>): Promise<R>
export function race<L, R>(left: FutureInstance<L, R>): (right: FutureInstance<L, R>) => FutureInstance<L, R>

/** Create a Future with the given rejection reason. See https://github.com/fluture-js/Fluture#reject */
export function reject<L>(reason: L): FutureInstance<L, never>
export function reject<L>(reason: L): Rejected<L>

/** Creates a Future which rejects after the given duration with the given reason. See https://github.com/fluture-js/Fluture#rejectafter */
export function rejectAfter(duration: number): <L>(reason: L) => FutureInstance<L, never>
export function rejectAfter(duration: number): <L>(reason: L) => Rejected<L>

/** Convert a ConcurrentFuture to a regular Future. See https://github.com/fluture-js/Fluture#concurrentfuture */
export function seq<L, R>(source: ConcurrentFutureInstance<L, R>): FutureInstance<L, R>
Expand All @@ -176,7 +213,7 @@ export function seq<L, R>(source: ConcurrentFutureInstance<L, R>): FutureInstanc
export function swap<L, R>(source: FutureInstance<L, R>): FutureInstance<R, L>

/** Fork the Future into the given continuation. See https://github.com/fluture-js/Fluture#value */
export function value<R>(resolve: ResolveFunction<R>): (source: FutureInstance<never, R>) => Cancel
export function value<R>(resolve: ResolveFunction<R>): (source: Resolved<R>) => Cancel

/** Enable or disable debug mode. See https://github.com/fluture-js/Fluture#debugmode */
export function debugMode(debug: boolean): void;
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"test:build": "npm run build && oletus test/build/*.js",
"coverage:upload": "c8 report --reporter=text-lcov > coverage.lcov && codecov",
"coverage:report": "c8 report --reporter=html",
"test:types": "tsc index.d.ts"
"test:types": "tsd"
},
"author": "Aldwin Vlasblom <aldwin.vlasblom@gmail.com> (https://github.com/Avaq)",
"homepage": "https://github.com/fluture-js/Fluture",
Expand Down Expand Up @@ -83,7 +83,11 @@
"sanctuary-benchmark": "^1.0.0",
"sanctuary-either": "^2.0.0",
"sanctuary-type-classes": "^12.0.0",
"tsd": "^0.11.0",
"typescript": "^3.6.4",
"xyz": "^4.0.0"
},
"tsd": {
"directory": "test/types"
}
}
6 changes: 6 additions & 0 deletions test/types/after.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {expectType} from 'tsd';

import * as fl from '../../index.js';

expectType<fl.FutureInstance<never, number>> (fl.after (1) (42));
expectType<fl.FutureInstance<never, string>> (fl.after (1) ('a'));
35 changes: 35 additions & 0 deletions test/types/alt.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {expectType} from 'tsd';

import * as fl from '../../index.js';

const fsn: fl.FutureInstance<string, number> = fl.resolve (42);
const fns: fl.FutureInstance<number, string> = fl.resolve ('a');

expectType<fl.FutureInstance<never, never>> (fl.alt (fl.never) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.alt (fl.reject ('a')) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.alt (fl.resolve ('a')) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.alt (fl.never) (fl.reject ('a')));
expectType<fl.FutureInstance<never, string>> (fl.alt (fl.never) (fl.resolve ('a')));
expectType<fl.FutureInstance<never, number>> (fl.alt (fl.reject ('a')) (fl.resolve (42)));
expectType<fl.FutureInstance<never, number>> (fl.alt (fl.resolve (42)) (fl.reject ('a')));
expectType<fl.FutureInstance<never, number>> (fl.alt (fl.resolve (42)) (fl.resolve (42)));
expectType<fl.FutureInstance<number, never>> (fl.alt (fl.reject (42)) (fl.reject (42)));
expectType<fl.FutureInstance<never, number>> (fl.alt (fl.resolve ('a')) (fl.resolve (42)));
expectType<fl.FutureInstance<string, never>> (fl.alt (fl.reject ('a')) (fl.reject (42)));
expectType<fl.FutureInstance<string, number | string>> (fl.alt (fsn) (fns));

const csn: fl.ConcurrentFutureInstance<string, number> = fl.Par (fl.resolve (42));
const cns: fl.ConcurrentFutureInstance<number, string> = fl.Par (fl.resolve ('a'));

expectType<fl.ConcurrentFutureInstance<never, never>> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.never)));
expectType<fl.ConcurrentFutureInstance<string, never>> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.never)));
expectType<fl.ConcurrentFutureInstance<never, string>> (fl.alt (fl.Par (fl.resolve ('a'))) (fl.Par (fl.never)));
expectType<fl.ConcurrentFutureInstance<string, never>> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.reject ('a'))));
expectType<fl.ConcurrentFutureInstance<never, string>> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.resolve ('a'))));
expectType<fl.ConcurrentFutureInstance<string, number>> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.resolve (42))));
expectType<fl.ConcurrentFutureInstance<string, number>> (fl.alt (fl.Par (fl.resolve (42))) (fl.Par (fl.reject ('a'))));
expectType<fl.ConcurrentFutureInstance<never, number>> (fl.alt (fl.Par (fl.resolve (42))) (fl.Par (fl.resolve (42))));
expectType<fl.ConcurrentFutureInstance<number, never>> (fl.alt (fl.Par (fl.reject (42))) (fl.Par (fl.reject (42))));
expectType<fl.ConcurrentFutureInstance<never, number | string>> (fl.alt (fl.Par (fl.resolve ('a'))) (fl.Par (fl.resolve (42))));
expectType<fl.ConcurrentFutureInstance<string | number, never>> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.reject (42))));
expectType<fl.ConcurrentFutureInstance<string | number, number | string>> (fl.alt (csn) (cns));
18 changes: 18 additions & 0 deletions test/types/and.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {expectType} from 'tsd';

import * as fl from '../../index.js';

const fsn: fl.FutureInstance<string, number> = fl.resolve (42);
const fns: fl.FutureInstance<number, string> = fl.resolve ('a');

expectType<fl.FutureInstance<never, never>> (fl.and (fl.never) (fl.never));
expectType<fl.FutureInstance<string, never>> (fl.and (fl.never) (fl.reject ('a')));
expectType<fl.FutureInstance<never, never>> (fl.and (fl.never) (fl.resolve ('a')));
expectType<fl.FutureInstance<never, never>> (fl.and (fl.reject ('a')) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.and (fl.resolve ('a')) (fl.never));
expectType<fl.FutureInstance<string, never>> (fl.and (fl.resolve (42)) (fl.reject ('a')));
expectType<fl.FutureInstance<string, never>> (fl.and (fl.reject ('a')) (fl.resolve (42)));
expectType<fl.FutureInstance<never, number>> (fl.and (fl.resolve (42)) (fl.resolve (42)));
expectType<fl.FutureInstance<number, never>> (fl.and (fl.reject (42)) (fl.reject (42)));
expectType<fl.FutureInstance<string, number>> (fl.and (fsn) (fsn));
expectType<fl.FutureInstance<string | number, number>> (fl.and (fsn) (fns));
55 changes: 55 additions & 0 deletions test/types/ap.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {expectType, expectError} from 'tsd';

import * as fl from '../../index.js';

const mx = fl.resolve (42);
const mf = fl.resolve ((x: number) => x + 1);

const msx: fl.FutureInstance<string, number> = fl.resolve (42);
const mnx: fl.FutureInstance<number, number> = fl.resolve (42);
const msf: fl.FutureInstance<string, (x: number) => number> = fl.resolve ((x: number) => x + 1);

expectType<fl.FutureInstance<never, number>> (fl.ap (mx) (mf));
expectType<fl.FutureInstance<string, number>> (fl.ap (msx) (msf));
expectType<fl.FutureInstance<string | number, number>> (fl.ap (mnx) (msf));

expectType<fl.FutureInstance<never, never>> (fl.ap (fl.never) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.ap (mx) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.ap (fl.never) (mf));

expectType<fl.FutureInstance<never, never>> (fl.ap (fl.reject ('a')) (fl.never));
expectType<fl.FutureInstance<string, never>> (fl.ap (fl.never) (fl.reject ('a')));

expectType<fl.FutureInstance<string, never>> (fl.ap (fl.reject ('a')) (mf));
expectType<fl.FutureInstance<string, never>> (fl.ap (mx) (fl.reject ('a')));
expectType<fl.FutureInstance<number, never>> (fl.ap (fl.reject (42)) (fl.reject (42)));
expectType<fl.FutureInstance<number, never>> (fl.ap (fl.reject ('a')) (fl.reject (42)));

expectError (fl.ap (mx) (mx));
expectError (fl.ap (mf) (mf));

const cmx = fl.Par (fl.resolve (42));
const cmf = fl.Par (fl.resolve ((x: number) => x + 1));

const cmsx: fl.ConcurrentFutureInstance<string, number> = fl.Par (fl.resolve (42));
const cmnx: fl.ConcurrentFutureInstance<number, number> = fl.Par (fl.resolve (42));
const cmsf: fl.ConcurrentFutureInstance<string, (x: number) => number> = fl.Par (fl.resolve ((x: number) => x + 1));

expectType<fl.ConcurrentFutureInstance<never, number>> (fl.ap (cmx) (cmf));
expectType<fl.ConcurrentFutureInstance<string, number>> (fl.ap (cmsx) (cmsf));
expectType<fl.ConcurrentFutureInstance<string | number, number>> (fl.ap (cmnx) (cmsf));

expectType<fl.ConcurrentFutureInstance<never, never>> (fl.ap (fl.Par (fl.never)) (fl.Par (fl.never)));
expectType<fl.ConcurrentFutureInstance<never, never>> (fl.ap (cmx) (fl.Par (fl.never)));
expectType<fl.ConcurrentFutureInstance<never, never>> (fl.ap (fl.Par (fl.never)) (cmf));

expectType<fl.ConcurrentFutureInstance<string, never>> (fl.ap (fl.Par (fl.reject ('a'))) (fl.Par (fl.never)));
expectType<fl.ConcurrentFutureInstance<string, never>> (fl.ap (fl.Par (fl.never)) (fl.Par (fl.reject ('a'))));

expectType<fl.ConcurrentFutureInstance<string, never>> (fl.ap (fl.Par (fl.reject ('a'))) (cmf));
expectType<fl.ConcurrentFutureInstance<string, never>> (fl.ap (cmx) (fl.Par (fl.reject ('a'))));
expectType<fl.ConcurrentFutureInstance<number, never>> (fl.ap (fl.Par (fl.reject (42))) (fl.Par (fl.reject (42))));
expectType<fl.ConcurrentFutureInstance<string | number, never>> (fl.ap (fl.Par (fl.reject ('a'))) (fl.Par (fl.reject (42))));

expectError (fl.ap (cmx) (cmx));
expectError (fl.ap (cmf) (cmf));
6 changes: 6 additions & 0 deletions test/types/attempt-p.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {expectType} from 'tsd';

import * as fl from '../../index.js';

expectType<fl.FutureInstance<never, number>> (fl.attemptP (() => Promise.resolve (42)));
expectType<fl.FutureInstance<unknown, never>> (fl.attemptP (() => Promise.reject ('a')));
6 changes: 6 additions & 0 deletions test/types/attempt.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {expectType} from 'tsd';

import * as fl from '../../index.js';

expectType<fl.FutureInstance<unknown, number>> (fl.attempt (() => 42));
expectType<fl.FutureInstance<unknown, never>> (fl.attempt (() => { throw new Error }));
31 changes: 31 additions & 0 deletions test/types/bichain.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {expectType, expectError} from 'tsd';

import * as fl from '../../index.js';

const frej = (x: string) => fl.reject (x + '!');
const fres = (x: string) => fl.resolve (x + '!');
const grej = (y: number) => fl.reject (y + 1);
const gres = (y: number) => fl.resolve (y + 1);

const msx = fl.reject ('a');
const mxn = fl.resolve (42);
const msn: fl.FutureInstance<string, number> = fl.resolve (42);

expectType<fl.FutureInstance<string, never>> (fl.bichain (frej) (grej) (msx));
expectType<fl.FutureInstance<never, string>> (fl.bichain (fres) (gres) (msx));

expectType<fl.FutureInstance<number, never>> (fl.bichain (frej) (grej) (mxn));
expectType<fl.FutureInstance<never, number>> (fl.bichain (fres) (gres) (mxn));

expectType<fl.FutureInstance<string | number, never>> (fl.bichain (frej) (grej) (msn));
expectType<fl.FutureInstance<never, string | number>> (fl.bichain (fres) (gres) (msn));
expectType<fl.FutureInstance<number, string>> (fl.bichain (fres) (grej) (msn));
expectType<fl.FutureInstance<string, number>> (fl.bichain (frej) (gres) (msn));

expectType<fl.FutureInstance<never, never>> (fl.bichain (frej) (grej) (fl.never));
expectType<fl.FutureInstance<never, never>> (fl.bichain (fres) (gres) (fl.never));

expectError (fl.bichain (frej) (grej) (fl.reject (42)));
expectError (fl.bichain (frej) (grej) (fl.resolve ('a')));
expectError (fl.bichain (fres) (gres) (fl.reject (42)));
expectError (fl.bichain (fres) (gres) (fl.resolve ('a')));