Skip to content
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
10 changes: 10 additions & 0 deletions .changeset/fix-ponyfill-typescript-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@sec-ant/readable-stream": minor
---

Fix TypeScript issues with the ponyfill (#66)

- `asyncIterator()` now returns `ReadableStreamAsyncIterator<R>` instead of an internal interface that exposed a private `unique symbol` brand. The package provides a compatible global declaration for older TypeScript versions and merges with modern `lib.dom.d.ts`, so ponyfill and polyfill usage share the same iterator type surface.
- Breaking type change: the unused `TReturn` generic parameter on `asyncIterator()` has been removed from the public signature. Runtime `iterator.return(value)` behavior remains spec-compliant and resolves with `{ done: true, value }`, while the public type follows `lib.dom`'s `ReadableStreamAsyncIterator<T>` shape.
- Added a top-level `types` field and a `typesVersions` map mirroring every subpath in `exports`, so the package's type definitions can be resolved under classic `moduleResolution: "node"` in addition to `node16` / `nodenext` / `bundler`.
- The polyfill now installs `ReadableStream.prototype.values` and `ReadableStream.prototype[Symbol.asyncIterator]` with Web IDL-conformant descriptors (`values` is enumerable, `[Symbol.asyncIterator]` is not) and guarantees the two slots reference the same function object.
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
],
"main": "./dist/index/index.js",
"module": "./dist/index/index.js",
"types": "./dist/index/index.d.ts",
"exports": {
".": "./dist/index/index.js",
"./asyncIterator": "./dist/index/asyncIterator.js",
Expand All @@ -23,6 +24,37 @@
"types": "./dist/types/async-iterator.d.ts"
}
},
"typesVersions": {
"*": {
"asyncIterator": [
"./dist/index/asyncIterator.d.ts"
],
"fromAnyIterable": [
"./dist/index/fromAnyIterable.d.ts"
],
"ponyfill": [
"./dist/ponyfill/index.d.ts"
],
"ponyfill/asyncIterator": [
"./dist/ponyfill/asyncIterator.d.ts"
],
"ponyfill/fromAnyIterable": [
"./dist/ponyfill/fromAnyIterable.d.ts"
],
"polyfill": [
"./dist/polyfill/index.d.ts"
],
"polyfill/asyncIterator": [
"./dist/polyfill/asyncIterator.d.ts"
],
"polyfill/fromAnyIterable": [
"./dist/polyfill/fromAnyIterable.d.ts"
],
"async-iterator": [
"./dist/types/async-iterator.d.ts"
]
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/Sec-ant/readable-stream.git"
Expand Down
111 changes: 46 additions & 65 deletions src/core/asyncIterator.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { AsyncIterablePrototype } from "./asyncIterablePrototype.js";

/**
* the implementer that does all the heavy works
*/
class ReadableStreamAsyncIterableIteratorImpl<R, TReturn>
implements AsyncIterator<R>
declare global {
interface AsyncIteratorObject<T, TReturn = unknown, TNext = unknown>
extends AsyncIterator<T, TReturn, TNext> {
[Symbol.asyncIterator](): AsyncIteratorObject<T, TReturn, TNext>;
}

interface ReadableStreamAsyncIterator<T>
extends AsyncIteratorObject<T, undefined, unknown> {
[Symbol.asyncIterator](): ReadableStreamAsyncIterator<T>;
}
}

class ReadableStreamAsyncIterableIteratorImpl<R>
implements AsyncIterator<R, unknown>
{
#reader: ReadableStreamDefaultReader<R>;
#preventCancel: boolean;
#isFinished = false;
#ongoingPromise:
| Promise<
ReadableStreamReadResult<R> | ReadableStreamReadDoneResult<TReturn>
>
| undefined = undefined;
#ongoingPromise: Promise<ReadableStreamReadResult<R>> | undefined = undefined;
constructor(reader: ReadableStreamDefaultReader<R>, preventCancel: boolean) {
this.#reader = reader;
this.#preventCancel = preventCancel;
Expand All @@ -25,13 +30,13 @@ class ReadableStreamAsyncIterableIteratorImpl<R, TReturn>
: nextSteps();
return this.#ongoingPromise as Promise<IteratorResult<R, undefined>>;
}
return(value?: TReturn) {
return(value?: unknown) {
const returnSteps = () => this.#returnSteps(value);
return (
this.#ongoingPromise
? this.#ongoingPromise.then(returnSteps, returnSteps)
: returnSteps()
) as Promise<IteratorReturnResult<TReturn>>;
) as Promise<IteratorReturnResult<unknown>>;
}
async #nextSteps(): Promise<ReadableStreamReadResult<R>> {
if (this.#isFinished) {
Expand All @@ -57,8 +62,8 @@ class ReadableStreamAsyncIterableIteratorImpl<R, TReturn>
return readResult;
}
async #returnSteps(
value?: TReturn,
): Promise<ReadableStreamReadDoneResult<TReturn>> {
value?: unknown,
): Promise<ReadableStreamReadDoneResult<unknown>> {
if (this.#isFinished) {
return {
done: true,
Expand All @@ -83,87 +88,63 @@ class ReadableStreamAsyncIterableIteratorImpl<R, TReturn>
}
}

// incapsulation
const implementSymbol = Symbol();

/**
* declare `ReadableStreamAsyncIterableIterator` interaface
*/
interface ReadableStreamAsyncIterableIterator<R, TReturn>
interface InternalReadableStreamAsyncIterableIterator<R>
extends ReadableStreamAsyncIterator<R> {
[implementSymbol]: ReadableStreamAsyncIterableIteratorImpl<R, TReturn>;
[implementSymbol]: ReadableStreamAsyncIterableIteratorImpl<R>;
}

/**
* A next method in an iterator protocal
* @param this
* @returns
*/
function _next<R, TReturn>(
this: ReadableStreamAsyncIterableIterator<R, TReturn>,
) {
function _next<R>(this: InternalReadableStreamAsyncIterableIterator<R>) {
return this[implementSymbol].next();
}
Object.defineProperty(_next, "name", { value: "next" });

/**
* A return method in an iterator protocal
* @param this
* @param returnValue
* @returns
*/
function _return<R, TReturn>(
this: ReadableStreamAsyncIterableIterator<R, TReturn>,
returnValue?: TReturn,
function _return<R>(
this: InternalReadableStreamAsyncIterableIterator<R>,
returnValue?: unknown,
) {
return this[implementSymbol].return(returnValue);
}
Object.defineProperty(_return, "name", { value: "return" });

/**
* create `readableStreamAsyncIterableIteratorPrototype`
*/
const readableStreamAsyncIterableIteratorPrototype: ReadableStreamAsyncIterableIterator<
unknown,
unknown
> = Object.create(AsyncIterablePrototype, {
next: {
enumerable: true,
configurable: true,
writable: true,
value: _next,
},
return: {
enumerable: true,
configurable: true,
writable: true,
value: _return,
},
});
const readableStreamAsyncIterableIteratorPrototype: InternalReadableStreamAsyncIterableIterator<unknown> =
Object.create(AsyncIterablePrototype, {
next: {
enumerable: true,
configurable: true,
writable: true,
value: _next,
},
return: {
enumerable: true,
configurable: true,
writable: true,
value: _return,
},
});

export interface ReadableStreamIteratorOptions {
preventCancel?: boolean;
}

/**
* Get an async iterable iterator from a readable stream
* Get an async iterable iterator from a readable stream.
* @param readableStream
* @param readableStreamIteratorOptions
* @returns
*/
export function asyncIterator<R, TReturn>(
export function asyncIterator<R>(
readableStream: ReadableStream<R>,
{ preventCancel = false }: ReadableStreamIteratorOptions = {},
) {
): ReadableStreamAsyncIterator<R> {
const reader = readableStream.getReader();
const implement = new ReadableStreamAsyncIterableIteratorImpl<R, TReturn>(
const implement = new ReadableStreamAsyncIterableIteratorImpl<R>(
reader,
preventCancel,
);
const readableStreamAsyncIterableIterator: ReadableStreamAsyncIterableIterator<
R,
TReturn
> = Object.create(readableStreamAsyncIterableIteratorPrototype);
const readableStreamAsyncIterableIterator: InternalReadableStreamAsyncIterableIterator<R> =
Object.create(readableStreamAsyncIterableIteratorPrototype);
readableStreamAsyncIterableIterator[implementSymbol] = implement;
return readableStreamAsyncIterableIterator;
}
39 changes: 28 additions & 11 deletions src/polyfill/asyncIterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,32 @@

import { asyncIterator } from "../core/asyncIterator.js";

ReadableStream.prototype.values ??= ReadableStream.prototype[
Symbol.asyncIterator
] ??= function (
...args: Parameters<typeof asyncIterator> extends [infer _, ...infer T]
? T
: never
) {
return asyncIterator(this, ...args);
};
const values =
ReadableStream.prototype.values ??
ReadableStream.prototype[Symbol.asyncIterator] ??
function values(
this: ReadableStream<unknown>,
...args: Parameters<typeof asyncIterator> extends [infer _, ...infer T]
? T
: never
) {
return asyncIterator(this, ...args);
};

ReadableStream.prototype[Symbol.asyncIterator] ??=
ReadableStream.prototype.values;
if (ReadableStream.prototype.values == null) {
Object.defineProperty(ReadableStream.prototype, "values", {
value: values,
writable: true,
enumerable: true,
configurable: true,
});
}

if (ReadableStream.prototype[Symbol.asyncIterator] == null) {
Object.defineProperty(ReadableStream.prototype, Symbol.asyncIterator, {
value: ReadableStream.prototype.values,
writable: true,
enumerable: false,
configurable: true,
});
}
19 changes: 11 additions & 8 deletions src/types/async-iterator.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { ReadableStreamIteratorOptions } from "../core/asyncIterator.js";
import type {
asyncIterator,
ReadableStreamIteratorOptions,
} from "../core/asyncIterator.js";

type AsyncIteratorOptions =
Parameters<typeof asyncIterator> extends [unknown, infer Options]
? Options
: ReadableStreamIteratorOptions | undefined;

/**
* augment global readable stream interface
*/
declare global {
interface AsyncIteratorObject<T, TReturn = unknown, TNext = unknown>
extends AsyncIterator<T, TReturn, TNext> {
Expand All @@ -15,10 +20,8 @@ declare global {
// biome-ignore lint/suspicious/noExplicitAny: to be compatible with lib.dom.d.ts
interface ReadableStream<R = any> {
[Symbol.asyncIterator](
options?: ReadableStreamIteratorOptions,
): ReadableStreamAsyncIterator<R>;
values(
options?: ReadableStreamIteratorOptions,
options?: AsyncIteratorOptions,
): ReadableStreamAsyncIterator<R>;
values(options?: AsyncIteratorOptions): ReadableStreamAsyncIterator<R>;
}
}
Loading
Loading