Skip to content

Commit

Permalink
TypeScript: fix definitions to have ReadableStream compatible with …
Browse files Browse the repository at this point in the history
…`WebStream`, add tests (#13)

Update `WebStream` definition so that type inference works when using a
`ReadableStream`. Specifically, we cannot rely on the `AsyncIterable`
interface, because ReadableStreams are not currently implemented by all
browsers as including the necessary properties.
  • Loading branch information
larabr authored Feb 28, 2022
1 parent 3283ac9 commit fb1ae49
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 18 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/test-type-definitions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Types

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
lint:
name: Test type definitions

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '15'
- run: npm ci
- run: npm run test-type-definitions
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
test/typescript-definitions.js
.DS_Store
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@
"web-streams-polyfill": "~3.0.3"
},
"devDependencies": {
"@openpgp/jsdoc": "^3.6.4"
"@openpgp/jsdoc": "^3.6.4",
"@types/node": "^17.0.18",
"esm": "^3.2.25",
"typescript": "^4.5.5"
},
"scripts": {
"test-type-definitions": "tsc test/typescript-definitions.ts && node --require esm test/typescript-definitions.js",
"test": "npm run test-type-definitions",
"docs": "jsdoc --configure .jsdocrc.js --destination docs --readme README.md lib",
"preversion": "rm -rf docs",
"version": "npm run docs && git add -A docs",
Expand Down
22 changes: 22 additions & 0 deletions test/typescript-definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as assert from 'assert';
import { Readable as NodeReadableStream } from 'stream';
import { ReadableStream as WebReadableStream } from 'web-streams-polyfill';
import { WebStream, NodeStream, readToEnd } from '../';

(async () => {

const nodeStream: NodeStream<string> = new NodeReadableStream();
assert(nodeStream instanceof NodeReadableStream);
// @ts-expect-error detect type parameter mismatch
const webStream: WebStream<string> = new ReadableStream<Uint8Array>();
assert(webStream instanceof WebReadableStream);

await readToEnd(new Uint8Array([1])) as Uint8Array;
await readToEnd(new Uint8Array([1]), _ => _) as Uint8Array[];

console.log('TypeScript definitions are correct');
})().catch(e => {
console.error('TypeScript definitions tests failed by throwing the following error');
console.error(e);
process.exit(1);
});
48 changes: 31 additions & 17 deletions web-stream-tools.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,39 @@
// Contributors:
// - Flowcrypt a.s. <[email protected]>

declare module "@openpgp/web-stream-tools" {
type Data = Uint8Array | string;

interface BaseStream<T extends Data> extends AsyncIterable<T> { }
export namespace ReadableStreamInternals {
// copied+simplified version of ReadableStream from lib.dom.d.ts, needed to enforce type checks in WebStream below
interface ReadableStreamDefaultReadDoneResult {
done: true;
value?: undefined;
}
interface ReadableStreamDefaultReadValueResult<T> {
done: false;
value: T;
}
interface ReadableStreamDefaultReader<R = any> {
readonly closed: Promise<undefined>;
cancel(reason?: any): Promise<void>;
read(): Promise<ReadableStreamDefaultReadValueResult<R> | ReadableStreamDefaultReadDoneResult>;
releaseLock(): void;
}
}

interface WebStream<T extends Data> extends BaseStream<T> { // copied+simplified version of ReadableStream from lib.dom.d.ts
readonly locked: boolean; getReader: Function; pipeThrough: Function; pipeTo: Function; tee: Function;
cancel(reason?: any): Promise<void>;
}
type Data = Uint8Array | string;

interface NodeStream<T extends Data> extends BaseStream<T> { // copied+simplified version of ReadableStream from @types/node/index.d.ts
readable: boolean; pipe: Function; unpipe: Function; wrap: Function;
read(size?: number): string | Uint8Array; setEncoding(encoding: string): this; pause(): this; resume(): this;
isPaused(): boolean; unshift(chunk: string | Uint8Array): void;
}
interface WebStream<T extends Data> { // copied+simplified version of ReadableStream from lib.dom.d.ts
readonly locked: boolean; pipeThrough: Function; pipeTo: Function; tee: Function;
cancel(reason?: any): Promise<void>;
getReader(): ReadableStreamInternals.ReadableStreamDefaultReader<T>;
}

type Stream<T extends Data> = WebStream<T> | NodeStream<T>;
interface NodeStream<T extends Data> extends AsyncIterable<T> { // copied+simplified version of ReadableStream from @types/node/index.d.ts
readable: boolean; pipe: Function; unpipe: Function; wrap: Function; setEncoding(encoding: string): this; pause(): this; resume(): this;
isPaused(): boolean; unshift(chunk: string | Uint8Array): void;
read(size?: number): T;
}

type MaybeStream<T extends Data> = T | Stream<T>;
type Stream<T extends Data> = WebStream<T> | NodeStream<T>;
type MaybeStream<T extends Data> = T | Stream<T>;

export function readToEnd<T extends Data>(input: MaybeStream<T>, concat?: (list: T[]) => T): Promise<T>;
}
export function readToEnd<T extends Data, R extends any = T>(input: MaybeStream<T>, join?: (chunks: T[]) => R): Promise<R>;

0 comments on commit fb1ae49

Please sign in to comment.