diff --git a/types/k6/experimental/streams.d.ts b/types/k6/experimental/streams.d.ts new file mode 100644 index 00000000000000..32811764640462 --- /dev/null +++ b/types/k6/experimental/streams.d.ts @@ -0,0 +1,198 @@ +/** + * This module provides an experimental implementation of the Streams API + * for k6. + * + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/ + */ + +/** + * The ReadableStream object provides the API for creating and managing readable + * streams of raw data, bit by bit, as soon as it is available, without needing to generate a buffer, string, or blob. + */ +export class ReadableStream { + /** + * The ReadableStream constructor returns a newly created ReadableStream object. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestream/ + * + * @param underlyingSource - defines the source of data for the stream. + * @param queuingStrategy - defines the queuing strategy to adopt for the stream. + */ + constructor(underlyingSource?: UnderlyingSource, queuingStrategy?: QueuingStrategy); + + /** + * Closes the stream and signals a reason for the closure. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestream/cancel/ + * + * Used when you’ve completely finished with the stream and don’t need any more data from it, + * even if chunks are enqueued waiting to be read. + * + * That data is lost after cancel is called, and the stream is not readable anymore. + * To close the stream without getting rid of enqueued chunks, use `ReadableStreamDefaultController.close()`. + * + * It returns a promise that is resolved with `undefined` when the stream is canceled. + * + * @param reason - the reason for canceling the stream. + */ + cancel(reason: any): Promise; + + /** + * Creates a reader and locks the stream to it. + * While the stream is locked, no other reader can be acquired until this one is released. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestream/getreader/ + */ + getReader(): ReadableStreamDefaultReader; +} + +/** + * The object that defines the source of data for the stream. + */ +export interface UnderlyingSource { + /** + * Called when the stream is created. + * It can be used to perform any setup tasks. + * The content of this method is to be defined by the user. + */ + start?: (controller: ReadableStreamDefaultController) => void | Promise; + + /** + * Called repeatedly to fetch and queue data into the stream, + * until it reaches its high watermark. + * If `pull()` returns a promise, it won’t be called again until the promise is resolved. + */ + pull?: (controller: ReadableStreamDefaultController) => void | Promise; + + /** + * Called when the stream is canceled. + * It can be used to release access to the stream source and perform any cleanup tasks. + * The reason parameter passed to this method is an optional human-readable value + * that represents the reason for canceling the stream. + */ + cancel?: (reason?: any) => void | Promise; + + /** + * Specifies the type of the underlying source. + * It can currently only receive the value 'default' which is its default value. + */ + type?: "default"; +} + +/** + * The object that allows the user to control a ReadableStream’s state and internal queue. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestreamdefaultcontroller/ + */ +export interface ReadableStreamDefaultController { + /** + * Closes the associated stream. + * Readers can still read any previously enqueued chunks from the stream. + * Once those chunks are read, the stream closes, and no more data is available. + */ + close: () => void; + + /** + * Enqueues a chunk of data into the associated stream. + */ + enqueue: (chunk: any) => void; + + /** + * Makes any future interactions with the associated stream to error. + */ + error: (reason: any) => void; +} + +/** + * The object that defines the queuing strategy to adopt for the stream. + * + * Although the user can define a custom queueing strategy, the default behavior and recommended way + * to use the `ReadableStream` is to use a `CountQueuingStrategy` object. + */ +export interface QueuingStrategy { + /** + * Represents the maximum number of chunks that the stream can hold in its internal queue. + * The default value is 1. + */ + highWaterMark?: number; + + /** + * Returns the size of the chunk passed as an argument. + * The default value is a function that returns 1. + */ + size?(chunk: any): number; +} + +/** + * The CountQueuingStrategy object is the default QueuingStrategy for ReadableStream. + * It counts the number of chunks in the queue. + */ +export class CountQueuingStrategy { + /** + * The CountQueuingStrategy constructor returns a newly created CountQueuingStrategy object. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestream/countqueuingstrategy/ + * + * @param options - an object with optional property `highWaterMark` that determines + * the maximum number of chunks that the queue can contain. + */ + constructor(options?: { highWaterMark?: number }); + + /** + * Represents the maximum number of chunks that the stream can hold in its internal queue. + * The default value is 1. + */ + readonly highWaterMark: number; + + /** + * Returns the size of the chunk passed as an argument. + * The default value is a function that returns 1. + */ + size(chunk: any): number; +} + +/** + * The object used to read stream data. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestreamdefaultreader/ + */ +export class ReadableStreamDefaultReader { + /** + * The ReadableStreamDefaultReader constructor returns a newly created ReadableStreamDefaultReader object. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestreamdefaultreader/ + * + * @param stream - defines the stream the reader will own. + */ + constructor(stream: ReadableStream); + + /** + * Closes the reader and signals a reason for the closure. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestreamdefaultreader/cancel/ + * + * Used when you’ve completely finished with the stream and don’t need any more data from it, + * even if chunks are enqueued waiting to be read. + * That data is lost after cancel is called, and the stream is not readable anymore. + * To close the stream without getting rid of enqueued chunks, use `ReadableStreamDefaultController.close()`. + * + * It returns a promise that is resolved with `undefined` when the stream is canceled. + * + * @param reason - the reason for canceling the stream. + */ + cancel(reason: any): Promise; + + /** + * Returns a promise providing access to the next chunk in the stream’s internal queue. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestreamdefaultreader/read/ + * + * - If the stream is closed and no more data is available, the promise resolves with an object of the form: + * `{ done: true, value: undefined }`. + * - If the stream is errored, the promise rejects with the error that caused the stream to error. + */ + read(): Promise<{ done: false; value: any } | { done: true; value: undefined }>; + + /** + * Releases the reader’s lock on the stream. + * https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/streams/readablestreamdefaultreader/releaselock/ + * + * If the associated stream is errored as the lock is released, the reader will be errored as well. + * This method is useful when done with the stream and want to release the lock on it. + * + * If the reader’s lock is released as pending read operations are still in progress, + * the reader’s `ReadableStreamDefaultReader.read()` calls are immediately rejected with a `TypeError`. + */ + readonly releaseLock: () => void; +} diff --git a/types/k6/experimental/websockets.d.ts b/types/k6/experimental/websockets.d.ts index d18f1a30fe253b..125df5823e8463 100644 --- a/types/k6/experimental/websockets.d.ts +++ b/types/k6/experimental/websockets.d.ts @@ -1,4 +1,5 @@ import { CookieJar } from "../http"; +import { ReadableStream } from "./streams"; /** * This module provides an experimental implementation of the WebSocket API @@ -139,6 +140,7 @@ export class Blob { arrayBuffer(): Promise; bytes(): Promise; slice(start?: number, end?: number): Blob; + stream(): ReadableStream; text(): Promise; } diff --git a/types/k6/test/experimental/streams.ts b/types/k6/test/experimental/streams.ts new file mode 100644 index 00000000000000..67fff6efeb3e85 --- /dev/null +++ b/types/k6/test/experimental/streams.ts @@ -0,0 +1,117 @@ +import { CountQueuingStrategy, ReadableStream, ReadableStreamDefaultReader } from "k6/experimental/streams"; + +// +// ReadableStream constructor +// +new ReadableStream(); +new ReadableStream({}, {}); +new ReadableStream({}, new CountQueuingStrategy()); +new ReadableStream({ + start: _ => { + }, + pull: _ => { + }, + cancel: _ => { + }, + type: "default", +}, {}); +new ReadableStream({ + start: _ => { + }, + pull: _ => { + }, + cancel: _ => { + }, + type: "default", +}, { + highWaterMark: 5, + size: _ => { + return 1; + }, +}); +new ReadableStream({ + // @ts-expect-error + type: "byob", +}, {}); +new ReadableStream({}, { + // @ts-expect-error + size: _ => { + }, +}); + +const rs = new ReadableStream(); + +// +// ReadableStream.cancel +// +// @ts-expect-error +rs.cancel(); +rs.cancel("reason"); // $ExpectType Promise; +// @ts-expect-error +rs.cancel = () => {}; + +// +// ReadableStream.getReader +// +rs.getReader(); // $ExpectType ReadableStreamDefaultReader; +// @ts-expect-error +rs.getReader = () => {}; + +// +// ReadableStreamDefaultReader constructor +// +// @ts-expect-error +new ReadableStreamDefaultReader(); +const reader = new ReadableStreamDefaultReader(new ReadableStream()); + +// +// ReadableStreamDefaultReader.cancel +// +// @ts-expect-error +reader.cancel(); +reader.cancel("reason"); // $ExpectType Promise; +// @ts-expect-error +reader.cancel = () => {}; + +// +// ReadableStreamDefaultReader.read +// +reader.read(); // $ExpectType Promise<{ done: false; value: any; } | { done: true; value: undefined; }>; +// @ts-expect-error +reader.read = () => {}; + +// +// ReadableStreamDefaultReader.releaseLock +// +reader.releaseLock(); // $ExpectType void; +// @ts-expect-error +reader.releaseLock = () => {}; + +// +// CountQueuingStrategy constructor +// +const cqs = new CountQueuingStrategy(); +new CountQueuingStrategy({}); +new CountQueuingStrategy({ highWaterMark: 5 }); +new CountQueuingStrategy({ + highWaterMark: 5, + // @ts-expect-error + size: _ => { + return 1; + }, +}); + +// +// CountQueuingStrategy.highWaterMark +// +cqs.highWaterMark; // $ExpectType number +// @ts-expect-error +cqs.highWaterMark = 5; + +// +// CountQueuingStrategy.size +// @ts-expect-error +cqs.size(); +cqs.size("chunk"); // $ExpectType number; +// @ts-expect-error +cqs.size = () => {}; diff --git a/types/k6/test/websockets.ts b/types/k6/test/websockets.ts index 8c89e72f9961ec..d3ea87ce35db8b 100644 --- a/types/k6/test/websockets.ts +++ b/types/k6/test/websockets.ts @@ -52,6 +52,7 @@ const blob = new Blob(["something"]); await blob.text(); // $ExpectType string await blob.arrayBuffer(); // $ExpectType ArrayBuffer await blob.bytes(); // $ExpectType Uint8Array + blob.stream(); // $ExpectType ReadableStream blob.slice(10, 2); // $ExpectType Blob // @ts-expect-error blob.slice(10, 2, "string"); diff --git a/types/k6/tsconfig.json b/types/k6/tsconfig.json index 190f22614df20e..52da087e4180a4 100644 --- a/types/k6/tsconfig.json +++ b/types/k6/tsconfig.json @@ -35,6 +35,7 @@ "test/websockets.ts", "test/experimental/browser.ts", "test/experimental/fs.ts", + "test/experimental/streams.ts", "test/ws.ts" ] }