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

Update to spec version of 13 November 2023 #134

Merged
merged 17 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"no-param-reassign": "off",
"no-process-env": "error",
"no-proto": "error",
"no-redeclare": "error",
"no-redeclare": "off",
"no-return-assign": ["error", "except-parens"],
"no-script-url": "off",
"no-self-assign": "error",
Expand Down Expand Up @@ -269,6 +269,7 @@
}],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-redeclare": "error",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-useless-constructor": "error"
}
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
* This allows TypeScript users to use new methods such as `ReadableStream.prototype[Symbol.asyncIterator]()`,
even when TypeScript doesn't yet have a built-in type definition for them.
* 💥 The type definitions now require TypeScript 3.5 or higher. ([#130](https://github.com/MattiasBuelens/web-streams-polyfill/pull/130))
* 👓 Align with [spec version `4dc123a`](https://github.com/whatwg/streams/tree/4dc123a6e7f7ba89a8c6a7975b021156f39cab52/) ([#115](https://github.com/MattiasBuelens/web-streams-polyfill/issues/115), [#134](https://github.com/MattiasBuelens/web-streams-polyfill/pull/134))
* Added `ReadableStream.from(asyncIterable)`, which creates a `ReadableStream` wrapping the given iterable or async iterable.
* Added `Transformer.cancel` method, which is called when the readable side of a `TransformStream` is cancelled or when its writable side is aborted.
* Added `min` option to `ReadableStreamBYOBReader.read(view, options)`.
* Added support for `AbortSignal.reason` when aborting a pipe.
* 🐛 Prevent [warnings from Bluebird](http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-created-in-a-handler-but-was-not-returned-from-it) about a promise being created within a handler but not being returned from a handler. ([#131](https://github.com/MattiasBuelens/web-streams-polyfill/pull/131))
* 🏠 Improve internal `DOMException` polyfill. ([#133](https://github.com/MattiasBuelens/web-streams-polyfill/pull/133))

Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The `polyfill/es2018` and `ponyfill/es2018` variants work in any ES2018-compatib

## Compliance

The polyfill implements [version `4b6b93c` (25 Oct 2021)][spec-snapshot] of the streams specification.
The polyfill implements [version `4dc123a` (13 Nov 2023)][spec-snapshot] of the streams specification.

The polyfill is tested against the same [web platform tests][wpt] that are used by browsers to test their native implementations.
The polyfill aims to pass all tests, although it allows some exceptions for practical reasons:
Expand Down Expand Up @@ -103,12 +103,12 @@ Thanks to these people for their work on [the original polyfill][creatorrr-polyf
[rs-asynciterator]: https://streams.spec.whatwg.org/#rs-asynciterator
[ws-controller-signal]: https://streams.spec.whatwg.org/#ws-default-controller-signal
[abortcontroller-polyfill]: https://www.npmjs.com/package/abortcontroller-polyfill
[spec-snapshot]: https://streams.spec.whatwg.org/commit-snapshots/4b6b93c69e531e2fe45a6ed4cb1484a7ba4eb8bb/
[wpt]: https://github.com/web-platform-tests/wpt/tree/96ca25f0f7526282c0d47e6bf6a7edd439da1968/streams
[wpt-bad-buffers]: https://github.com/web-platform-tests/wpt/blob/96ca25f0f7526282c0d47e6bf6a7edd439da1968/streams/readable-byte-streams/bad-buffers-and-views.any.js
[spec-snapshot]: https://streams.spec.whatwg.org/commit-snapshots/4dc123a6e7f7ba89a8c6a7975b021156f39cab52/
[wpt]: https://github.com/web-platform-tests/wpt/tree/2a298b616b7c865917d7198a287310881cbfdd8d/streams
[wpt-bad-buffers]: https://github.com/web-platform-tests/wpt/blob/2a298b616b7c865917d7198a287310881cbfdd8d/streams/readable-byte-streams/bad-buffers-and-views.any.js
[proposal-arraybuffer-transfer]: https://github.com/domenic/proposal-arraybuffer-transfer
[ref-impl-transferarraybuffer]: https://github.com/whatwg/streams/blob/4b6b93c69e531e2fe45a6ed4cb1484a7ba4eb8bb/reference-implementation/lib/abstract-ops/ecmascript.js#L16
[ref-impl-transferarraybuffer]: https://github.com/whatwg/streams/blob/4dc123a6e7f7ba89a8c6a7975b021156f39cab52/reference-implementation/lib/abstract-ops/ecmascript.js#L18
[issue-3]: https://github.com/MattiasBuelens/web-streams-polyfill/issues/3
[wpt-async-iterator-prototype]: https://github.com/web-platform-tests/wpt/blob/96ca25f0f7526282c0d47e6bf6a7edd439da1968/streams/readable-streams/async-iterator.any.js#L24
[wpt-async-iterator-prototype]: https://github.com/web-platform-tests/wpt/blob/2a298b616b7c865917d7198a287310881cbfdd8d/streams/readable-streams/async-iterator.any.js#L24
[stub-async-iterator-prototype]: https://github.com/MattiasBuelens/web-streams-polyfill/blob/v2.0.0/src/target/es5/stub/async-iterator-prototype.ts
[creatorrr-polyfill]: https://github.com/creatorrr/web-streams-polyfill
14 changes: 13 additions & 1 deletion etc/web-streams-polyfill.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
export interface AbortSignal {
readonly aborted: boolean;
addEventListener(type: 'abort', listener: () => void): void;
readonly reason?: any;
removeEventListener(type: 'abort', listener: () => void): void;
}

Expand Down Expand Up @@ -59,6 +60,7 @@ export class ReadableStream<R = any> {
});
constructor(underlyingSource?: UnderlyingSource<R>, strategy?: QueuingStrategy<R>);
cancel(reason?: any): Promise<void>;
static from<R>(asyncIterable: Iterable<R> | AsyncIterable<R>): ReadableStream<R>;
getReader({ mode }: {
mode: 'byob';
}): ReadableStreamBYOBReader;
Expand Down Expand Up @@ -86,10 +88,16 @@ export class ReadableStreamBYOBReader {
constructor(stream: ReadableStream<Uint8Array>);
cancel(reason?: any): Promise<void>;
get closed(): Promise<undefined>;
read<T extends ArrayBufferView>(view: T): Promise<ReadableStreamBYOBReadResult<T>>;
read<T extends ArrayBufferView>(view: T, options?: ReadableStreamBYOBReaderReadOptions): Promise<ReadableStreamBYOBReadResult<T>>;
releaseLock(): void;
}

// @public
export interface ReadableStreamBYOBReaderReadOptions {
// (undocumented)
min?: number;
}

// @public
export type ReadableStreamBYOBReadResult<T extends ArrayBufferView> = {
done: false;
Expand Down Expand Up @@ -156,6 +164,7 @@ export interface StreamPipeOptions {

// @public
export interface Transformer<I = any, O = any> {
cancel?: TransformerCancelCallback;
flush?: TransformerFlushCallback<O>;
// (undocumented)
readableType?: undefined;
Expand All @@ -165,6 +174,9 @@ export interface Transformer<I = any, O = any> {
writableType?: undefined;
}

// @public (undocumented)
export type TransformerCancelCallback = (reason: any) => void | PromiseLike<void>;

// @public (undocumented)
export type TransformerFlushCallback<O> = (controller: TransformStreamDefaultController<O>) => void | PromiseLike<void>;

Expand Down
7 changes: 6 additions & 1 deletion src/lib/abort-signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface AbortSignal {
*/
readonly aborted: boolean;

/**
* If aborted, returns the reason for aborting.
*/
readonly reason?: any;

/**
* Add an event listener to be triggered when this signal becomes aborted.
*/
Expand Down Expand Up @@ -51,7 +56,7 @@ export function isAbortSignal(value: unknown): value is AbortSignal {
export interface AbortController {
readonly signal: AbortSignal;

abort(): void;
abort(reason?: any): void;
}

interface AbortControllerConstructor {
Expand Down
114 changes: 114 additions & 0 deletions src/lib/abstract-ops/ecmascript.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { reflectCall } from 'lib/helpers/webidl';
import { typeIsObject } from '../helpers/miscellaneous';
import assert from '../../stub/assert';

export function CreateArrayFromList<T extends any[]>(elements: T): T {
// We use arrays to represent lists, so this is basically a no-op.
// Do a slice though just in case we happen to depend on the unique-ness.
Expand Down Expand Up @@ -40,3 +44,113 @@ export function ArrayBufferSlice(buffer: ArrayBufferLike, begin: number, end: nu
CopyDataBlockBytes(slice, 0, buffer, begin, length);
return slice;
}

export type MethodName<T> = {
[P in keyof T]: T[P] extends Function | undefined ? P : never;
}[keyof T];

export function GetMethod<T, K extends MethodName<T>>(receiver: T, prop: K): T[K] | undefined {
const func = receiver[prop];
if (func === undefined || func === null) {
return undefined;
}
if (typeof func !== 'function') {
throw new TypeError(`${String(prop)} is not a function`);
}
return func;
}

export interface SyncIteratorRecord<T> {
iterator: Iterator<T>,
nextMethod: Iterator<T>['next'],
done: boolean;
}

export interface AsyncIteratorRecord<T> {
iterator: AsyncIterator<T>,
nextMethod: AsyncIterator<T>['next'],
done: boolean;
}

export type SyncOrAsyncIteratorRecord<T> = SyncIteratorRecord<T> | AsyncIteratorRecord<T>;

export function CreateAsyncFromSyncIterator<T>(syncIteratorRecord: SyncIteratorRecord<T>): AsyncIteratorRecord<T> {
// Instead of re-implementing CreateAsyncFromSyncIterator and %AsyncFromSyncIteratorPrototype%,
// we use yield* inside an async generator function to achieve the same result.

// Wrap the sync iterator inside a sync iterable, so we can use it with yield*.
const syncIterable = {
[Symbol.iterator]: () => syncIteratorRecord.iterator
};
// Create an async generator function and immediately invoke it.
const asyncIterator = (async function* () {
return yield* syncIterable;
}());
// Return as an async iterator record.
const nextMethod = asyncIterator.next;
return { iterator: asyncIterator, nextMethod, done: false };
}

export type SyncOrAsyncIterable<T> = Iterable<T> | AsyncIterable<T>;
export type SyncOrAsyncIteratorMethod<T> = () => (Iterator<T> | AsyncIterator<T>);

function GetIterator<T>(
obj: SyncOrAsyncIterable<T>,
hint: 'async',
method?: SyncOrAsyncIteratorMethod<T>
): AsyncIteratorRecord<T>;
function GetIterator<T>(
obj: Iterable<T>,
hint: 'sync',
method?: SyncOrAsyncIteratorMethod<T>
): SyncIteratorRecord<T>;
function GetIterator<T>(
obj: SyncOrAsyncIterable<T>,
hint = 'sync',
method?: SyncOrAsyncIteratorMethod<T>
): SyncOrAsyncIteratorRecord<T> {
assert(hint === 'sync' || hint === 'async');
if (method === undefined) {
if (hint === 'async') {
method = GetMethod(obj as AsyncIterable<T>, Symbol.asyncIterator);
if (method === undefined) {
const syncMethod = GetMethod(obj as Iterable<T>, Symbol.iterator);
const syncIteratorRecord = GetIterator(obj as Iterable<T>, 'sync', syncMethod);
return CreateAsyncFromSyncIterator(syncIteratorRecord);
}
} else {
method = GetMethod(obj as Iterable<T>, Symbol.iterator);
}
}
if (method === undefined) {
throw new TypeError('The object is not iterable');
}
const iterator = reflectCall(method, obj, []);
if (!typeIsObject(iterator)) {
throw new TypeError('The iterator method must return an object');
}
const nextMethod = iterator.next;
return { iterator, nextMethod, done: false } as SyncOrAsyncIteratorRecord<T>;
}

export { GetIterator };

export function IteratorNext<T>(iteratorRecord: AsyncIteratorRecord<T>): Promise<IteratorResult<T>> {
const result = reflectCall(iteratorRecord.nextMethod, iteratorRecord.iterator, []);
if (!typeIsObject(result)) {
throw new TypeError('The iterator.next() method must return an object');
}
return result;
}

export function IteratorComplete<TReturn>(
iterResult: IteratorResult<unknown, TReturn>
): iterResult is IteratorReturnResult<TReturn> {
assert(typeIsObject(iterResult));
return Boolean(iterResult.done);
}

export function IteratorValue<T>(iterResult: IteratorYieldResult<T>): T {
assert(typeIsObject(iterResult));
return iterResult.value;
}
1 change: 1 addition & 0 deletions src/lib/abstract-ops/internal-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const AbortSteps = Symbol('[[AbortSteps]]');
export const ErrorSteps = Symbol('[[ErrorSteps]]');
export const CancelSteps = Symbol('[[CancelSteps]]');
export const PullSteps = Symbol('[[PullSteps]]');
export const ReleaseSteps = Symbol('[[ReleaseSteps]]');
39 changes: 39 additions & 0 deletions src/lib/helpers/array-buffer-view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;

export interface ArrayBufferViewConstructor<T extends ArrayBufferView = ArrayBufferView> {
new(buffer: ArrayBufferLike, byteOffset: number, length?: number): T;

readonly prototype: T;
}

export interface TypedArrayConstructor<T extends TypedArray = TypedArray> extends ArrayBufferViewConstructor<T> {
readonly BYTES_PER_ELEMENT: number;
}

export type DataViewConstructor = ArrayBufferViewConstructor<DataView>;

function isDataViewConstructor(ctor: Function): ctor is DataViewConstructor {
return ctor === DataView;
}

export function isDataView(view: ArrayBufferView): view is DataView {
return isDataViewConstructor(view.constructor);
}

export function arrayBufferViewElementSize<T extends ArrayBufferView>(ctor: ArrayBufferViewConstructor<T>): number {
if (isDataViewConstructor(ctor)) {
return 1;
}
return (ctor as unknown as TypedArrayConstructor).BYTES_PER_ELEMENT;
}
6 changes: 4 additions & 2 deletions src/lib/helpers/webidl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import assert from '../../stub/assert';

const originalPromise = Promise;
const originalPromiseThen = Promise.prototype.then;
const originalPromiseResolve = Promise.resolve.bind(originalPromise);
const originalPromiseReject = Promise.reject.bind(originalPromise);

// https://webidl.spec.whatwg.org/#a-new-promise
export function newPromise<T>(executor: (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
) => void): Promise<T> {
return new originalPromise(executor);
}

// https://webidl.spec.whatwg.org/#a-promise-resolved-with
export function promiseResolvedWith<T>(value: T | PromiseLike<T>): Promise<T> {
return originalPromiseResolve(value);
return newPromise(resolve => resolve(value));
}

// https://webidl.spec.whatwg.org/#a-promise-rejected-with
export function promiseRejectedWith<T = never>(reason: any): Promise<T> {
return originalPromiseReject(reason);
}
Expand Down
Loading
Loading