diff --git a/whatwg-fetch/index.d.ts b/whatwg-fetch/index.d.ts index f17d88a7df..67bde53016 100644 --- a/whatwg-fetch/index.d.ts +++ b/whatwg-fetch/index.d.ts @@ -6,9 +6,9 @@ /// interface Window { - fetch(url: RequestInfo, init?: RequestInit): Promise; + fetch(input: RequestInfo, init?: RequestInit): Promise; } -declare var fetch: typeof window.fetch; +declare let fetch: typeof window.fetch; declare type HeadersInit = Headers | string[][] | { [key: string]: string }; declare class Headers { @@ -16,7 +16,7 @@ declare class Headers { append(name: string, value: string): void; delete(name: string): void; - get(name: string): string; // | null; (TS 2.0 strict null check) + get(name: string): string | null; has(name: string): boolean; set(name: string, value: string): void; @@ -29,32 +29,35 @@ declare class Headers { } declare type BodyInit = Blob | ArrayBufferView | ArrayBuffer | FormData /* | URLSearchParams */ | string; +declare type ResponseBodyInit = BodyInit | ReadableStream; interface Body { - bodyUsed: boolean; + readonly bodyUsed: boolean; arrayBuffer(): Promise; blob(): Promise; formData(): Promise; json(): Promise; + json(): Promise; text(): Promise; } declare type RequestInfo = Request | string; declare class Request { - constructor(input: RequestInfo, init?: RequestInit); - - method: string; - url: string; - headers: Headers; + constructor(input: RequestInfo, init?: RequestInit); - type: RequestType - destination: RequestDestination; - referrer: string; - referrerPolicy: ReferrerPolicy; - mode: RequestMode; - credentials: RequestCredentials; - cache: RequestCache; - redirect: RequestRedirect; - integrity: string; + readonly method: string; + readonly url: string; + readonly headers: Headers; + + readonly type: RequestType + readonly destination: RequestDestination; + readonly referrer: string; + readonly referrerPolicy: ReferrerPolicy; + readonly mode: RequestMode; + readonly credentials: RequestCredentials; + readonly cache: RequestCache; + readonly redirect: RequestRedirect; + readonly integrity: string; + readonly keepalive: boolean; clone(): Request; } @@ -70,7 +73,7 @@ interface RequestInit { cache?: RequestCache; redirect?: RequestRedirect; integrity?: string; - window?: any; + window?: null; // can only be set to null } type RequestType = "" | "audio" | "font" | "image" | "script" | "style" | "track" | "video"; @@ -79,27 +82,30 @@ type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors"; type RequestCredentials = "omit" | "same-origin" | "include"; type RequestCache = "default" | "no-store" | "reload" | "no-cache" | "force-cache" | "only-if-cached"; type RequestRedirect = "follow" | "error" | "manual"; + type ReferrerPolicy = "" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url"; declare class Response { - constructor(body?: BodyInit, init?: ResponseInit); + constructor(body?: ResponseBodyInit, init?: ResponseInit); static error(): Response; static redirect(url: string, status?: number): Response; - type: ResponseType; - url: string; - redirected: boolean; - status: number; - ok: boolean; - statusText: string; - headers: Headers; - body: ReadableStream; // | null; - trailer: Promise; + readonly type: ResponseType; + + readonly url: string; + readonly redirected: boolean; + readonly status: number; + readonly ok: boolean; + readonly statusText: string; + readonly headers: Headers; + readonly body: ReadableStream | null; + readonly trailer: Promise; clone(): Response; } interface Response extends Body {} + interface ResponseInit { status?: number; statusText?: string; diff --git a/whatwg-fetch/tsconfig.json b/whatwg-fetch/tsconfig.json index 2333d8dfc0..4e8f7e915e 100644 --- a/whatwg-fetch/tsconfig.json +++ b/whatwg-fetch/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es6", "noImplicitAny": true, - "strictNullChecks": false, + "strictNullChecks": true, "baseUrl": "../", "typeRoots": [ "../" diff --git a/whatwg-fetch/whatwg-fetch-tests.ts b/whatwg-fetch/whatwg-fetch-tests.ts index aa6b90b8c8..39a4cf9d2b 100644 --- a/whatwg-fetch/whatwg-fetch-tests.ts +++ b/whatwg-fetch/whatwg-fetch-tests.ts @@ -68,4 +68,43 @@ function handlePromise(promise: Promise) { }).then((text) => { console.log(text); }); -} \ No newline at end of file +} + +function test_Body_json() { + interface FooBar { + foo: string; + bar: string; + } + + fetch('http://test.com') + .then(response => response.json()) + .then(fooBar => { + console.log(fooBar.foo); + console.log(fooBar.bar); + }); + + fetch('http://test.com') + .then(response => > response.json()) + .then(fooBar => { + console.log(fooBar.foo); + console.log(fooBar.bar); + }); + + fetch('http://test.com') + .then(response => response.json() as Promise) + .then(fooBar => { + console.log(fooBar.foo); + console.log(fooBar.bar); + }); + + fetch('http://test.com') + .then(response => response.json()) + .then(fooBar => { + // fooBar is of type any, not of type FooBar + + // FIXME Was behaving properly with TypeScript 2.0.10, not anymore with 2.1.4 + // See Wrong type with Promise chaining and 2.1.4 https://github.com/Microsoft/TypeScript/issues/12409 + //console.log(fooBar.foo); + //console.log(fooBar.bar); + }); +} diff --git a/whatwg-streams/index.d.ts b/whatwg-streams/index.d.ts index 757189b190..4f1e26ee7c 100644 --- a/whatwg-streams/index.d.ts +++ b/whatwg-streams/index.d.ts @@ -15,11 +15,18 @@ interface ReadableByteStreamSource { cancel?(reason: string): void | Promise; type: "bytes"; + autoAllocateChunkSize?: number; } interface QueuingStrategy { - highWaterMark?: number; size?(chunk: ArrayBufferView): number; + highWaterMark?: number; +} + +interface PipeOptions { + preventClose?: boolean; + preventAbort?: boolean; + preventCancel?: boolean; } declare class ReadableStream { @@ -31,8 +38,8 @@ declare class ReadableStream { cancel(reason: string): Promise; getReader(): ReadableStreamDefaultReader; getReader({ mode }: { mode: "byob" }): ReadableStreamBYOBReader; - pipeThrough({ writable, readable }: { writable: WritableStream, readable: T }): T; - pipeTo(dest: WritableStream, { preventClose, preventAbort, preventCancel }: { preventClose?: boolean, preventAbort?: boolean, preventCancel?: boolean }): Promise; + pipeThrough({ writable, readable }: { writable: WritableStream, readable: T }, options?: PipeOptions): T; + pipeTo(dest: WritableStream, options?: PipeOptions): Promise; tee(): [ReadableStream, ReadableStream]; } @@ -49,7 +56,7 @@ declare class ReadableStreamDefaultReader { declare class ReadableStreamBYOBReader { constructor(stream: ReadableStream); - closed: Promise; + closed: Promise; cancel(reason: string): Promise; read(view: ArrayBufferView): Promise>; @@ -88,8 +95,8 @@ declare class ReadableStreamBYOBRequest { interface WritableStreamSink { start?(controller: WritableStreamDefaultController): void | Promise; - write?(chunk: any): void | Promise; - close?(): void | Promise; + write?(chunk: any, controller?: WritableStreamDefaultController): void | Promise; + close?(controller: WritableStreamDefaultController): void | Promise; abort?(reason: string): void | Promise; } @@ -106,7 +113,7 @@ declare class WritableStreamDefaultWriter { constructor(stream: WritableStream); closed: Promise; - desiredSize: number; + desiredSize: number | null; ready: Promise; abort(reason: string): Promise; @@ -124,11 +131,11 @@ declare class WritableStreamDefaultController { declare class ByteLengthQueuingStrategy { constructor({ highWaterMark }: { highWaterMark: number }); - size(chunk: ArrayBufferView): number; + size(chunk: ArrayBufferView): number | undefined; } declare class CountQueuingStrategy { constructor({ highWaterMark }: { highWaterMark: number }); - size(): number; // 1; -} \ No newline at end of file + size(): 1; +} diff --git a/whatwg-streams/tsconfig.json b/whatwg-streams/tsconfig.json index 9c6a6ad155..318d69cb04 100644 --- a/whatwg-streams/tsconfig.json +++ b/whatwg-streams/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es6", "noImplicitAny": true, - "strictNullChecks": false, + "strictNullChecks": true, "baseUrl": "../", "typeRoots": [ "../" diff --git a/whatwg-streams/whatwg-streams-tests.ts b/whatwg-streams/whatwg-streams-tests.ts index ce5eb2b256..b1735b754f 100644 --- a/whatwg-streams/whatwg-streams-tests.ts +++ b/whatwg-streams/whatwg-streams-tests.ts @@ -1,20 +1,39 @@ -function makeReadableWebSocketStream(url: string, protocols: string[]) { - const ws = new WebSocket(url, protocols); - ws.binaryType = "arraybuffer"; +/// - return new ReadableStream({ - start(controller) { - ws.onmessage = event => controller.enqueue(event.data); - ws.onclose = () => controller.close(); - ws.onerror = () => controller.error(new Error("The WebSocket errored!")); - }, +// Examples taken from https://streams.spec.whatwg.org/#creating-examples - cancel() { - ws.close(); - } - }); +// 8.1. A readable stream with an underlying push source (no backpressure support) + +{ + function makeReadableWebSocketStream(url: string, protocols: string | string[]) { + const ws = new WebSocket(url, protocols); + ws.binaryType = "arraybuffer"; + + return new ReadableStream({ + start(controller) { + ws.onmessage = event => controller.enqueue(event.data); + ws.onclose = () => controller.close(); + ws.onerror = () => controller.error(new Error("The WebSocket errored!")); + }, + + cancel() { + ws.close(); + } + }); + } + + const writableStream = new WritableStream(); + + const webSocketStream = makeReadableWebSocketStream("wss://example.com:443/", "protocol"); + + webSocketStream.pipeTo(writableStream) + .then(() => console.log("All data successfully written!")) + .catch(e => console.error("Something went wrong!", e)); } + +// 8.2. A readable stream with an underlying push source and backpressure support + function makeReadableBackpressureSocketStream(host: string, port: number) { const socket = createBackpressureSocket(host, port); @@ -49,6 +68,9 @@ function makeReadableBackpressureSocketStream(host: string, port: number) { function createBackpressureSocket(host: string, port: number): any { }; } + +// 8.3. A readable byte stream with an underlying push source (no backpressure support) + const DEFAULT_CHUNK_SIZE = 65536; function makeUDPSocketStream(host: string, port: number) { @@ -93,7 +115,100 @@ function makeUDPSocketStream(host: string, port: number) { function createUDPSocket(host: string, port: number): any { }; } -function makeWritableWebSocketStream(url: string, protocols: string[]) { + +// 8.4. A readable stream with an underlying pull source + +//const fs = require("pr/fs"); // https://github.com/jden/pr +interface fs { + open(path: string | Buffer, flags: string | number): Promise; + read(fd: number, buffer: Buffer, offset: number, length: number, position: number): Promise; + write(fd: number, buffer: Buffer, offset: number, length: number): Promise; + close(fd: number): Promise; +} +let fs: fs; + +{ + const CHUNK_SIZE = 1024; + + function makeReadableFileStream(filename: string) { + let fd: number; + let position = 0; + + return new ReadableStream({ + start() { + return fs.open(filename, "r").then(result => { + fd = result; + }); + }, + + pull(controller) { + const buffer = new ArrayBuffer(CHUNK_SIZE); + + return fs.read(fd, buffer, 0, CHUNK_SIZE, position).then(bytesRead => { + if (bytesRead === 0) { + return fs.close(fd).then(() => controller.close()); + } else { + position += bytesRead; + controller.enqueue(new Uint8Array(buffer, 0, bytesRead)); + } + }); + }, + + cancel() { + return fs.close(fd); + } + }); + } +} + + +// 8.5. A readable byte stream with an underlying pull source + +{ + //const fs = require("pr/fs"); // https://github.com/jden/pr + const DEFAULT_CHUNK_SIZE = 1024; + + function makeReadableByteFileStream(filename: string) { + let fd: number; + let position = 0; + + return new ReadableStream({ + type: "bytes", + + start() { + return fs.open(filename, "r").then(result => { + fd = result; + }); + }, + + pull(controller) { + // Even when the consumer is using the default reader, the auto-allocation + // feature allocates a buffer and passes it to us via byobRequest. + const v = controller.byobRequest.view; + + return fs.read(fd, v.buffer, v.byteOffset, v.byteLength, position).then(bytesRead => { + if (bytesRead === 0) { + return fs.close(fd).then(() => controller.close()); + } else { + position += bytesRead; + controller.byobRequest.respond(bytesRead); + } + }); + }, + + cancel() { + return fs.close(fd); + }, + + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE + }); + } +} + + +// 8.6. A writable stream with no backpressure or success signals + +function makeWritableWebSocketStream(url: string, protocols: string | string[]) { const ws = new WebSocket(url, protocols); return new WritableStream({ @@ -117,71 +232,123 @@ function makeWritableWebSocketStream(url: string, protocols: string[]) { }); } -function streamifyWebSocket(url: string, protocol: string) { - const ws = new WebSocket(url, protocol); - ws.binaryType = "arraybuffer"; +{ + const readableStream = new ReadableStream(); - return { - readable: new ReadableStream(new WebSocketSource(ws)), - writable: new WritableStream(new WebSocketSink(ws)) - }; + const webSocketStream = makeWritableWebSocketStream("wss://example.com:443/", "protocol"); + + readableStream.pipeTo(webSocketStream) + .then(() => console.log("All data successfully written!")) + .catch(e => console.error("Something went wrong!", e)); } -class WebSocketSource implements ReadableStreamSource { - private _ws: WebSocket - constructor(ws: WebSocket) { - this._ws = ws; - } +// 8.7. A writable stream with backpressure and success signals - start(controller: ReadableStreamDefaultController) { - this._ws.onmessage = event => controller.enqueue(event.data); - this._ws.onclose = () => controller.close(); +{ + //const fs = require("pr/fs"); // https://github.com/jden/pr - this._ws.addEventListener("error", () => { - controller.error(new Error("The WebSocket errored!")); + function makeWritableFileStream(filename: string) { + let fd: number; + + return new WritableStream({ + start() { + return fs.open(filename, "w").then(result => { + fd = result; + }); + }, + + write(chunk) { + return fs.write(fd, chunk, 0, chunk.length); + }, + + close() { + return fs.close(fd); + } }); } - cancel() { - this._ws.close(); - } + const fileStream = makeWritableFileStream("/example/path/on/fs.txt"); + const writer = fileStream.getWriter(); + + writer.write("To stream, or not to stream\n"); + writer.write("That is the question\n"); + + writer.close() + .then(() => console.log("chunks written and stream closed successfully!")) + .catch(e => console.error(e)); } -class WebSocketSink implements WritableStreamSink { - private _ws: WebSocket - constructor(ws: WebSocket) { - this._ws = ws; +// 8.8. A { readable, writable } stream pair wrapping the same underlying resource + +{ + function streamifyWebSocket(url: string, protocol: string) { + const ws = new WebSocket(url, protocol); + ws.binaryType = "arraybuffer"; + + return { + readable: new ReadableStream(new WebSocketSource(ws)), + writable: new WritableStream(new WebSocketSink(ws)) + }; } - start(controller: WritableStreamDefaultController) { - this._ws.addEventListener("error", () => { - controller.error(new Error("The WebSocket errored!")); - }); + class WebSocketSource implements ReadableStreamSource { + private _ws: WebSocket; - return new Promise(resolve => this._ws.onopen = () => resolve()); - } + constructor(ws: WebSocket) { + this._ws = ws; + } - write(chunk: any) { - this._ws.send(chunk); - } + start(controller: ReadableStreamDefaultController) { + this._ws.onmessage = event => controller.enqueue(event.data); + this._ws.onclose = () => controller.close(); - close() { - return new Promise((resolve, reject) => { - this._ws.onclose = () => resolve(); + this._ws.addEventListener("error", () => { + controller.error(new Error("The WebSocket errored!")); + }); + } + + cancel() { this._ws.close(); - }); + } } + + class WebSocketSink implements WritableStreamSink { + private _ws: WebSocket; + + constructor(ws: WebSocket) { + this._ws = ws; + } + + start(controller: WritableStreamDefaultController) { + this._ws.addEventListener("error", () => { + controller.error(new Error("The WebSocket errored!")); + }); + + return new Promise(resolve => this._ws.onopen = () => resolve()); + } + + write(chunk: any) { + this._ws.send(chunk); + } + + close() { + return new Promise((resolve, reject) => { + this._ws.onclose = () => resolve(); + this._ws.close(); + }); + } + } + + const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol"); + const writer = streamyWS.writable.getWriter(); + const reader = streamyWS.readable.getReader(); + + writer.write("Hello"); + writer.write("web socket!"); + + reader.read().then(({ value, done }) => { + console.log("The web socket says: ", value); + }); } - -const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol"); -const writer = streamyWS.writable.getWriter(); -const reader = streamyWS.readable.getReader(); - -writer.write("Hello"); -writer.write("web socket!"); - -reader.read().then(({ value, done }) => { - console.log("The web socket says: ", value); -}); \ No newline at end of file