diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index 95d964aaf..8a94d9362 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -207,7 +207,7 @@ Uncaught exceptions print `Uncaught ` to stderr instead of Node.js's stac In a TypeScript `--dynamic` build, `const host: any = globalThis` or `(globalThis as any).TextDecoder` accesses the engine's global object. Its Node globals use those shims even without an embedded npm package. For example, the engine's `TextDecoder` supports UTF-8 labels only. Optional capabilities such as `fetch` are installed only when the build links their bridge. -Embedded Web Streams support default readable streams, writable streams and their writers/controllers, count and byte-length queuing strategies, transform streams, and UTF-8 decoder streams. Writable sinks serialize queued writes, wait for asynchronous start/close/abort callbacks, and expose queue backpressure through `ready` and `desiredSize`. Transform streams still run without readable-side backpressure. Byte streams, BYOB readers, `tee()`, `pipeTo()`, stream transfer, and piping with AbortSignal remain unsupported. These embedded APIs do not broaden the separate static Web Streams surface. +Embedded Web Streams support default readable and writable streams, their writer and transform-controller APIs, count and byte-length queuing strategies, transform streams, and UTF-8 encoder/decoder streams. Writable sinks serialize queued writes and wait for asynchronous start/close/abort callbacks. Transforms honor readable-side backpressure and asynchronous start/transform/flush/cancel callbacks. Default-stream `pipeTo()` and `pipeThrough()` honor `preventClose`, `preventAbort`, `preventCancel`, and AbortSignal, propagate cleanup errors, and release their locks when piping settles. Byte streams, BYOB readers, `tee()`, and stream transfer remain unsupported. These embedded APIs do not broaden the separate static Web Streams surface. Static tasks drain before engine jobs run at event-loop quiescence. A static `await` racing a package promise can therefore resolve in a different order from Node.js. diff --git a/docs/src/generated/node-v24-compatibility-meta.json b/docs/src/generated/node-v24-compatibility-meta.json index 07d91eed2..e7f98cfde 100644 --- a/docs/src/generated/node-v24-compatibility-meta.json +++ b/docs/src/generated/node-v24-compatibility-meta.json @@ -2,6 +2,6 @@ "schemaVersion": 3, "nodeVersion": "24.15.0", "nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119", - "artifactVersion": "60361b544f92e9e4378a", + "artifactVersion": "ab49b98a2d4f6853980e", "rowCount": 3662 } diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 3aa509823..e37d5dadf 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -491,9 +491,9 @@ }, "dynamic": { "supported": 0, - "partial": 38, + "partial": 40, "refused": 0, - "not-implemented": 54, + "not-implemented": 52, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -1331,9 +1331,9 @@ }, "dynamic": { "supported": 0, - "partial": 27, + "partial": 40, "refused": 0, - "not-implemented": 61, + "not-implemented": 48, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -44202,9 +44202,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "Verified absent from the embedded engine's web/global compatibility layer.", - "verification": "verified-absence" + "status": "partial", + "detail": "Provided by the embedded engine's web/global compatibility layer.", + "verification": "test-backed" } }, { @@ -44258,9 +44258,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "Verified absent from the embedded engine's web/global compatibility layer.", - "verification": "verified-absence" + "status": "partial", + "detail": "Provided by the embedded engine's web/global compatibility layer.", + "verification": "test-backed" } }, { @@ -94335,9 +94335,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -94363,9 +94363,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -95651,9 +95651,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -95679,9 +95679,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -95707,9 +95707,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.", + "verification": "test-backed" } }, { @@ -95735,9 +95735,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -95763,9 +95763,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -95791,9 +95791,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -95819,9 +95819,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -96015,9 +96015,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.", + "verification": "test-backed" } }, { @@ -96043,9 +96043,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -96071,9 +96071,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { @@ -96099,9 +96099,9 @@ "verification": "registry-gap" }, "dynamic": { - "status": "not-implemented", - "detail": "This API is not implemented by the dynamic-island shim.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented embedded module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/dynamic-support.json b/internal/compatibility/dynamic-support.json index b82be5a0c..b45f28bae 100644 --- a/internal/compatibility/dynamic-support.json +++ b/internal/compatibility/dynamic-support.json @@ -19,6 +19,11 @@ "wasi": "by-design" }, "features": [ + {"chapter": "webstreams", "symbols": ["readableStream.pipeTo", "readableStream.pipeThrough"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts"]}, + {"chapter": "webstreams", "parent": "Class: `TransformStream`", "symbols": ["readable", "writable"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts"]}, + {"chapter": "webstreams", "symbols": ["transformStreamDefaultController.enqueue", "transformStreamDefaultController.error", "transformStreamDefaultController.terminate"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts"]}, + {"chapter": "webstreams", "parent": "Class: `TransformStreamDefaultController`", "symbols": ["desiredSize"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts"]}, + {"chapter": "webstreams", "parent": "Class: `TextEncoderStream`", "symbols": ["encoding", "readable", "writable"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts"]}, {"chapter": "webstreams", "symbols": ["writableStream.abort", "writableStream.close", "writableStream.getWriter", "writableStreamDefaultWriter.abort", "writableStreamDefaultWriter.close", "writableStreamDefaultWriter.releaseLock", "writableStreamDefaultWriter.write", "writableStreamDefaultController.error"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"]}, {"chapter": "webstreams", "parent": "Class: `WritableStream`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["locked"]}, {"chapter": "webstreams", "parent": "Class: `WritableStreamDefaultWriter`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["closed", "desiredSize", "ready"]}, @@ -109,15 +114,15 @@ "stream": { "exports": ["Stream", "Readable", "Writable", "Duplex", "Transform", "PassThrough", "pipeline", "finished", "addAbortSignal", "promises", "isErrored", "isDestroyed", "isReadable", "isWritable"], "members": ["stream.Readable.from", "readable.read", "readable.push", "readable.unshift", "readable.pause", "readable.resume", "readable.isPaused", "readable.setEncoding", "readable.pipe", "readable.unpipe", "readable.destroy", "writable.write", "writable.end", "writable.cork", "writable.uncork", "writable.setDefaultEncoding", "writable.destroy", "transform.destroy"], "refusedMembers": ["readable.wrap"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] }, "stream/promises": { "exports": ["pipeline", "finished"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] }, "stream/consumers": { "exports": ["text", "buffer", "arrayBuffer", "json", "blob"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] }, - "stream/web": { "exports": ["ReadableStream", "TransformStream", "TextDecoderStream", "WritableStream", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "CountQueuingStrategy", "ByteLengthQueuingStrategy"], "evidence": ["tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"] }, + "stream/web": {"exports": ["ReadableStream", "TransformStream", "TextDecoderStream", "WritableStream", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "CountQueuingStrategy", "ByteLengthQueuingStrategy", "TransformStreamDefaultController", "TextEncoderStream"], "evidence": ["tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts"]}, "crypto": { "exports": ["createHash", "createHmac", "hash", "Hash", "Hmac", "randomBytes", "randomFillSync", "randomFill", "randomInt", "randomUUID", "getRandomValues", "timingSafeEqual", "pbkdf2", "pbkdf2Sync", "getHashes", "getCiphers", "getCurves", "webcrypto", "subtle", "constants", "KeyObject", "createCipheriv", "createDecipheriv", "createSign", "createVerify", "createDiffieHellman", "createECDH", "createPublicKey", "createPrivateKey", "createSecretKey", "diffieHellman", "generateKeyPair", "generateKeyPairSync", "generateKey", "generateKeySync", "sign", "verify", "publicEncrypt", "publicDecrypt", "privateEncrypt", "privateDecrypt", "scrypt", "scryptSync", "hkdf", "hkdfSync", "X509Certificate", "Certificate", "checkPrime", "checkPrimeSync", "generatePrime", "generatePrimeSync", "secureHeapUsed", "setEngine", "setFips", "getFips"], "refused": ["KeyObject", "createCipheriv", "createDecipheriv", "createSign", "createVerify", "createDiffieHellman", "createECDH", "createPublicKey", "createPrivateKey", "createSecretKey", "diffieHellman", "generateKeyPair", "generateKeyPairSync", "generateKey", "generateKeySync", "sign", "verify", "publicEncrypt", "publicDecrypt", "privateEncrypt", "privateDecrypt", "X509Certificate", "Certificate", "checkPrime", "checkPrimeSync", "generatePrime", "generatePrimeSync", "secureHeapUsed", "setEngine"], "members": ["hash.copy", "hash.digest", "hash.update", "hmac.digest", "hmac.update"], "evidence": ["tests/fixtures/npm/cases/crypto-shims/main.ts", "tests/fixtures/npm/cases/node24-crypto-kdf/main.ts"] }, "util": { "exports": ["getSystemErrorName", "getSystemErrorMessage", "getSystemErrorMap", "parseEnv", "format", "formatWithOptions", "inspect", "inherits", "promisify", "callbackify", "deprecate", "debuglog", "debug", "types", "isDeepStrictEqual", "stripVTControlCharacters", "styleText", "parseArgs", "toUSVString", "_extend", "TextEncoder", "TextDecoder", "MIMEType", "MIMEParams", "isArray"], "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts"] }, "util/types": { "exports": ["isAnyArrayBuffer", "isArrayBufferView", "isArgumentsObject", "isArrayBuffer", "isAsyncFunction", "isBigInt64Array", "isBigUint64Array", "isBooleanObject", "isBoxedPrimitive", "isBigIntObject", "isCryptoKey", "isDataView", "isDate", "isExternal", "isFloat16Array", "isFloat32Array", "isFloat64Array", "isGeneratorFunction", "isGeneratorObject", "isInt8Array", "isInt16Array", "isInt32Array", "isKeyObject", "isMap", "isMapIterator", "isModuleNamespaceObject", "isNativeError", "isNumberObject", "isPromise", "isProxy", "isRegExp", "isSet", "isSetIterator", "isSharedArrayBuffer", "isStringObject", "isSymbolObject", "isTypedArray", "isUint8Array", "isUint8ClampedArray", "isUint16Array", "isUint32Array", "isWeakMap", "isWeakSet"], "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts", "tests/fixtures/npm/cases/node24-util-types/main.ts"] } }, "globals": { - "supported": ["atob", "btoa", "clearImmediate", "clearInterval", "clearTimeout", "queueMicrotask", "setImmediate", "setInterval", "setTimeout", "structuredClone", "AbortController", "AbortSignal", "Blob", "CustomEvent", "DOMException", "Event", "EventTarget", "File", "Headers", "MessageChannel", "MessageEvent", "MessagePort", "Request", "Response", "TextDecoder", "TextDecoderStream", "TextEncoder", "TransformStream", "URLSearchParams", "ReadableStream", "WritableStream", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "CountQueuingStrategy", "ByteLengthQueuingStrategy"], + "supported": ["atob", "btoa", "clearImmediate", "clearInterval", "clearTimeout", "queueMicrotask", "setImmediate", "setInterval", "setTimeout", "structuredClone", "AbortController", "AbortSignal", "Blob", "CustomEvent", "DOMException", "Event", "EventTarget", "File", "Headers", "MessageChannel", "MessageEvent", "MessagePort", "Request", "Response", "TextDecoder", "TextDecoderStream", "TextEncoder", "TextEncoderStream", "TransformStream", "TransformStreamDefaultController", "URLSearchParams", "ReadableStream", "WritableStream", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "CountQueuingStrategy", "ByteLengthQueuingStrategy"], "npmSupported": ["Buffer", "URL"], - "absent": ["BroadcastChannel", "CloseEvent", "CompressionStream", "CryptoKey", "DecompressionStream", "EventSource", "FormData", "Navigator", "ReadableByteStreamController", "ReadableStreamBYOBReader", "ReadableStreamBYOBRequest", "Storage", "SubtleCrypto", "TextEncoderStream", "TransformStreamDefaultController", "URLPattern", "WebAssembly", "WebSocket"], + "absent": ["BroadcastChannel", "CloseEvent", "CompressionStream", "CryptoKey", "DecompressionStream", "EventSource", "FormData", "Navigator", "ReadableByteStreamController", "ReadableStreamBYOBReader", "ReadableStreamBYOBRequest", "Storage", "SubtleCrypto", "URLPattern", "WebAssembly", "WebSocket"], "evidence": ["tests/harness/web-globals.test.ts"] } } diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 8cbf42aef..f288c6608 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -14,10 +14,10 @@ "implement": 80 }, "dynamic": { - "audit-partial": 885, - "verify-gap": 2145, + "audit-partial": 900, + "verify-gap": 2132, "replace-refusal": 112, - "implement": 208 + "implement": 206 } }, "priorities": { @@ -45608,11 +45608,11 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "implement", - "verification": "verified-absence", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "island-global-absent:TextEncoderStream", + "source": "island-global:TextEncoderStream", "tests": [ "tests/harness/web-globals.test.ts" ] @@ -45678,11 +45678,11 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "implement", - "verification": "verified-absence", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "island-global-absent:TransformStreamDefaultController", + "source": "island-global:TransformStreamDefaultController", "tests": [ "tests/harness/web-globals.test.ts" ] @@ -104244,7 +104244,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -104372,12 +104377,16 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.readableStream.pipeThrough", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts" + ] } } }, @@ -104405,12 +104414,16 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.readableStream.pipeTo", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts" + ] } } }, @@ -105376,7 +105389,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -105562,7 +105580,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -105859,7 +105882,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -105971,7 +105999,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -106000,12 +106033,15 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.readable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts" + ] } } }, @@ -106033,12 +106069,15 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.writable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts" + ] } } }, @@ -106066,12 +106105,22 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-export:node:stream/web.TransformStreamDefaultController", + "tests": [ + "tests/fixtures/npm/cases/web-streams/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] } } }, @@ -106099,12 +106148,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.transformStreamDefaultController.enqueue", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] } } }, @@ -106132,12 +106183,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.transformStreamDefaultController.error", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] } } }, @@ -106165,12 +106218,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.transformStreamDefaultController.terminate", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] } } }, @@ -106198,12 +106253,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.desiredSize", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] } } }, @@ -106240,7 +106297,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -106352,7 +106414,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } @@ -106455,12 +106522,22 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-export:node:stream/web.TextEncoderStream", + "tests": [ + "tests/fixtures/npm/cases/web-streams/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] } } }, @@ -106488,12 +106565,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.encoding", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] } } }, @@ -106521,12 +106600,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.readable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] } } }, @@ -106554,12 +106635,14 @@ "tests": [] }, "dynamic": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "island-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "island-feature:webstreams.writable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] } } }, @@ -106596,7 +106679,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] } } diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index c68d4b0e2..bbc31c563 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -540,9 +540,9 @@ }, "dynamic": { "supported": 0, - "partial": 38, + "partial": 40, "refused": 0, - "not-implemented": 54, + "not-implemented": 52, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -1452,9 +1452,9 @@ }, "dynamic": { "supported": 0, - "partial": 27, + "partial": 40, "refused": 0, - "not-implemented": 61, + "not-implemented": 48, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -61620,8 +61620,8 @@ "evidence": "compiler-unmatched:globals" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-global-absent:TextEncoderStream", + "status": "partial", + "evidence": "island-global:TextEncoderStream", "tests": [ "tests/harness/web-globals.test.ts" ] @@ -61680,8 +61680,8 @@ "evidence": "compiler-unmatched:globals" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-global-absent:TransformStreamDefaultController", + "status": "partial", + "evidence": "island-global:TransformStreamDefaultController", "tests": [ "tests/harness/web-globals.test.ts" ] @@ -122757,9 +122757,14 @@ "status": "partial", "evidence": "derived:descendants", "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", "tests/fixtures/npm/cases/web-streams/main.ts" ] }, @@ -123063,7 +123068,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -123198,8 +123208,13 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.readableStream.pipeThrough", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts" + ] }, "anchorSource": "exact" }, @@ -123225,8 +123240,13 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.readableStream.pipeTo", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts" + ] }, "anchorSource": "exact" }, @@ -124021,7 +124041,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -124209,7 +124234,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -124466,7 +124496,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -124563,7 +124598,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -124617,8 +124657,12 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.readable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts" + ] }, "anchorSource": "ancestor" }, @@ -124644,8 +124688,12 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.writable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts" + ] }, "anchorSource": "ancestor" }, @@ -124671,8 +124719,19 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-export:node:stream/web.TransformStreamDefaultController", + "tests": [ + "tests/fixtures/npm/cases/web-streams/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] }, "anchorSource": "exact" }, @@ -124698,8 +124757,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.transformStreamDefaultController.enqueue", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] }, "anchorSource": "exact" }, @@ -124725,8 +124787,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.transformStreamDefaultController.error", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] }, "anchorSource": "exact" }, @@ -124752,8 +124817,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.transformStreamDefaultController.terminate", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] }, "anchorSource": "exact" }, @@ -124779,8 +124847,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.desiredSize", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ] }, "anchorSource": "ancestor" }, @@ -124812,7 +124883,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -124909,7 +124985,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" @@ -125000,8 +125081,19 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-export:node:stream/web.TextEncoderStream", + "tests": [ + "tests/fixtures/npm/cases/web-streams/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] }, "anchorSource": "exact" }, @@ -125027,8 +125119,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.encoding", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] }, "anchorSource": "ancestor" }, @@ -125054,8 +125149,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.readable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] }, "anchorSource": "ancestor" }, @@ -125081,8 +125179,11 @@ "evidence": "compiler-unmatched:webstreams" }, "dynamic": { - "status": "not-implemented", - "evidence": "island-unmatched:webstreams" + "status": "partial", + "evidence": "island-feature:webstreams.writable", + "tests": [ + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ] }, "anchorSource": "ancestor" }, @@ -125114,7 +125215,12 @@ "tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", - "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" + "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", + "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" ] }, "anchorSource": "exact" diff --git a/packages/compiler/test/ts7/baselines/order-parity.json b/packages/compiler/test/ts7/baselines/order-parity.json index 55024b02a..d330e0b8d 100644 --- a/packages/compiler/test/ts7/baselines/order-parity.json +++ b/packages/compiler/test/ts7/baselines/order-parity.json @@ -13811,6 +13811,36 @@ "/tests/fixtures/npm/cases/node24-web-stream-errors/main.ts" ], "diags": [] + }, + "/tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts": { + "order": [ + "/tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts" + ], + "diags": [] + }, + "/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts": { + "order": [ + "/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts" + ], + "diags": [] + }, + "/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts": { + "order": [ + "/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts" + ], + "diags": [] + }, + "/tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts": { + "order": [ + "/tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts" + ], + "diags": [] + }, + "/tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts": { + "order": [ + "/tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts" + ], + "diags": [] } } } diff --git a/packages/runtime/src/scr_island.c b/packages/runtime/src/scr_island.c index c7f82d84f..7d4421adc 100644 --- a/packages/runtime/src/scr_island.c +++ b/packages/runtime/src/scr_island.c @@ -7536,7 +7536,8 @@ static const char isl_modules_bootstrap[] = " const g = globalThis;\n" " const w = {\n" " ReadableStream: g.ReadableStream, TransformStream: g.TransformStream,\n" - " TextDecoderStream: g.TextDecoderStream,\n" + " TextDecoderStream: g.TextDecoderStream, TextEncoderStream: g.TextEncoderStream,\n" + " TransformStreamDefaultController: g.TransformStreamDefaultController,\n" " WritableStream: g.WritableStream, WritableStreamDefaultWriter: g.WritableStreamDefaultWriter,\n" " WritableStreamDefaultController: g.WritableStreamDefaultController,\n" " CountQueuingStrategy: g.CountQueuingStrategy, ByteLengthQueuingStrategy: g.ByteLengthQueuingStrategy,\n" diff --git a/packages/runtime/src/scr_island_manifest.h b/packages/runtime/src/scr_island_manifest.h index f1de6a060..37dbbede6 100644 --- a/packages/runtime/src/scr_island_manifest.h +++ b/packages/runtime/src/scr_island_manifest.h @@ -131,7 +131,8 @@ static const struct { "text,buffer,arrayBuffer,json,blob"}, {"node:stream/web", "ReadableStream,TransformStream,TextDecoderStream,WritableStream,WritableStreamDefaultWri" - "ter,WritableStreamDefaultController,CountQueuingStrategy,ByteLengthQueuingStrategy"}, + "ter,WritableStreamDefaultController,CountQueuingStrategy,ByteLengthQueuingStrategy,Trans" + "formStreamDefaultController,TextEncoderStream"}, {"node:crypto", "createHash,createHmac,hash,Hash,Hmac,randomBytes,randomFillSync,randomFill,randomInt,ran" "domUUID,getRandomValues,timingSafeEqual,pbkdf2,pbkdf2Sync,getHashes,getCiphers,getCurves" diff --git a/packages/runtime/src/scr_web.c b/packages/runtime/src/scr_web.c index 2af5d5cba..7ae609fbf 100644 --- a/packages/runtime/src/scr_web.c +++ b/packages/runtime/src/scr_web.c @@ -426,6 +426,7 @@ static const char web_prelude[] = " g[C.name] = C;\n" " }\n" "\n" + " const readableStreams = new WeakSet();\n" " class ReadableStream {\n" " constructor(source, strategy) {\n" " if (source === undefined) source = {};\n" @@ -439,7 +440,7 @@ static const char web_prelude[] = " this._highWaterMark = sizing.highWaterMark;\n" " this._size = sizing.size;\n" " this._queueSize = 0;\n" - " this._src = source;\n" + " this._src = source; readableStreams.add(this);\n" " this._queue = [];\n" " this._state = 'readable';\n" " this._storedError = undefined;\n" @@ -540,7 +541,7 @@ static const char web_prelude[] = " this._closeRequested = true;\n" " this._finishClose();\n" " const src = this._src;\n" - " return Promise.resolve(src.cancel ? src.cancel(reason) : undefined).then(() => undefined);\n" + " return writableInvoke(src.cancel, src, [reason]).then(() => undefined);\n" " }\n" " getReader(options) {\n" " if (options !== undefined && options !== null && options.mode !== undefined) {\n" @@ -687,96 +688,237 @@ static const char web_prelude[] = " });\n" " }\n" " pipeThrough(transform, options) {\n" - " if (transform === null || typeof transform !== 'object' || !transform.writable || !transform.readable) {\n" - " throw new TypeError('pipeThrough requires a { writable, readable } pair');\n" - " }\n" - " if (options !== undefined && options !== null && options.signal !== undefined) {\n" - " throw new Error('scriptc: AbortSignal is not supported by island streams yet');\n" - " }\n" - " pump(this, transform.writable);\n" - " return transform.readable;\n" + " if (!readableStreams.has(this)) throw streamInvalidThis('ReadableStream');\n" + " const readable = transform?.readable;\n" + " if (!readableStreams.has(readable)) throw streamArgument('transform.readable', 'an instance of ReadableStream', readable);\n" + " const writable = transform?.writable;\n" + " if (!writableStreams.has(writable)) throw streamArgument('transform.writable', 'an instance of WritableStream', writable);\n" + " const opts = pipeOptions(options); pipeLocks(this, writable);\n" + " pipeStreams(this, writable, opts).catch(() => {});\n" + " return readable;\n" " }\n" - " pipeTo() {\n" - " throw new Error('ReadableStream.pipeTo is not supported in the scriptc island (use pipeThrough or a reader)');\n" + " pipeTo(destination, options) {\n" + " try {\n" + " if (!readableStreams.has(this)) throw streamInvalidThis('ReadableStream');\n" + " if (!writableStreams.has(destination)) throw streamArgument('transform.writable', 'an instance of WritableStream', destination);\n" + " const opts = pipeOptions(options); pipeLocks(this, destination);\n" + " return pipeStreams(this, destination, opts);\n" + " } catch (e) { return Promise.reject(e); }\n" " }\n" " tee() {\n" " throw new Error('ReadableStream.tee is not supported in the scriptc island');\n" " }\n" " }\n" "\n" - " const pump = (rs, ws) => {\n" - " const reader = rs.getReader();\n" - " const writer = ws.getWriter();\n" - " const step = () =>\n" - " reader.read().then((r) => {\n" - " if (r.done) return writer.close();\n" - " return writer.write(r.value).then(step);\n" + " const pipeOptions = (options) => {\n" + " if (options !== undefined && options !== null && typeof options !== 'object' && typeof options !== 'function') throw streamArgument('options', 'of type object', options);\n" + " const preventAbort = options?.preventAbort, preventCancel = options?.preventCancel, preventClose = options?.preventClose, signal = options?.signal;\n" + " if (signal !== undefined && (signal === null || typeof signal !== 'object' || !('aborted' in signal))) throw streamArgument('options.signal', 'an instance of AbortSignal', signal);\n" + " return { preventAbort: !!preventAbort, preventCancel: !!preventCancel, preventClose: !!preventClose, signal };\n" + " };\n" + " const pipeLocks = (source, dest) => {\n" + " if (source._locked) throw streamInvalidState('The ReadableStream is locked');\n" + " if (writableStreams.get(dest).writer !== undefined) throw streamInvalidState('The WritableStream is locked');\n" + " };\n" + " const pipeStreams = (source, dest, { preventClose, preventAbort, preventCancel, signal }) => {\n" + " const reader = ReadableStream.prototype.getReader.call(source), writer = new WritableStreamDefaultWriter(dest), sink = writableStreams.get(dest);\n" + " const completion = streamDeferred();\n" + " let shuttingDown = false, currentWrite = Promise.resolve(), listening = false;\n" + " const finalize = (rejected, error) => {\n" + " writer.releaseLock(); reader.releaseLock();\n" + " if (listening) signal.removeEventListener('abort', abort);\n" + " if (rejected) completion.reject(error); else completion.resolve();\n" + " };\n" + " const waitForWrites = async () => {\n" + " let write;\n" + " do { write = currentWrite; await write; } while (write !== currentWrite);\n" + " };\n" + " const shutdown = (action, rejected = false, error) => {\n" + " if (shuttingDown) return;\n" + " shuttingDown = true;\n" + " const waiting = sink.state === 'writable' && !sink.closeRequest ? waitForWrites() : Promise.resolve();\n" + " waiting.then(() => action ? action() : undefined).then(\n" + " () => finalize(rejected, error), (e) => finalize(true, e));\n" + " };\n" + " const abort = () => {\n" + " const reason = signal.reason;\n" + " shutdown(() => {\n" + " const actions = [];\n" + " if (!preventAbort && sink.state === 'writable') actions.push(writer.abort(reason));\n" + " if (!preventCancel && source._state === 'readable') actions.push(reader.cancel(reason));\n" + " return Promise.all(actions);\n" + " }, true, reason);\n" + " };\n" + " const writeChunk = (chunk) => { currentWrite = writer.write(chunk); currentWrite.catch(() => {}); };\n" + " // Buffered reads register their write before source.closed observers run. Pending\n" + " // reads queue the write during enqueue, ahead of a subsequent close notification.\n" + " const step = async () => {\n" + " if (shuttingDown) return true;\n" + " if (sink.backpressure) { await writer.ready; if (shuttingDown) return true; }\n" + " if (source._state === 'readable' && source._queue.length) {\n" + " while (source._queue.length) {\n" + " if (shuttingDown) return true;\n" + " const queued = source._queue.shift();\n" + " source._queueSize = Math.max(0, source._queueSize - queued.size);\n" + " if (source._closeRequested && !source._queue.length) source._finishClose();\n" + " writeChunk(queued.chunk);\n" + " if (sink.backpressure || sink.state !== 'writable' || sink.closeRequest) break;\n" + " }\n" + " source._maybePull();\n" + " return source._state === 'closed';\n" + " }\n" + " if (source._state === 'closed') return true;\n" + " if (source._state === 'errored') throw source._storedError;\n" + " return new Promise((resolve, reject) => {\n" + " source._reads.push({ resolve(r) {\n" + " if (r.done) resolve(true);\n" + " else Promise.resolve().then(() => { writeChunk(r.value); resolve(false); });\n" + " }, reject });\n" + " source._maybePull();\n" " });\n" - " step().catch((e) => {\n" - " writer.abort(e).catch(() => {});\n" - " reader.cancel(e).catch(() => {});\n" + " };\n" + " if (signal !== undefined) {\n" + " if (signal.aborted) { abort(); return completion.promise; }\n" + " signal.addEventListener('abort', abort, { once: true, [Symbol.for('scriptc.events.resistStopPropagation')]: true });\n" + " listening = true;\n" + " }\n" + " (async () => { while (!await step()); })().catch(() => {});\n" + " const sourceError = (e) => shutdown(preventAbort ? undefined : () => writer.abort(e), true, e);\n" + " const sinkError = (e) => shutdown(preventCancel ? undefined : () => reader.cancel(e), true, e);\n" + " const sourceClose = () => shutdown(preventClose ? undefined : () => {\n" + " if (sink.closeRequest || sink.state === 'closed') return;\n" + " if (sink.state === 'errored') throw sink.error;\n" + " return writer.close();\n" " });\n" + " if (source._state === 'errored') sourceError(source._storedError); else reader.closed.catch(sourceError);\n" + " if (sink.state === 'errored') sinkError(sink.error); else writer.closed.catch(sinkError);\n" + " if (source._state === 'closed') sourceClose(); else reader.closed.then(sourceClose, () => {});\n" + " if (sink.closeRequest || sink.state === 'closed') {\n" + " const e = streamInvalidState('Destination WritableStream is closed');\n" + " shutdown(preventCancel ? undefined : () => reader.cancel(e), true, e);\n" + " }\n" + " return completion.promise;\n" " };\n" "\n" - " class TransformStream {\n" - " constructor(transformer, ws, rs) {\n" - " if (transformer === undefined || transformer === null) transformer = {};\n" - " if (transformer.readableType !== undefined || transformer.writableType !== undefined) {\n" - " throw new RangeError('readableType/writableType are not supported in the scriptc island');\n" + " const transformStreams = new WeakMap(), transformControllers = new WeakMap();\n" + " const transformToken = Symbol('TransformStreamDefaultController');\n" + " const transformPressure = (s, pressure) => {\n" + " if (s.change) s.change.resolve();\n" + " s.change = streamDeferred(); s.backpressure = pressure;\n" + " };\n" + " const transformClear = (s) => { s.transform = s.flush = s.cancel = undefined; };\n" + " const transformUnblock = (s) => { if (s.backpressure) transformPressure(s, false); };\n" + " const transformErrorWritable = (s, e) => {\n" + " transformClear(s);\n" + " const w = writableStreams.get(s.writable);\n" + " if (w.state === 'writable') { writableClear(w); writableError(w, e); }\n" + " transformUnblock(s);\n" + " };\n" + " const transformError = (s, e) => { s.readable._errorStream(e); transformErrorWritable(s, e); };\n" + " const transformFinish = (s, kind, reason) => {\n" + " if (s.finish) return s.finish;\n" + " const d = streamDeferred(); s.finish = d.promise;\n" + " const result = writableInvoke(kind === 'close' ? s.flush : s.cancel, s.transformer, kind === 'close' ? [s.controller] : [reason]);\n" + " transformClear(s);\n" + " result.then(() => {\n" + " if (kind === 'cancel') {\n" + " const w = writableStreams.get(s.writable);\n" + " if (w.state === 'errored') { d.reject(w.error); return; }\n" + " transformErrorWritable(s, reason);\n" + " } else {\n" + " if (s.readable._state === 'errored') { d.reject(s.readable._storedError); return; }\n" + " if (kind === 'close') { if (s.readable._state === 'readable' && !s.readable._closeRequested) s.readable._controller.close(); }\n" + " else s.readable._errorStream(reason);\n" " }\n" - " const t = transformer;\n" - " const self = this;\n" - " let rc = null;\n" - " this._readable = new ReadableStream({\n" - " start(c) { rc = c; },\n" - " cancel(reason) { writableError(writableStreams.get(self._writable), reason); },\n" - " }, rs == null ? { highWaterMark: 0 } : rs);\n" - " const tc = {\n" - " enqueue(chunk) { rc.enqueue(chunk); },\n" - " error(e) {\n" - " self._readable._errorStream(e);\n" - " writableError(writableStreams.get(self._writable), e);\n" - " },\n" - " terminate() {\n" - " if (self._readable._state === 'readable' && !self._readable._closeRequested) rc.close();\n" - " writableError(writableStreams.get(self._writable), new TypeError('The transform stream has been terminated'));\n" - " },\n" - " get desiredSize() { return rc.desiredSize; },\n" - " };\n" - " this._controller = tc;\n" + " d.resolve();\n" + " }, (e) => {\n" + " if (kind === 'cancel') transformErrorWritable(s, e); else s.readable._errorStream(e);\n" + " d.reject(e);\n" + " }).catch((e) => d.reject(e));\n" + " return s.finish;\n" + " };\n" + " class TransformStreamDefaultController {\n" + " constructor(token, stream) {\n" + " if (token !== transformToken) throw streamError(TypeError, 'ERR_ILLEGAL_CONSTRUCTOR', 'Illegal constructor');\n" + " transformControllers.set(this, stream);\n" + " }\n" + " get desiredSize() {\n" + " const s = transformControllers.get(this);\n" + " if (!s) throw streamInvalidThis('TransformStreamDefaultController');\n" + " return s.readable._controller.desiredSize;\n" + " }\n" + " enqueue(chunk) {\n" + " const s = transformControllers.get(this);\n" + " if (!s) throw streamInvalidThis('TransformStreamDefaultController');\n" + " const r = s.readable;\n" + " if (r._state !== 'readable' || r._closeRequested) throw streamInvalidState('Unable to enqueue');\n" + " try { r._controller.enqueue(chunk); }\n" + " catch (e) { transformErrorWritable(s, e); throw r._storedError; }\n" + " if (!r._reads.length && r._controller.desiredSize <= 0 && !s.backpressure) transformPressure(s, true);\n" + " }\n" + " error(e) {\n" + " const s = transformControllers.get(this);\n" + " if (!s) throw streamInvalidThis('TransformStreamDefaultController');\n" + " transformError(s, e);\n" + " }\n" + " terminate() {\n" + " const s = transformControllers.get(this);\n" + " if (!s) throw streamInvalidThis('TransformStreamDefaultController');\n" + " if (s.readable._state === 'readable' && !s.readable._closeRequested) s.readable._controller.close();\n" + " transformErrorWritable(s, streamInvalidState('TransformStream has been terminated'));\n" + " }\n" + " }\n" + " class TransformStream {\n" + " constructor(transformer = {}, ws, rs) {\n" + " if (transformer === null || (typeof transformer !== 'object' && typeof transformer !== 'function')) throw streamArgument('transformer', 'of type object', transformer);\n" + " for (const [name, value] of [['writableStrategy', ws], ['readableStrategy', rs]]) {\n" + " if (value !== undefined && value !== null && typeof value !== 'object' && typeof value !== 'function') throw streamArgument(name, 'of type object', value);\n" + " }\n" + " const readableType = transformer.readableType, writableType = transformer.writableType, start = transformer.start;\n" + " if (readableType !== undefined) throw streamInvalidValue('transformer.readableType', readableType);\n" + " if (writableType !== undefined) throw streamInvalidValue('transformer.writableType', writableType);\n" + " const readableSizing = streamStrategy(rs, 0), writableSizing = streamStrategy(ws, 1);\n" + " const s = { transformer, finish: undefined, backpressure: true };\n" + " transformPressure(s, true); transformStreams.set(this, s);\n" " let resolveStart;\n" " const startGate = new Promise((resolve) => { resolveStart = resolve; });\n" - " this._writable = new WritableStream({\n" + " s.writable = new WritableStream({\n" " start() { return startGate; },\n" - " /* Eager transform: writes run the transformer immediately (no\n" - " * readable-side backpressure) — order-exact, documented. */\n" - " write(chunk) {\n" - " let r;\n" - " try { r = t.transform ? t.transform(chunk, tc) : tc.enqueue(chunk); } catch (e) { tc.error(e); throw e; }\n" - " return Promise.resolve(r).catch((e) => { tc.error(e); throw e; });\n" + " async write(chunk) {\n" + " if (s.backpressure) {\n" + " await s.change.promise;\n" + " const w = writableStreams.get(s.writable);\n" + " if (w.state === 'erroring' || w.state === 'errored') throw w.error;\n" + " }\n" + " try { if (s.transform) await s.transform.call(transformer, chunk, s.controller); }\n" + " catch (e) { transformError(s, e); throw e; }\n" " },\n" - " close() {\n" - " let r;\n" - " try { r = t.flush ? t.flush(tc) : undefined; } catch (e) { tc.error(e); throw e; }\n" - " return Promise.resolve(r).then(\n" - " () => {\n" - " if (self._readable._state === 'readable' && !self._readable._closeRequested) rc.close();\n" - " },\n" - " (e) => { tc.error(e); throw e; },\n" - " );\n" - " },\n" - " abort(e) { self._readable._errorStream(e); },\n" - " }, ws);\n" - " const startResult = t.start ? t.start(tc) : undefined; // synchronous invocation\n" - " resolveStart(Promise.resolve(startResult).catch((e) => { tc.error(e); throw e; }));\n" + " close() { return transformFinish(s, 'close'); },\n" + " abort(reason) { return transformFinish(s, 'abort', reason); },\n" + " }, writableSizing);\n" + " s.readable = new ReadableStream({\n" + " start() { return startGate; },\n" + " pull() { transformPressure(s, false); return s.change.promise; },\n" + " cancel(reason) { return transformFinish(s, 'cancel', reason); },\n" + " }, readableSizing);\n" + " s.controller = new TransformStreamDefaultController(transformToken, s);\n" + " const transform = transformer.transform, flush = transformer.flush, cancel = transformer.cancel;\n" + " for (const [name, fn] of [['transform', transform], ['flush', flush], ['cancel', cancel]]) {\n" + " if (fn && typeof fn !== 'function') throw streamArgument('transformer.' + name, 'of type function', fn);\n" + " }\n" + " s.transform = transform || ((chunk, controller) => controller.enqueue(chunk)); s.flush = flush; s.cancel = cancel;\n" + " const startResult = start === undefined ? undefined : start.call(transformer, s.controller);\n" + " resolveStart(Promise.resolve(startResult).catch((e) => { transformError(s, e); throw e; }));\n" " }\n" - " get readable() { return this._readable; }\n" - " get writable() { return this._writable; }\n" + " get readable() { const s = transformStreams.get(this); if (!s) throw streamInvalidThis('TransformStream'); return s.readable; }\n" + " get writable() { const s = transformStreams.get(this); if (!s) throw streamInvalidThis('TransformStream'); return s.writable; }\n" + " }\n" + " for (const C of [TransformStream, TransformStreamDefaultController]) {\n" + " Object.defineProperty(C.prototype, Symbol.toStringTag, { value: C.name, configurable: true });\n" + " for (const name of Object.getOwnPropertyNames(C.prototype)) if (name !== 'constructor') Object.defineProperty(C.prototype, name, { enumerable: true });\n" + " g[C.name] = C;\n" " }\n" - "\n" " g.ReadableStream = ReadableStream;\n" - " g.TransformStream = TransformStream;\n" - "\n" "\n" " const textEncoders = new WeakSet();\n" " class TextEncoder {\n" @@ -916,6 +1058,37 @@ static const char web_prelude[] = " }\n" " }\n" "\n" + " class TextEncoderStream {\n" + " #transform;\n" + " constructor() {\n" + " const encoder = new TextEncoder();\n" + " let pending = null;\n" + " this.#transform = new TransformStream({\n" + " transform(chunk, controller) {\n" + " chunk = String(chunk);\n" + " let text = '';\n" + " for (let i = 0; i < chunk.length; i++) {\n" + " const unit = chunk.charCodeAt(i), item = chunk[i];\n" + " if (pending !== null) {\n" + " const high = pending; pending = null;\n" + " if (unit >= 0xdc00 && unit <= 0xdfff) { text += high + item; continue; }\n" + " text += '\\ufffd';\n" + " }\n" + " if (unit >= 0xd800 && unit <= 0xdbff) pending = item;\n" + " else text += unit >= 0xdc00 && unit <= 0xdfff ? '\\ufffd' : item;\n" + " }\n" + " if (text) controller.enqueue(encoder.encode(text));\n" + " },\n" + " flush(controller) { if (pending !== null) controller.enqueue(new Uint8Array([0xef, 0xbf, 0xbd])); },\n" + " });\n" + " }\n" + " get encoding() { this.#transform; return 'utf-8'; }\n" + " get readable() { return this.#transform.readable; }\n" + " get writable() { return this.#transform.writable; }\n" + " }\n" + " for (const name of ['encoding', 'readable', 'writable']) Object.defineProperty(TextEncoderStream.prototype, name, { enumerable: true });\n" + " g.TextEncoderStream = TextEncoderStream;\n" + "\n" " class TextDecoderStream extends g.TransformStream {\n" " constructor(label, options) {\n" " const dec = new TextDecoder(label, options);\n" diff --git a/tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts b/tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts new file mode 100644 index 000000000..2808b147f --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts @@ -0,0 +1,4 @@ +// @dynamic +// @stderr +import { reportEncoding } from "node24-web-stream-fixture/encoding.js"; +reportEncoding(); diff --git a/tests/fixtures/npm/cases/node24-web-stream-encoding/package.json b/tests/fixtures/npm/cases/node24-web-stream-encoding/package.json new file mode 100644 index 000000000..089153bcb --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-encoding/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts b/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts new file mode 100644 index 000000000..148b5f22b --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts @@ -0,0 +1,4 @@ +// @dynamic +// @stderr +import { reportPipeAbort } from "node24-web-stream-fixture/pipe-abort.js"; +reportPipeAbort(); diff --git a/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/package.json b/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/package.json new file mode 100644 index 000000000..089153bcb --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-pipe-abort/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts b/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts new file mode 100644 index 000000000..0e19324eb --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts @@ -0,0 +1,4 @@ +// @dynamic +// @stderr +import { reportPipeErrors } from "node24-web-stream-fixture/pipe-errors.js"; +reportPipeErrors(); diff --git a/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/package.json b/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/package.json new file mode 100644 index 000000000..089153bcb --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-pipe-errors/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts b/tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts new file mode 100644 index 000000000..b858d2acd --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts @@ -0,0 +1,4 @@ +// @dynamic +// @stderr +import { reportPipes } from "node24-web-stream-fixture/pipes.js"; +reportPipes(); diff --git a/tests/fixtures/npm/cases/node24-web-stream-pipes/package.json b/tests/fixtures/npm/cases/node24-web-stream-pipes/package.json new file mode 100644 index 000000000..089153bcb --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-pipes/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts b/tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts new file mode 100644 index 000000000..f6901a2dc --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts @@ -0,0 +1,4 @@ +// @dynamic +// @stderr +import { reportTransforms } from "node24-web-stream-fixture/transforms.js"; +reportTransforms(); diff --git a/tests/fixtures/npm/cases/node24-web-stream-transforms/package.json b/tests/fixtures/npm/cases/node24-web-stream-transforms/package.json new file mode 100644 index 000000000..089153bcb --- /dev/null +++ b/tests/fixtures/npm/cases/node24-web-stream-transforms/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/encoding.d.ts b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/encoding.d.ts new file mode 100644 index 000000000..9fec825c3 --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/encoding.d.ts @@ -0,0 +1 @@ +export declare function reportEncoding(): void; diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/encoding.js b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/encoding.js new file mode 100644 index 000000000..ad8cc1bed --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/encoding.js @@ -0,0 +1,32 @@ +import { ReadableStream, WritableStream, TextEncoderStream, TextDecoderStream, TransformStream } from "node:stream/web"; +import { out, result, sameError, launch } from "./helpers.js"; + +export function reportEncoding() { launch(async () => { + const encode = async (values) => { + const got = []; + await ReadableStream.from(values).pipeThrough(new TextEncoderStream()).pipeTo(new WritableStream({ write(v) { got.push([...v].join(",")); } })); + return got.join(" | "); + }; + const encoder = new TextEncoderStream(); + out("encoder", TextEncoderStream === globalThis.TextEncoderStream, encoder.encoding, encoder.readable instanceof ReadableStream, encoder.writable instanceof WritableStream, encoder instanceof TransformStream, Object.prototype.toString.call(encoder), Object.keys(TextEncoderStream.prototype).join(",")); + await encoder.writable.close(); + out("ascii", await encode(["hello", "", " world"])); + out("surrogate", await encode(["A\ud83d", "", "\ude00B", "\ud800", "x\udc00", "\udbff"])); + out("surrogate-pairs", await encode(["\ud800", "\ud801", "\udc00", "\udc01"])); + out("empty", await encode(["", ""])); + out("coercion", await encode([undefined, null, false, 42, 3n, Symbol("x"), { toString() { return "object"; } }])); + { + const got = []; + await ReadableStream.from(["Hello ", "\ud83d", "\ude00", "!"]).pipeThrough(new TextEncoderStream()).pipeThrough(new TextDecoderStream()).pipeTo(new WritableStream({ write(v) { got.push(v); } })); + out("roundtrip", got.join("")); + } + { + const reason = new Error("coercion"), t = new TextEncoderStream(), w = t.writable.getWriter(), r = t.readable.getReader(); + const read = sameError(r.read(), reason), write = sameError(w.write({ toString() { throw reason; } }), reason); + out("encoding-error", await write, await read, await sameError(w.closed, reason), await sameError(r.closed, reason)); + } + for (const name of ["encoding", "readable", "writable"]) { + try { Object.getOwnPropertyDescriptor(TextEncoderStream.prototype, name).get.call({}); out("brand", name, false); } + catch (e) { out("brand", name, e instanceof TypeError); } + } +}); } diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-abort.d.ts b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-abort.d.ts new file mode 100644 index 000000000..dc4dd08f6 --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-abort.d.ts @@ -0,0 +1 @@ +export declare function reportPipeAbort(): void; diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-abort.js b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-abort.js new file mode 100644 index 000000000..2948b6644 --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-abort.js @@ -0,0 +1,53 @@ +import { ReadableStream, WritableStream, TransformStream } from "node:stream/web"; +import { out, deferred, sameError, launch } from "./helpers.js"; + +export function reportPipeAbort() { launch(async () => { + for (const preventAbort of [false, true]) for (const preventCancel of [false, true]) { + const controller = new AbortController(), reason = new Error("pre-abort"), log = []; + controller.abort(reason); + const source = new ReadableStream({ cancel(e) { log.push("cancel:" + (e === reason)); } }, { highWaterMark: 0 }); + const sink = new WritableStream({ write() { log.push("write"); }, abort(e) { log.push("abort:" + (e === reason)); } }); + out("pre-abort", preventAbort, preventCancel, await sameError(source.pipeTo(sink, { signal: controller.signal, preventAbort, preventCancel }), reason), log.sort().join(","), source.locked, sink.locked); + if (preventCancel) await source.cancel(); if (preventAbort) await sink.close(); + } + { + const controller = new AbortController(), source = new ReadableStream({ pull() { throw new Error("read before destination capacity"); } }, { highWaterMark: 0 }); + let writes = 0; + const sink = new WritableStream({ write() { writes++; } }, { highWaterMark: 0 }); + const p = source.pipeTo(sink, { signal: controller.signal }); + await new Promise(resolve => setTimeout(resolve, 0)); controller.abort(); + out("zero-sink", await sameError(p, controller.signal.reason), controller.signal.reason.name, writes, source.locked, sink.locked); + } + { + const entered = deferred(), controller = new AbortController(), reason = new Error("pending-read"), log = []; + controller.signal.addEventListener("abort", e => { log.push("stopped"); e.stopImmediatePropagation(); }); + const source = new ReadableStream({ pull() { entered.resolve(); }, cancel(e) { log.push("cancel:" + (e === reason)); } }, { highWaterMark: 0 }); + const sink = new WritableStream({ abort(e) { log.push("abort:" + (e === reason)); } }); + const p = source.pipeTo(sink, { signal: controller.signal }); await entered.promise; controller.abort(reason); + out("read-abort", await sameError(p, reason), log.sort().join(","), source.locked, sink.locked); + } + { + const entered = deferred(), gate = deferred(), controller = new AbortController(), reason = new Error("pending-write"), log = []; + const source = new ReadableStream({ start(c) { c.enqueue("a"); c.enqueue("b"); }, cancel(e) { log.push("cancel:" + (e === reason)); } }); + const sink = new WritableStream({ async write(v) { log.push("write:" + v); entered.resolve(); await gate.promise; log.push("written"); }, abort(e) { log.push("abort:" + (e === reason)); } }); + const p = source.pipeTo(sink, { signal: controller.signal }); await entered.promise; controller.abort(reason); + await Promise.resolve(); out("write-abort-waits", log.join(","), source.locked, sink.locked); + gate.resolve(); out("write-abort", await sameError(p, reason), log.sort().join(","), source.locked, sink.locked); + } + for (const fails of ["abort", "cancel"]) { + const controller = new AbortController(), reason = new Error("reason"), cleanup = new Error("cleanup"); + const source = new ReadableStream({ cancel() { if (fails === "cancel") return Promise.reject(cleanup); } }); + const sink = new WritableStream({ abort() { if (fails === "abort") return Promise.reject(cleanup); } }); + controller.abort(reason); + out("abort-cleanup", fails, await sameError(source.pipeTo(sink, { signal: controller.signal }), cleanup), source.locked, sink.locked); + } + { + const controller = new AbortController(), reason = new Error("through-abort"), entered = deferred(); + const source = new ReadableStream({ pull() { entered.resolve(); }, cancel(e) { out("through-cancel", e === reason); } }, { highWaterMark: 0 }); + const t = new TransformStream(), output = source.pipeThrough(t, { signal: controller.signal }); + const reader = output.getReader(), read = sameError(reader.read(), reason); + await entered.promise; controller.abort(reason); out("through-abort", await read, await sameError(reader.closed, reason)); + reader.releaseLock(); await new Promise(resolve => setTimeout(resolve, 0)); + out("through-unlocked", source.locked, t.writable.locked); + } +}); } diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-errors.d.ts b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-errors.d.ts new file mode 100644 index 000000000..95222ac9c --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-errors.d.ts @@ -0,0 +1 @@ +export declare function reportPipeErrors(): void; diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-errors.js b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-errors.js new file mode 100644 index 000000000..eae37f313 --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipe-errors.js @@ -0,0 +1,46 @@ +import { ReadableStream, WritableStream } from "node:stream/web"; +import { out, deferred, result, sameError, launch } from "./helpers.js"; + +export function reportPipeErrors() { launch(async () => { + for (const preventAbort of [false, true]) for (const cleanupFails of [false, true]) { + const reason = new Error("source"), cleanup = new Error("abort"), log = []; + const source = new ReadableStream({ start(c) { c.error(reason); } }); + const sink = new WritableStream({ abort(e) { log.push(e === reason); if (cleanupFails) throw cleanup; } }); + const p = source.pipeTo(sink, { preventAbort }); + out("source-error", preventAbort, cleanupFails, await sameError(p, cleanupFails && !preventAbort ? cleanup : reason), log.join(","), source.locked, sink.locked); + if (preventAbort) await sink.close(); + } + for (const preventCancel of [false, true]) for (const cleanupFails of [false, true]) { + const reason = new Error("sink"), cleanup = new Error("cancel"), log = []; + const source = new ReadableStream({ start(c) { c.enqueue("chunk"); }, cancel(e) { log.push(e === reason); if (cleanupFails) throw cleanup; } }); + const sink = new WritableStream({ write() { throw reason; } }); + out("sink-error", preventCancel, cleanupFails, await sameError(source.pipeTo(sink, { preventCancel }), cleanupFails && !preventCancel ? cleanup : reason), log.join(","), source.locked, sink.locked); + if (preventCancel) await result(source.cancel()); + } + { + const reason = new Error("already"), source = new ReadableStream({ cancel(e) { out("initial-cancel", e === reason); } }); + const sink = new WritableStream({ start(c) { c.error(reason); } }); + out("initial-error", await sameError(source.pipeTo(sink), reason), source.locked, sink.locked); + } + { + const entered = deferred(), gate = deferred(), log = []; let controller; + const reason = new Error("during-write"); + const source = new ReadableStream({ start(c) { controller = c; c.enqueue("a"); } }); + const sink = new WritableStream({ async write(v) { log.push(v); entered.resolve(); await gate.promise; log.push("written"); }, abort(e) { log.push("abort:" + (e === reason)); } }); + const p = sameError(source.pipeTo(sink), reason); await entered.promise; controller.error(reason); + await Promise.resolve(); out("error-waits", log.join(","), source.locked, sink.locked); + gate.resolve(); out("error-done", await p, log.join(","), source.locked, sink.locked); + } + { + let controller; const entered = deferred(), reason = new Error("idle-destination"); + const source = new ReadableStream({ pull() { entered.resolve(); }, cancel(e) { out("idle-cancel", e === reason); } }, { highWaterMark: 0 }); + const sink = new WritableStream({ start(c) { controller = c; } }); + const p = source.pipeTo(sink); await entered.promise; controller.error(reason); + out("idle-error", await sameError(p, reason), source.locked, sink.locked); + } + { + const reason = new Error("close"), source = ReadableStream.from(["last"]); + const sink = new WritableStream({ write(v) { out("close-last", v); }, close() { throw reason; } }); + out("close-error", await sameError(source.pipeTo(sink), reason), source.locked, sink.locked); + } +}); } diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipes.d.ts b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipes.d.ts new file mode 100644 index 000000000..aa553c767 --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipes.d.ts @@ -0,0 +1 @@ +export declare function reportPipes(): void; diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipes.js b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipes.js new file mode 100644 index 000000000..b88159f0b --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/pipes.js @@ -0,0 +1,98 @@ +import { ReadableStream, WritableStream, TransformStream } from "node:stream/web"; +import { out, deferred, result, caught, launch } from "./helpers.js"; + +export function reportPipes() { launch(async () => { + for (const highWaterMark of [1, 4]) { + const log = []; + const source = new ReadableStream({ start(c) { for (const v of [1, 2, 3]) c.enqueue(v); c.close(); } }); + const sink = new WritableStream({ write(v) { log.push(v); }, close() { log.push("closed"); } }, { highWaterMark }); + const p = source.pipeTo(sink); + out("locks", highWaterMark, source.locked, sink.locked); + out("pipe", await p, log.join(","), source.locked, sink.locked); + } + { + let controller; + const source = new ReadableStream({ start(c) { controller = c; } }, { highWaterMark: 0 }); + const got = [], sink = new WritableStream({ write(v) { got.push(v); }, close() { got.push("close"); } }); + const p = source.pipeTo(sink); + controller.enqueue("last"); controller.close(); await p; + out("pending-last", got.join(","), source.locked, sink.locked); + } + { + const entered = deferred(), gate = deferred(), log = []; + const source = new ReadableStream({ start(c) { c.enqueue("last"); c.close(); } }); + const sink = new WritableStream({ async write(v) { log.push(v); entered.resolve(); await gate.promise; log.push("written"); }, close() { log.push("closed"); } }); + const p = source.pipeTo(sink); await entered.promise; + out("final-inflight", log.join(","), source.locked, sink.locked); + gate.resolve(); await p; out("final-settled", log.join(","), source.locked, sink.locked); + } + { + const entered = deferred(), gate = deferred(); let pulls = 0; + const source = new ReadableStream({ pull(c) { c.enqueue(++pulls); if (pulls === 3) c.close(); } }, { highWaterMark: 0 }); + const got = [], sink = new WritableStream({ async write(v) { got.push(v); if (v === 1) { entered.resolve(); await gate.promise; } } }); + const p = source.pipeTo(sink); await entered.promise; + out("pipe-demand", pulls, got.join(",")); gate.resolve(); await p; + out("pipe-drain", pulls, got.join(",")); + } + for (const preventClose of [false, true]) { + const log = [], source = ReadableStream.from(["a", "b"]); + const sink = new WritableStream({ write(v) { log.push(v); }, close() { log.push("close"); } }); + await source.pipeTo(sink, { preventClose }); + out("prevent-close", preventClose, log.join(","), source.locked, sink.locked); + const w = sink.getWriter(); out("after-pipe", await result(w.write("later"))); + if (preventClose) await w.close(); w.releaseLock(); + } + { + const source = ReadableStream.from([1, 2]); + const twice = new TransformStream({ transform(v, c) { c.enqueue(v * 2); } }); + const plus = new TransformStream({ transform(v, c) { c.enqueue(v + 1); }, flush(c) { c.enqueue("flush"); } }); + const got = [], sink = new WritableStream({ write(v) { got.push(v); } }); + const output = source.pipeThrough(twice).pipeThrough(plus); + out("chain-locked", source.locked, twice.writable.locked, twice.readable.locked, plus.writable.locked, output === plus.readable); + await output.pipeTo(sink); + out("chain", got.join(","), ...[source, twice.writable, twice.readable, plus.writable, plus.readable, sink].map(s => s.locked)); + } + { + const source = ReadableStream.from(["kept-open"]), t = new TransformStream(); + const output = source.pipeThrough(t, { preventClose: true }), reader = output.getReader(); + out("through-prevent-close", (await reader.read()).value); + await new Promise(resolve => setTimeout(resolve, 0)); + out("through-released", source.locked, t.writable.locked); + await t.writable.close(); out("through-manual-close", (await reader.read()).done); reader.releaseLock(); + } + { + const got = [], source = ReadableStream.from(["internal"]), sink = new WritableStream({ write(v) { got.push(v); } }); + source.getReader = sink.getWriter = () => { throw new Error("public acquisition override"); }; + await source.pipeTo(sink); out("internal-locks", got.join(","), source.locked, sink.locked); + } + { + const order = [], source = new ReadableStream({ start(c) { c.close(); } }), sink = new WritableStream(); + const options = Object.fromEntries(["preventAbort", "preventCancel", "preventClose", "signal"].map(name => [name, undefined])); + for (const name of Object.keys(options)) Object.defineProperty(options, name, { get() { order.push(name); return undefined; } }); + await source.pipeTo(sink, options); out("option-order", order.join(",")); + } + for (const options of [null, [], () => {}]) { + const source = new ReadableStream({ start(c) { c.close(); } }); + out("option-object", await result(source.pipeTo(new WritableStream(), options))); + } + out("invalid-source", await result(ReadableStream.prototype.pipeTo.call({}, new WritableStream()))); + out("invalid-destination", await result(new ReadableStream().pipeTo({}))); + out("invalid-options", await result(new ReadableStream().pipeTo(new WritableStream(), 1))); + out("invalid-signal", await result(new ReadableStream().pipeTo(new WritableStream(), { signal: null }))); + out("invalid-through", caught(() => new ReadableStream().pipeThrough({})), caught(() => new ReadableStream().pipeThrough({ readable: new ReadableStream(), writable: {} }))); + { + const source = new ReadableStream(), sink = new WritableStream(), r = source.getReader(); + out("locked-source", await result(source.pipeTo(sink)), caught(() => source.pipeThrough({ readable: new ReadableStream(), writable: sink })), sink.locked); + r.releaseLock(); const w = sink.getWriter(); + out("locked-sink", await result(source.pipeTo(sink)), source.locked); w.releaseLock(); + } + for (const closing of [false, true]) { + const gate = deferred(), reason = deferred(); + const sink = new WritableStream({ close() { return gate.promise; } }); + const close = sink.close(); if (!closing) { gate.resolve(); await close; } + const source = new ReadableStream({ cancel(e) { reason.resolve(e); } }); + const p = source.pipeTo(sink); + out("closed-destination", closing, await result(p), (await reason.promise).code, source.locked, sink.locked); + gate.resolve(); await close; + } +}); } diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/transforms.d.ts b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/transforms.d.ts new file mode 100644 index 000000000..2917ad296 --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/transforms.d.ts @@ -0,0 +1 @@ +export declare function reportTransforms(): void; diff --git a/tests/fixtures/npm/node_modules/node24-web-stream-fixture/transforms.js b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/transforms.js new file mode 100644 index 000000000..07c5452fe --- /dev/null +++ b/tests/fixtures/npm/node_modules/node24-web-stream-fixture/transforms.js @@ -0,0 +1,77 @@ +import { TransformStream, TransformStreamDefaultController, ByteLengthQueuingStrategy } from "node:stream/web"; +import { out, deferred, result, caught, sameError, launch } from "./helpers.js"; + +export function reportTransforms() { launch(async () => { + out("controller-export", TransformStreamDefaultController === globalThis.TransformStreamDefaultController, caught(() => new TransformStreamDefaultController())); + out("controller-brand", ...["enqueue", "error", "terminate"].map(name => caught(() => TransformStreamDefaultController.prototype[name].call({}))), caught(() => Object.getOwnPropertyDescriptor(TransformStreamDefaultController.prototype, "desiredSize").get.call({}))); + out("transform-brand", ...["readable", "writable"].map(name => caught(() => Object.getOwnPropertyDescriptor(TransformStream.prototype, name).get.call({})))); + { + let controller, transformed = 0; + const t = new TransformStream({ start(c) { controller = c; }, transform(v, c) { transformed++; c.enqueue(v); } }); + const w = t.writable.getWriter(), r = t.readable.getReader(); + const first = w.write("a"), second = w.write("b"); + await new Promise(resolve => setTimeout(resolve, 0)); + out("no-demand", transformed, controller.desiredSize, w.desiredSize, controller instanceof TransformStreamDefaultController, Object.prototype.toString.call(controller)); + out("first-demand", (await r.read()).value); await first; + out("one-demand", transformed, controller.desiredSize); + out("second-demand", (await r.read()).value); await second; + await w.close(); out("demand-closed", (await r.read()).done, controller.desiredSize, await result(w.closed)); + out("enqueue-closed", caught(() => controller.enqueue("late"))); + } + { + const entered = deferred(); let controller, transformed = 0; + const t = new TransformStream({ start(c) { controller = c; }, transform(v, c) { transformed++; c.enqueue(v); if (transformed === 2) entered.resolve(); } }, undefined, new ByteLengthQueuingStrategy({ highWaterMark: 4 })); + const w = t.writable.getWriter(), r = t.readable.getReader(); + const writes = [w.write(new Uint8Array(3)), w.write(new Uint8Array(2)), w.write(new Uint8Array(1))]; + await entered.promise; await writes[1]; + out("transform-capacity", transformed, controller.desiredSize); + out("sized-read", (await r.read()).value.byteLength); await writes[2]; + out("transform-resumed", transformed, controller.desiredSize); + const close = w.close(), got = []; for (;;) { const v = await r.read(); if (v.done) break; got.push(v.value.byteLength); } await close; + out("sized-rest", got.join(",")); + } + { + const start = deferred(), flush = deferred(), entered = deferred(), log = []; + const transformer = { start(c) { log.push("start:" + (this === transformer)); c.enqueue("start"); return start.promise; }, transform(v, c) { log.push("transform:" + (this === transformer)); c.enqueue(v); }, async flush(c) { log.push("flush:" + (this === transformer)); entered.resolve(); await flush.promise; c.enqueue("flush"); } }; + const t = new TransformStream(transformer), w = t.writable.getWriter(), r = t.readable.getReader(); + transformer.transform = () => { throw new Error("changed"); }; transformer.flush = transformer.transform; + const write = w.write("value"); out("start-buffer", (await r.read()).value, log.join(",")); + const read = r.read(); start.resolve(); out("start-release", (await read).value); await write; + const close = w.close(); await entered.promise; out("flush-pending", log.join(",")); + const final = r.read(); flush.resolve(); out("flush-value", (await final).value); await close; out("flush-done", (await r.read()).done); + } + for (const kind of ["error", "terminate", "cancel"]) { + let c; const reason = new Error("controller-error"); + const t = new TransformStream({ start(controller) { c = controller; } }); + const w = t.writable.getWriter(), r = t.readable.getReader(); + const write = result(w.write("blocked")); await new Promise(resolve => setTimeout(resolve, 0)); + if (kind === "error") c.error(reason); + if (kind === "terminate") c.terminate(); + if (kind === "cancel") await r.cancel(reason); + out("unblock", kind, await write, await result(w.closed), await result(r.read()), c.desiredSize); + } + for (const kind of ["transform", "flush", "start"]) { + const reason = new Error(kind), t = new TransformStream({ [kind]() { return Promise.reject(reason); } }); + const w = t.writable.getWriter(), r = t.readable.getReader(), read = sameError(r.read(), reason); + if (kind === "transform") out("transform-failure", await sameError(w.write("x"), reason)); + if (kind === "flush") out("flush-failure", await sameError(w.close(), reason)); + out("both-error", kind, await read, await sameError(w.closed, reason), await sameError(r.closed, reason)); + } + for (const direction of ["readable", "writable"]) for (const fails of [false, true]) { + const entered = deferred(), gate = deferred(), reason = new Error("cancel"), failure = new Error("cancel-failure"), log = []; + const transformer = { cancel(e) { log.push(this === transformer && e === reason); entered.resolve(); return gate.promise; } }; + const t = new TransformStream(transformer), w = t.writable.getWriter(), r = t.readable.getReader(); + transformer.cancel = () => { throw new Error("changed"); }; + const end = direction === "readable" ? r.cancel(reason) : w.abort(reason); + await entered.promise; out("cancel-wait", direction, fails, log.join(",")); + if (fails) gate.reject(failure); else gate.resolve(); + out("cancel-end", direction, fails, await result(end), await sameError(direction === "readable" ? w.closed : r.closed, fails ? failure : reason)); + } + { + const entered = deferred(), gate = deferred(), reason = new Error("race"), log = []; + const t = new TransformStream({ async flush() { log.push("flush"); entered.resolve(); await gate.promise; }, cancel() { log.push("cancel"); } }); + const w = t.writable.getWriter(), r = t.readable.getReader(), close = result(w.close()); + await entered.promise; const cancel = result(r.cancel(reason)); gate.resolve(); + out("flush-cancel-race", await close, await cancel, log.join(","), await result(w.closed), (await r.read()).done); + } +}); } diff --git a/tests/harness/web-globals.test.ts b/tests/harness/web-globals.test.ts index 59b1a9188..5c9ba4ba2 100644 --- a/tests/harness/web-globals.test.ts +++ b/tests/harness/web-globals.test.ts @@ -136,16 +136,16 @@ describe(`island web globals (scriptc-only${sanitize ? ", sanitized" : ""})`, () expect(result.exitCode).toBe(0); expect(result.stderr).toBe(""); expect(result.stdout.trimEnd()).toBe( - "function function function function function function function true false true true false false true", + "function function function function function function function true true true true false false true", ); }); - test("writable stream constructors and strategies are installed without an npm graph", async () => { + test("stream constructors and strategies are installed without an npm graph", async () => { const out = await islandEval( "web-writable-presence", - `[typeof WritableStream, typeof WritableStreamDefaultWriter, typeof WritableStreamDefaultController, typeof CountQueuingStrategy, typeof ByteLengthQueuingStrategy].join(' ')`, + `[typeof WritableStream, typeof WritableStreamDefaultWriter, typeof WritableStreamDefaultController, typeof CountQueuingStrategy, typeof ByteLengthQueuingStrategy, typeof TransformStreamDefaultController, typeof TextEncoderStream].join(' ')`, ); - expect(out).toBe("function function function function function"); + expect(out).toBe("function function function function function function function"); }); test("structuredClone clones deep (cycles included) and validates options like Node", async () => { @@ -201,14 +201,13 @@ describe(`island web globals (scriptc-only${sanitize ? ", sanitized" : ""})`, () expect(out).toBe("function function true true"); }); - test("tee, pipeTo, BYOB readers, and byte streams are fenced with clear errors", async () => { + test("tee, BYOB readers, and byte streams are fenced with clear errors", async () => { const out = await islandEval( "web-fences", `(() => { const grab = (f) => { try { f(); return 'no-throw'; } catch (e) { return e.message; } }; return [ grab(() => new ReadableStream().tee()), - grab(() => new ReadableStream().pipeTo({})), grab(() => new ReadableStream().getReader({ mode: 'byob' })), grab(() => new ReadableStream({ type: 'bytes' })), ].join(' | '); @@ -217,7 +216,6 @@ describe(`island web globals (scriptc-only${sanitize ? ", sanitized" : ""})`, () expect(out).toBe( [ "ReadableStream.tee is not supported in the scriptc island", - "ReadableStream.pipeTo is not supported in the scriptc island (use pipeThrough or a reader)", "BYOB readers are not supported in the scriptc island", "byte streams (type: 'bytes') are not supported in the scriptc island", ].join(" | "),