Added types
This commit is contained in:
parent
afdba8fb8a
commit
c8c0fba502
12 changed files with 5029 additions and 697 deletions
48
lib/functional.d.ts
vendored
Normal file
48
lib/functional.d.ts
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
type Combinations<A extends Array<any>, Acc extends Array<any> = []> =
|
||||
A extends [a: infer A, ...rest: infer Rest]
|
||||
? [[...Acc, A], ...Combinations<Rest, [...Acc, A]>]
|
||||
: []
|
||||
|
||||
type Curried<T extends (...args: any) => any, LeftArgs extends Array<any> = [Parameters<T>[0]]> =
|
||||
T extends (...args: LeftArgs) => any
|
||||
? T
|
||||
: T extends (...args: [...LeftArgs, ...infer V]) => infer R
|
||||
? (...args: LeftArgs) => Curried<(...args: V) => R, LeftArgs>
|
||||
: T extends (a: infer A) => infer R
|
||||
? (arg_last: A) => R
|
||||
: T extends (a: infer A, ...rest: infer V) => infer R
|
||||
? ((arg_inner: A) => Curried<(...args: V) => R, LeftArgs>)
|
||||
: never
|
||||
|
||||
type CurriedVariants<
|
||||
T extends (...args: any) => any,
|
||||
C extends any[][] = Combinations<Parameters<T>>
|
||||
> = T extends () => any
|
||||
? [T]
|
||||
: { [Cn in keyof C]: Curried<T, Extract<C[Cn], C[number]>>};
|
||||
|
||||
type Named<QueryKey extends string, Value> = {
|
||||
[key in QueryKey]: Value;
|
||||
}
|
||||
|
||||
type NamedCurriedVariants<Name extends string, T extends (...args: any) => any, V = CurriedVariants<T>> =
|
||||
{ [C in keyof V]: C extends `${number}` ? Named<Name, V[C]> : never };
|
||||
|
||||
type UnionToIntersection<U> =
|
||||
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
|
||||
|
||||
type InterfaceValues<T> = T extends Record<any, infer Inner> ? Inner : never;
|
||||
|
||||
type ToFunctional<Func, ExtraArg> = Func extends (...a: infer U) => infer R ? (...a: [...U, ExtraArg]) => R: never;
|
||||
|
||||
type Functions = {
|
||||
[x: string]: (...args: any) => any
|
||||
};
|
||||
|
||||
export type Typify<T> = { [ K in keyof T ]: T[K] };
|
||||
|
||||
export type Functionalify<T extends Typify<Functions>, ExtraArg> =
|
||||
UnionToIntersection<InterfaceValues<{
|
||||
[K in keyof T]: UnionToIntersection<NamedCurriedVariants<Extract<K, string>, ToFunctional<T[K], ExtraArg>>[number]>
|
||||
}>>
|
67
lib/index.d.ts
vendored
Normal file
67
lib/index.d.ts
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
type MapFn<T, R> = (value: T) => R;
|
||||
|
||||
type FlatChannel<T, Depth extends number> = {
|
||||
"done": T,
|
||||
"recur": T extends ReadChannel<infer InnerT>
|
||||
? FlatChannel<InnerT, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
|
||||
: T
|
||||
}[Depth extends -1 ? "done" : "recur"];
|
||||
|
||||
interface CancelablePromise<T> extends Promise<T> {
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
interface OrderPromise<C, T> extends CancelablePromise<T> {
|
||||
channel: C;
|
||||
prethen(onFulfilled: (value: C) => void): void;
|
||||
}
|
||||
type UnwrapOrderPromise<A> = A extends OrderPromise<any, infer T> ? T : never
|
||||
|
||||
export interface ReadChannel<T> {
|
||||
concat<C extends ReadChannel<T>>(...values: (T | C)[]): C;
|
||||
every(callback: (value: T) => boolean, thisArg?: any): Promise<boolean>;
|
||||
filter(callback: (value: T) => boolean, thisArg?: any): ReadWriteChannel<T>;
|
||||
flat<D extends number = 1>(depth?: D): ReadWriteChannel<FlatChannel<T, D>>;
|
||||
flatMap<R>(mapperFunction: MapFn<T, R | R[]>, thisArg?: any): ReadWriteChannel<R>;
|
||||
forEach(callbackfn: (value: T) => void, thisArg?: any): Promise<void>;
|
||||
join(separator?: string): Promise<string>;
|
||||
map<R>(mapperFunction: MapFn<T, R>, thisArg?: any): ReadWriteChannel<R>;
|
||||
readOnly(): ReadChannel<T>;
|
||||
reduce(callbackfn: (prev: any, next: T) => any, initialValue?: any): Promise<any>;
|
||||
shift(): OrderPromise<this, T>;
|
||||
slice(start?: number, end?: number): ReadWriteChannel<T>;
|
||||
some(callbackfn: (value: T) => boolean, thisArg?: any): Promise<boolean>;
|
||||
toString(): string;
|
||||
value(): T;
|
||||
values(): Promise<T[]>;
|
||||
}
|
||||
|
||||
export interface WriteChannel<T> {
|
||||
close(): void;
|
||||
length(): number;
|
||||
push(value: T): OrderPromise<this, number>;
|
||||
writeOnly(): WriteChannel<T>;
|
||||
}
|
||||
|
||||
export type ReadWriteChannel<T> = ReadChannel<T> & WriteChannel<T>;
|
||||
|
||||
|
||||
import { Functionalify, Typify } from './functional';
|
||||
// Ideally these would not be channels of `any`, but I couldn't find a way to pass on the generic arguments
|
||||
type FunctionalInterface =
|
||||
Functionalify<Typify<ReadChannel<any>>, ReadChannel<any>> & Functionalify<Typify<WriteChannel<any>>, WriteChannel<any>>
|
||||
|
||||
export type ChannelConstructor = {
|
||||
<T>(length?: number): ReadWriteChannel<T>;
|
||||
new <T>(length?: number): ReadWriteChannel<T>;
|
||||
all<T>(...values: T[]): ReadWriteChannel<T>;
|
||||
from<T, R = T>(iterable: Iterable<T>, mapfn?: MapFn<T, R>, thisArg?: any): ReadChannel<R>;
|
||||
from<T>(callback: () => T): ReadChannel<T>;
|
||||
of<T extends unknown[]>(...values: T): ReadChannel<T[number]>;
|
||||
isChannel<T extends ReadChannel<any> | WriteChannel<any>>(channel: T): channel is T;
|
||||
select<T extends OrderPromise<any, any>[]>(promises: T): CancelablePromise<ReadChannel<UnwrapOrderPromise<T[number]>>>;
|
||||
} & FunctionalInterface
|
||||
|
||||
declare const Channel: ChannelConstructor;
|
||||
|
||||
export default Channel;
|
4527
package-lock.json
generated
4527
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,10 @@
|
|||
"bugs": {
|
||||
"url": "https://gitlab.com/NodeGuy/channel/issues"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nodeguy/assert": "0.1.4",
|
||||
"@stryker-mutator/core": "4.4.1",
|
||||
|
@ -35,7 +38,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"mutationTest": "stryker run",
|
||||
"test": "nyc mocha --recursive"
|
||||
"test": "nyc ts-mocha --recursive"
|
||||
},
|
||||
"nyc": {
|
||||
"check-coverage": true,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Channel = require("../../lib");
|
||||
const Channel = require(`../../lib`);
|
||||
|
||||
it(`doubleselect`, async function() {
|
||||
this.timeout(10 * 1000);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Channel = require("../../lib");
|
||||
const Channel = require(`../../lib`);
|
||||
|
||||
it(`fifo`, function() {
|
||||
const N = 10;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Channel = require("../../lib");
|
||||
const Channel = require(`../../lib`);
|
||||
|
||||
it(`goroutines`, async function() {
|
||||
const f = async (left, right) => {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"use strict";
|
||||
|
||||
const assert = require(`@nodeguy/assert`);
|
||||
const Channel = require("../../lib");
|
||||
const Channel = require(`../../lib`);
|
||||
|
||||
it(`perm`, function() {
|
||||
const c = Channel();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Channel = require("../../lib");
|
||||
const Channel = require(`../../lib`);
|
||||
|
||||
it(`select`, async function() {
|
||||
const closed = Channel();
|
||||
|
|
520
test/index.js
520
test/index.js
|
@ -1,519 +1 @@
|
|||
"use strict";
|
||||
|
||||
const assert = require(`@nodeguy/assert`);
|
||||
const Channel = require(`../lib`);
|
||||
const stream = require(`stream`);
|
||||
|
||||
const assertRejects = async (callback, reason) => {
|
||||
try {
|
||||
await callback();
|
||||
} catch (exception) {
|
||||
if (reason) {
|
||||
assert.deepEqual(exception, reason);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert.fail(null, reason, `Missing expected rejection.`);
|
||||
};
|
||||
|
||||
describe(`Channel function`, function() {
|
||||
it(`allows the use of new`, function() {
|
||||
return new Channel();
|
||||
});
|
||||
|
||||
it(`is frozen`, function() {
|
||||
assert.throws(() => {
|
||||
Channel.frozen = false;
|
||||
});
|
||||
});
|
||||
|
||||
it(`creates a frozen object`, function() {
|
||||
assert.throws(() => {
|
||||
Channel().frozen = false;
|
||||
});
|
||||
});
|
||||
|
||||
it(`creates a buffered channel`, async function() {
|
||||
const channel = Channel(3);
|
||||
await channel.push(0);
|
||||
await channel.push(1);
|
||||
await channel.push(2);
|
||||
await channel.close();
|
||||
assert.deepEqual(await channel.values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`all`, async function() {
|
||||
const a = Channel.of(0, 1);
|
||||
const b = Channel.of(2, 3);
|
||||
assert.deepEqual(await Channel.all([a, b]).values(), [[0, 2], [1, 3]]);
|
||||
});
|
||||
|
||||
describe(`from`, function() {
|
||||
describe(`types`, function() {
|
||||
it(`callback`, async function() {
|
||||
let counter = 0;
|
||||
const callback = () => (counter < 3 ? counter++ : undefined);
|
||||
assert.deepEqual(await Channel.from(callback).values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`iterable`, async function() {
|
||||
assert.deepEqual(await Channel.from([0, 1, 2]).values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`Node.js's stream.Readable`, async function() {
|
||||
const readable = stream.PassThrough({ objectMode: true });
|
||||
readable.write(0);
|
||||
readable.write(1);
|
||||
readable.end(2);
|
||||
assert.deepEqual(await Channel.from(readable).values(), [0, 1, 2]);
|
||||
});
|
||||
});
|
||||
|
||||
it(`with mapfn`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.from([`a`, `b`, `c`], (value) =>
|
||||
value.toUpperCase()
|
||||
).values(),
|
||||
[`A`, `B`, `C`]
|
||||
);
|
||||
});
|
||||
|
||||
it(`returns a readOnly channel`, function() {
|
||||
assert(!(`push` in Channel.from([0, 1, 2])));
|
||||
});
|
||||
});
|
||||
|
||||
it(`isChannel`, function() {
|
||||
assert(Channel.isChannel(Channel.of(0, 1, 2)));
|
||||
assert(!Channel.isChannel(Array.of(0, 1, 2)));
|
||||
assert(!Channel.isChannel(undefined));
|
||||
assert(!Channel.isChannel(null));
|
||||
assert(Channel.isChannel(Channel().readOnly()));
|
||||
assert(Channel.isChannel(Channel().writeOnly()));
|
||||
});
|
||||
|
||||
it(`of`, async function() {
|
||||
const channel = Channel.of(0, 1, 2);
|
||||
assert(!(`push` in channel));
|
||||
assert.deepEqual(await channel.values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
describe(`select`, function() {
|
||||
it(`miscellaneous`, async function() {
|
||||
const a = Channel();
|
||||
const b = Channel();
|
||||
|
||||
(async () => {
|
||||
await b.push(0);
|
||||
await a.push(1);
|
||||
await a.shift();
|
||||
})();
|
||||
|
||||
assert.equal(await Channel.select([a.shift(), b.shift()]), b);
|
||||
assert.equal(b.value(), 0);
|
||||
assert.equal(await a.shift(), 1);
|
||||
assert.equal(await Channel.select([a.push(0), b.shift()]), a);
|
||||
});
|
||||
});
|
||||
|
||||
it(`allows for non-blocking selects`, async function() {
|
||||
const a = Channel();
|
||||
const b = Channel();
|
||||
const closed = Channel.of();
|
||||
|
||||
switch (await Channel.select([a.shift(), b.push(0), closed.shift()])) {
|
||||
case a:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
case b:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it(`cancel`, async function() {
|
||||
const channel = Channel();
|
||||
Channel.select([channel.push(`cancelled`)]).cancel();
|
||||
const closed = Channel.of();
|
||||
|
||||
assert.equal(
|
||||
await Channel.select([channel.shift(), closed.shift()]),
|
||||
closed
|
||||
);
|
||||
});
|
||||
|
||||
it(`complains when given non-channel method promises`, function() {
|
||||
return assertRejects(async () => {
|
||||
await Channel.select([Promise.resolve()]);
|
||||
}, new TypeError(`Channel.select accepts only promises returned by push & shift.`));
|
||||
});
|
||||
|
||||
it(`complains if not given an array`, function() {
|
||||
return assertRejects(async () => {
|
||||
await Channel.select(Channel.of(0).shift(), Channel.of(1).shift());
|
||||
}, new TypeError(`Channel.select: Argument must be an array.`));
|
||||
});
|
||||
});
|
||||
|
||||
describe(`functional interface`, async function() {
|
||||
it(`shift`, async function() {
|
||||
assert.equal(await Channel.shift(Channel.of(0)), 0);
|
||||
});
|
||||
|
||||
describe(`slice`, function() {
|
||||
it(`full application`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.slice(1, 4, Channel.of(0, 1, 2, 3, 4)).values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
|
||||
it(`single argument application`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.slice(1)(4)(Channel.of(0, 1, 2, 3, 4)).values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
|
||||
it(`double argument application`, async function() {
|
||||
assert.equal(await Channel.reduce(Math.max, 10, Channel.of(0, 1, 2)), 10);
|
||||
|
||||
assert.deepEqual(
|
||||
await Channel.slice(1, 4)(Channel.of(0, 1, 2, 3, 4)).values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Channel object`, function() {
|
||||
describe(`close`, function() {
|
||||
it(`can't close an already closed channel`, function() {
|
||||
const channel = Channel();
|
||||
channel.close();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.close();
|
||||
}, new Error(`Can't close an already-closed channel.`));
|
||||
});
|
||||
|
||||
it(`can't push to a closed channel`, async function() {
|
||||
const channel = Channel();
|
||||
channel.close();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.push(0);
|
||||
}, new Error(`Can't push to closed channel.`));
|
||||
});
|
||||
|
||||
it(`returns 'undefined' immediately from shift`, async function() {
|
||||
const channel = Channel();
|
||||
channel.close();
|
||||
assert.strictEqual(await channel.shift(), undefined);
|
||||
});
|
||||
|
||||
it(`Don't set 'lastValue' to 'undefined' when closing a channel with a cancelled shift.`, async function() {
|
||||
const channel = Channel();
|
||||
|
||||
// Set lastValue to 0.
|
||||
channel.push(0);
|
||||
await channel.shift();
|
||||
|
||||
channel.shift().cancel();
|
||||
channel.close();
|
||||
assert.strictEqual(channel.value(), 0);
|
||||
});
|
||||
});
|
||||
|
||||
it(`concat`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.concat(Channel.of(3, 4, 5), 6)
|
||||
.values(),
|
||||
[0, 1, 2, 3, 4, 5, 6]
|
||||
);
|
||||
});
|
||||
|
||||
it(`every`, async function() {
|
||||
const even = (number) => number % 2 === 0;
|
||||
assert(!(await Channel.of(0, 1, 2).every(even)));
|
||||
assert(await Channel.of(0, 2, 4).every(even));
|
||||
});
|
||||
|
||||
it(`filter`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2, 3, 4, 5)
|
||||
.filter((value) => value % 2 !== 0)
|
||||
.values(),
|
||||
[1, 3, 5]
|
||||
);
|
||||
});
|
||||
|
||||
it(`flat`, async function() {
|
||||
const flat1 = Channel.of(1, 2, Channel.of(3, 4)).flat();
|
||||
assert.deepEqual(await flat1.values(), [1, 2, 3, 4]);
|
||||
|
||||
const flat2 = Channel.of(1, 2, Channel.of(3, 4, Channel.of(5, 6))).flat();
|
||||
assert.equal(await flat2.shift(), 1);
|
||||
assert.equal(await flat2.shift(), 2);
|
||||
assert.equal(await flat2.shift(), 3);
|
||||
assert.equal(await flat2.shift(), 4);
|
||||
assert.deepEqual(await (await flat2.shift()).values(), [5, 6]);
|
||||
|
||||
const flat3 = Channel.of(1, 2, Channel.of(3, 4, Channel.of(5, 6))).flat(2);
|
||||
assert.deepEqual(await flat3.values(), [1, 2, 3, 4, 5, 6]);
|
||||
});
|
||||
|
||||
it(`flatMap`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(1, 2, 3, 4)
|
||||
.flatMap((x) => Channel.of(x * 2))
|
||||
.values(),
|
||||
[2, 4, 6, 8]
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
await Channel.of(`it's Sunny in`, ``, `California`)
|
||||
.flatMap((x) => Channel.from(x.split(` `)))
|
||||
.values(),
|
||||
[`it's`, `Sunny`, `in`, ``, `California`]
|
||||
);
|
||||
});
|
||||
|
||||
it(`forEach`, async function() {
|
||||
const output = [];
|
||||
await Channel.of(0, 1, 2).forEach((value) => output.push(value));
|
||||
assert.deepEqual(output, [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`join`, async function() {
|
||||
assert.equal(await Channel.of(`a`, `b`, `c`).join(), `a,b,c`);
|
||||
});
|
||||
|
||||
it(`length`, function() {
|
||||
assert.equal(Channel(42).length, 42);
|
||||
});
|
||||
|
||||
it(`map`, async function() {
|
||||
const channel = Channel(3);
|
||||
await channel.push(`a`);
|
||||
await channel.push(`b`);
|
||||
await channel.push(`c`);
|
||||
await channel.close();
|
||||
const mapped = channel.map((value) => value.toUpperCase());
|
||||
assert.deepEqual(await mapped.values(), [`A`, `B`, `C`]);
|
||||
});
|
||||
|
||||
describe(`push`, function() {
|
||||
it(`with shift`, async function() {
|
||||
const channel = Channel();
|
||||
(async () => {
|
||||
await channel.push(0);
|
||||
})();
|
||||
|
||||
assert.equal(await channel.shift(), 0);
|
||||
});
|
||||
|
||||
describe(`undefined`, function() {
|
||||
it(`outside select`, function() {
|
||||
const channel = Channel();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.push(undefined);
|
||||
}, new TypeError(`Can't push 'undefined' to channel, use close instead.`));
|
||||
});
|
||||
|
||||
it(`inside select`, function() {
|
||||
const channel = Channel();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await Channel.select([channel.push(undefined)]);
|
||||
}, new TypeError(`Can't push 'undefined' to channel, use close instead.`));
|
||||
});
|
||||
});
|
||||
|
||||
it(`disallows multiple values`, function() {
|
||||
const channel = Channel();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.push(0, 1, 2);
|
||||
}, new Error(`Can't push more than one value at a time.`));
|
||||
});
|
||||
});
|
||||
|
||||
it(`readOnly`, async function() {
|
||||
const channel = Channel();
|
||||
const readOnly = channel.readOnly();
|
||||
|
||||
assert.throws(() => {
|
||||
readOnly.close();
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
readOnly.push(0);
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
readOnly.writeOnly();
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await channel.push(1);
|
||||
})();
|
||||
|
||||
assert.equal(readOnly.readOnly(), readOnly);
|
||||
assert.equal(await readOnly.shift(), 1);
|
||||
assert.equal(readOnly.value(), 1);
|
||||
|
||||
assert.throws(() => {
|
||||
readOnly.frozen = false;
|
||||
});
|
||||
});
|
||||
|
||||
describe(`reduce`, function() {
|
||||
it(`callbackfn only`, async function() {
|
||||
assert.equal(await Channel.of(0, 1, 2).reduce(Math.max), 2);
|
||||
});
|
||||
|
||||
it(`initialValue`, async function() {
|
||||
assert.equal(await Channel.of(0, 1, 2).reduce(Math.max, 10), 10);
|
||||
});
|
||||
|
||||
it(`no values without initialValue`, function() {
|
||||
return assertRejects(async () => {
|
||||
await Channel.of().reduce(Math.max);
|
||||
}, new TypeError(`No values in channel and initialValue wasn't provided.`));
|
||||
});
|
||||
});
|
||||
|
||||
describe(`shift`, function() {
|
||||
it(`with push`, async function() {
|
||||
const channel = Channel();
|
||||
(async () => {
|
||||
await channel.push(0);
|
||||
})();
|
||||
|
||||
assert.equal(await channel.shift(), 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`slice`, function() {
|
||||
it(`no start`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.slice()
|
||||
.values(),
|
||||
[0, 1, 2]
|
||||
);
|
||||
});
|
||||
|
||||
it(`start`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.slice(1)
|
||||
.values(),
|
||||
[1, 2]
|
||||
);
|
||||
});
|
||||
|
||||
it(`end`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2, 3, 4)
|
||||
.slice(1, 4)
|
||||
.values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
|
||||
it(`starts after end of channel`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.slice(10)
|
||||
.values(),
|
||||
[]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`some`, async function() {
|
||||
const even = (value) => value % 2 === 0;
|
||||
const channel = Channel.of(0, 1, 2);
|
||||
assert(await channel.some(even));
|
||||
assert.deepEqual(await channel.values(), [1, 2]);
|
||||
assert(!(await Channel.of(1, 3, 5).some(even)));
|
||||
});
|
||||
|
||||
it(`toString`, function() {
|
||||
assert.equal(Channel(10).toString(), `Channel(10)`);
|
||||
});
|
||||
|
||||
it(`value`, async function() {
|
||||
const channel = Channel(1);
|
||||
await channel.push(null);
|
||||
await channel.shift();
|
||||
assert.equal(channel.value(), null);
|
||||
channel.close();
|
||||
await channel.shift();
|
||||
assert.equal(channel.value(), undefined);
|
||||
});
|
||||
|
||||
it(`values`, async function() {
|
||||
assert.deepEqual(await Channel.of(0, 1, 2).values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
describe(`writeOnly`, function() {
|
||||
it(`provides only write methods`, async function() {
|
||||
const channel = Channel();
|
||||
const writeOnly = channel.writeOnly();
|
||||
|
||||
assert.throws(() => {
|
||||
writeOnly.readOnly();
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
writeOnly.shift();
|
||||
});
|
||||
|
||||
assert.equal(writeOnly.value, undefined);
|
||||
|
||||
(async () => {
|
||||
await channel.shift();
|
||||
})();
|
||||
|
||||
await writeOnly.push(0);
|
||||
writeOnly.close();
|
||||
|
||||
assert.throws(() => {
|
||||
writeOnly.frozen = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(`allows promises to be sent through a channel`, function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
process.once(`unhandledRejection`, reject);
|
||||
const channel = Channel();
|
||||
|
||||
(async () => {
|
||||
await channel.push(Promise.resolve(`resolved`));
|
||||
await channel.push(Promise.reject(new Error(`rejected`)));
|
||||
})();
|
||||
|
||||
assert.equal(await channel.shift(), `resolved`);
|
||||
|
||||
try {
|
||||
await channel.shift();
|
||||
} catch (exception) {
|
||||
assert.equal(exception.message, `rejected`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
require(`./typed`);
|
538
test/typed.ts
Normal file
538
test/typed.ts
Normal file
|
@ -0,0 +1,538 @@
|
|||
"use strict";
|
||||
|
||||
const assert = require(`@nodeguy/assert`);
|
||||
import Channel from '../lib';
|
||||
const stream = require(`stream`);
|
||||
|
||||
const assertRejects = async (callback, reason) => {
|
||||
try {
|
||||
await callback();
|
||||
} catch (exception) {
|
||||
if (reason) {
|
||||
assert.deepEqual(exception, reason);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert.fail(null, reason, `Missing expected rejection.`);
|
||||
};
|
||||
|
||||
describe(`Channel function`, function() {
|
||||
it(`allows the use of new`, function() {
|
||||
return new Channel();
|
||||
});
|
||||
|
||||
it(`is frozen`, function() {
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
Channel.frozen = false;
|
||||
});
|
||||
});
|
||||
|
||||
it(`creates a frozen object`, function() {
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
Channel().frozen = false;
|
||||
});
|
||||
});
|
||||
|
||||
it(`creates a buffered channel`, async function() {
|
||||
const channel = Channel(3);
|
||||
await channel.push(0);
|
||||
await channel.push(1);
|
||||
await channel.push(2);
|
||||
await channel.close();
|
||||
assert.deepEqual(await channel.values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`all`, async function() {
|
||||
const a = Channel.of(0, 1);
|
||||
const b = Channel.of(2, 3);
|
||||
assert.deepEqual(await Channel.all([a, b]).values(), [[0, 2], [1, 3]]);
|
||||
});
|
||||
|
||||
describe(`from`, function() {
|
||||
describe(`types`, function() {
|
||||
it(`callback`, async function() {
|
||||
let counter = 0;
|
||||
const callback = () => (counter < 3 ? counter++ : undefined);
|
||||
assert.deepEqual(await Channel.from(callback).values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`iterable`, async function() {
|
||||
assert.deepEqual(await Channel.from([0, 1, 2]).values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`Node.js's stream.Readable`, async function() {
|
||||
const readable = stream.PassThrough({ objectMode: true });
|
||||
readable.write(0);
|
||||
readable.write(1);
|
||||
readable.end(2);
|
||||
assert.deepEqual(await Channel.from(readable).values(), [0, 1, 2]);
|
||||
});
|
||||
});
|
||||
|
||||
it(`with mapfn`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.from([`a`, `b`, `c`], (value) =>
|
||||
value.charCodeAt(0)
|
||||
).values(),
|
||||
[97, 98, 99]
|
||||
);
|
||||
});
|
||||
|
||||
it(`returns a readOnly channel`, function() {
|
||||
assert(!(`push` in Channel.from([0, 1, 2])));
|
||||
});
|
||||
});
|
||||
|
||||
it(`isChannel`, function() {
|
||||
assert(Channel.isChannel(Channel.of(0, 1, 2)));
|
||||
// @ts-expect-error
|
||||
assert(!Channel.isChannel(Array.of(0, 1, 2)));
|
||||
assert(!Channel.isChannel(undefined));
|
||||
assert(!Channel.isChannel(null));
|
||||
assert(Channel.isChannel(Channel().readOnly()));
|
||||
assert(Channel.isChannel(Channel().writeOnly()));
|
||||
});
|
||||
|
||||
it(`of`, async function() {
|
||||
const channel = Channel.of(0, 1, 2);
|
||||
assert(!(`push` in channel));
|
||||
assert.deepEqual(await channel.values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
describe(`select`, function() {
|
||||
it(`miscellaneous`, async function() {
|
||||
const a = Channel<number>();
|
||||
const b = Channel<string>();
|
||||
|
||||
(async () => {
|
||||
await b.push('0');
|
||||
await a.push(1);
|
||||
await a.shift();
|
||||
})();
|
||||
|
||||
const result = await Channel.select([a.shift(), b.shift()]);
|
||||
assert.equal(result, b);
|
||||
assert.equal(result.value(), '0');
|
||||
assert.equal(b.value(), '0');
|
||||
assert.equal(await a.shift(), 1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`allows for non-blocking selects`, async function() {
|
||||
const a = Channel();
|
||||
const b = Channel();
|
||||
const closed = Channel.of();
|
||||
|
||||
switch (await Channel.select([a.shift(), b.push(0), closed.shift()])) {
|
||||
case a:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
case b:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
it(`cancel`, async function() {
|
||||
const channel = Channel();
|
||||
Channel.select([channel.push(`cancelled`)]).cancel();
|
||||
const closed = Channel.of();
|
||||
|
||||
assert.equal(
|
||||
await Channel.select([channel.shift(), closed.shift()]),
|
||||
closed
|
||||
);
|
||||
});
|
||||
|
||||
it(`complains when given non-channel method promises`, function() {
|
||||
return assertRejects(async () => {
|
||||
// @ts-expect-error
|
||||
await Channel.select([Promise.resolve()]);
|
||||
}, new TypeError(`Channel.select accepts only promises returned by push & shift.`));
|
||||
});
|
||||
|
||||
it(`complains if not given an array`, function() {
|
||||
return assertRejects(async () => {
|
||||
// @ts-expect-error
|
||||
await Channel.select(Channel.of(0).shift(), Channel.of(1).shift());
|
||||
}, new TypeError(`Channel.select: Argument must be an array.`));
|
||||
});
|
||||
});
|
||||
|
||||
describe(`functional interface`, async function() {
|
||||
it(`shift`, async function() {
|
||||
assert.equal(await Channel.shift(Channel.of(0)), 0);
|
||||
});
|
||||
|
||||
describe(`slice`, function() {
|
||||
|
||||
it(`full application`, async function() {
|
||||
const result = await Channel.slice(1, 4, Channel.of(0, 1, 2, 3, 4));
|
||||
assert.deepEqual(
|
||||
await Channel.slice(1, 4, Channel.of(0, 1, 2, 3, 4)).values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
|
||||
it(`single argument application`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.slice(1)(4)(Channel.of(0, 1, 2, 3, 4)).values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
|
||||
it(`double argument application`, async function() {
|
||||
assert.equal(await Channel.reduce(Math.max, 10, Channel.of(0, 1, 2)), 10);
|
||||
|
||||
assert.deepEqual(
|
||||
await Channel.slice(1, 4)(Channel.of(0, 1, 2, 3, 4)).values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Channel object`, function() {
|
||||
describe(`close`, function() {
|
||||
it(`can't close an already closed channel`, function() {
|
||||
const channel = Channel();
|
||||
channel.close();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.close();
|
||||
}, new Error(`Can't close an already-closed channel.`));
|
||||
});
|
||||
|
||||
it(`can't push to a closed channel`, async function() {
|
||||
const channel = Channel();
|
||||
channel.close();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.push(0);
|
||||
}, new Error(`Can't push to closed channel.`));
|
||||
});
|
||||
|
||||
it(`returns 'undefined' immediately from shift`, async function() {
|
||||
const channel = Channel();
|
||||
channel.close();
|
||||
assert.strictEqual(await channel.shift(), undefined);
|
||||
});
|
||||
|
||||
it(`Don't set 'lastValue' to 'undefined' when closing a channel with a cancelled shift.`, async function() {
|
||||
const channel = Channel();
|
||||
|
||||
// Set lastValue to 0.
|
||||
channel.push(0);
|
||||
await channel.shift();
|
||||
|
||||
channel.shift().cancel();
|
||||
channel.close();
|
||||
assert.strictEqual(channel.value(), 0);
|
||||
});
|
||||
});
|
||||
|
||||
it(`concat`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.concat(Channel.of(3, 4, 5), 6)
|
||||
.values(),
|
||||
[0, 1, 2, 3, 4, 5, 6]
|
||||
);
|
||||
});
|
||||
|
||||
it(`every`, async function() {
|
||||
const even = (number) => number % 2 === 0;
|
||||
assert(!(await Channel.of(0, 1, 2).every(even)));
|
||||
assert(await Channel.of(0, 2, 4).every(even));
|
||||
});
|
||||
|
||||
it(`filter`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2, 3, 4, 5)
|
||||
.filter((value) => value % 2 !== 0)
|
||||
.values(),
|
||||
[1, 3, 5]
|
||||
);
|
||||
});
|
||||
|
||||
it(`flat`, async function() {
|
||||
const nestedChannel = Channel.of(1, 2, Channel.of(3, 4))
|
||||
const flat1 = nestedChannel.flat();
|
||||
assert.deepEqual(await flat1.values(), [1, 2, 3, 4]);
|
||||
|
||||
const flat2 = Channel.of(1, 2, Channel.of(3, 4, Channel.of(5, 6))).flat();
|
||||
assert.equal(await flat2.shift(), 1);
|
||||
assert.equal(await flat2.shift(), 2);
|
||||
assert.equal(await flat2.shift(), 3);
|
||||
assert.equal(await flat2.shift(), 4);
|
||||
// @ts-expect-error: this works but is not type-safe
|
||||
assert.deepEqual(await (await flat2.shift()).values(), [5, 6]);
|
||||
|
||||
const flat3 = Channel.of(1, 2, Channel.of(3, 4, Channel.of(5, 6))).flat(2);
|
||||
assert.deepEqual(await flat3.values(), [1, 2, 3, 4, 5, 6]);
|
||||
});
|
||||
|
||||
it(`flatMap`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(1, 2, 3, 4)
|
||||
.flatMap((x) => Channel.of(x * 2))
|
||||
.values(),
|
||||
[2, 4, 6, 8]
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
await Channel.of(`it's Sunny in`, ``, `California`)
|
||||
.flatMap((x) => Channel.from(x.split(` `)))
|
||||
.values(),
|
||||
[`it's`, `Sunny`, `in`, ``, `California`]
|
||||
);
|
||||
});
|
||||
|
||||
it(`forEach`, async function() {
|
||||
const output = [];
|
||||
await Channel.of(0, 1, 2).forEach((value) => output.push(value));
|
||||
assert.deepEqual(output, [0, 1, 2]);
|
||||
});
|
||||
|
||||
it(`join`, async function() {
|
||||
assert.equal(await Channel.of(`a`, `b`, `c`).join(), `a,b,c`);
|
||||
});
|
||||
|
||||
it(`length`, function() {
|
||||
assert.equal(Channel(42).length, 42);
|
||||
});
|
||||
|
||||
it(`map`, async function() {
|
||||
const channel = Channel<string>(3);
|
||||
await channel.push(`a`);
|
||||
await channel.push(`b`);
|
||||
await channel.push(`c`);
|
||||
await channel.close();
|
||||
const mapped = channel.map((value) => value.toUpperCase());
|
||||
assert.deepEqual(await mapped.values(), [`A`, `B`, `C`]);
|
||||
});
|
||||
|
||||
describe(`push`, function() {
|
||||
it(`with shift`, async function() {
|
||||
const channel = Channel();
|
||||
(async () => {
|
||||
await channel.push(0);
|
||||
})();
|
||||
|
||||
assert.equal(await channel.shift(), 0);
|
||||
});
|
||||
|
||||
describe(`undefined`, function() {
|
||||
it(`outside select`, function() {
|
||||
const channel = Channel();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await channel.push(undefined);
|
||||
}, new TypeError(`Can't push 'undefined' to channel, use close instead.`));
|
||||
});
|
||||
|
||||
it(`inside select`, function() {
|
||||
const channel = Channel();
|
||||
|
||||
return assertRejects(async () => {
|
||||
await Channel.select([channel.push(undefined)]);
|
||||
}, new TypeError(`Can't push 'undefined' to channel, use close instead.`));
|
||||
});
|
||||
});
|
||||
|
||||
it(`disallows multiple values`, function() {
|
||||
const channel = Channel();
|
||||
|
||||
return assertRejects(async () => {
|
||||
// @ts-expect-error
|
||||
await channel.push(0, 1, 2);
|
||||
}, new Error(`Can't push more than one value at a time.`));
|
||||
});
|
||||
});
|
||||
|
||||
it(`readOnly`, async function() {
|
||||
const channel = Channel();
|
||||
const readOnly = channel.readOnly();
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
readOnly.close();
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
readOnly.push(0);
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
readOnly.writeOnly();
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await channel.push(1);
|
||||
})();
|
||||
|
||||
assert.equal(readOnly.readOnly(), readOnly);
|
||||
assert.equal(await readOnly.shift(), 1);
|
||||
assert.equal(readOnly.value(), 1);
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
readOnly.frozen = false;
|
||||
});
|
||||
});
|
||||
|
||||
describe(`reduce`, function() {
|
||||
it(`callbackfn only`, async function() {
|
||||
assert.equal(await Channel.of(0, 1, 2).reduce(Math.max), 2);
|
||||
});
|
||||
|
||||
it(`initialValue`, async function() {
|
||||
assert.equal(await Channel.of(0, 1, 2).reduce(Math.max, 10), 10);
|
||||
});
|
||||
|
||||
it(`no values without initialValue`, function() {
|
||||
return assertRejects(async () => {
|
||||
await Channel.of().reduce(Math.max);
|
||||
}, new TypeError(`No values in channel and initialValue wasn't provided.`));
|
||||
});
|
||||
});
|
||||
|
||||
describe(`shift`, function() {
|
||||
it(`with push`, async function() {
|
||||
const channel = Channel();
|
||||
(async () => {
|
||||
await channel.push(0);
|
||||
})();
|
||||
|
||||
assert.equal(await channel.shift(), 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`slice`, function() {
|
||||
it(`no start`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.slice()
|
||||
.values(),
|
||||
[0, 1, 2]
|
||||
);
|
||||
});
|
||||
|
||||
it(`start`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.slice(1)
|
||||
.values(),
|
||||
[1, 2]
|
||||
);
|
||||
});
|
||||
|
||||
it(`end`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2, 3, 4)
|
||||
.slice(1, 4)
|
||||
.values(),
|
||||
[1, 2, 3]
|
||||
);
|
||||
});
|
||||
|
||||
it(`starts after end of channel`, async function() {
|
||||
assert.deepEqual(
|
||||
await Channel.of(0, 1, 2)
|
||||
.slice(10)
|
||||
.values(),
|
||||
[]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`some`, async function() {
|
||||
const even = (value) => value % 2 === 0;
|
||||
const channel = Channel.of(0, 1, 2);
|
||||
assert(await channel.some(even));
|
||||
assert.deepEqual(await channel.values(), [1, 2]);
|
||||
assert(!(await Channel.of(1, 3, 5).some(even)));
|
||||
});
|
||||
|
||||
it(`toString`, function() {
|
||||
assert.equal(Channel(10).toString(), `Channel(10)`);
|
||||
});
|
||||
|
||||
it(`value`, async function() {
|
||||
const channel = Channel(1);
|
||||
await channel.push(null);
|
||||
await channel.shift();
|
||||
assert.equal(channel.value(), null);
|
||||
channel.close();
|
||||
await channel.shift();
|
||||
assert.equal(channel.value(), undefined);
|
||||
});
|
||||
|
||||
it(`values`, async function() {
|
||||
assert.deepEqual(await Channel.of(0, 1, 2).values(), [0, 1, 2]);
|
||||
});
|
||||
|
||||
describe(`writeOnly`, function() {
|
||||
it(`provides only write methods`, async function() {
|
||||
const channel = Channel();
|
||||
const writeOnly = channel.writeOnly();
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
writeOnly.readOnly();
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
writeOnly.shift();
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
assert.equal(writeOnly.value, undefined);
|
||||
|
||||
(async () => {
|
||||
await channel.shift();
|
||||
})();
|
||||
|
||||
await writeOnly.push(0);
|
||||
writeOnly.close();
|
||||
|
||||
assert.throws(() => {
|
||||
// @ts-expect-error
|
||||
writeOnly.frozen = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(`allows promises to be sent through a channel`, function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
process.once(`unhandledRejection`, reject);
|
||||
const channel = Channel();
|
||||
|
||||
(async () => {
|
||||
await channel.push(Promise.resolve(`resolved`));
|
||||
await channel.push(Promise.reject(new Error(`rejected`)));
|
||||
})();
|
||||
|
||||
assert.equal(await channel.shift(), `resolved`);
|
||||
|
||||
try {
|
||||
await channel.shift();
|
||||
} catch (exception) {
|
||||
assert.equal(exception.message, `rejected`);
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ES2015"]
|
||||
},
|
||||
"include": [
|
||||
"lib"
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue