runtime: implement CompressionStream/DecompressionStream (#24757)

Closes https://github.com/oven-sh/bun/issues/1723
Closes https://github.com/oven-sh/bun/pull/22214
Closes https://github.com/oven-sh/bun/pull/24241

also supports the `"brotli"` and `"zstd"` formats

<img width="1244" height="547" alt="image"
src="https://github.com/user-attachments/assets/aecf4489-29ad-411d-9f6b-3bee50ed1b27"
/>
This commit is contained in:
Meghan Denny
2025-11-20 17:14:37 -08:00
committed by GitHub
parent b72ba31441
commit 5702b39ef1
20 changed files with 699 additions and 12 deletions

View File

@@ -13,7 +13,4 @@ export function run(opts = {}) {
}
export const bench = Mitata.bench;
export function group(_name, fn) {
return Mitata.group(fn);
}
export const group = Mitata.group;

View File

@@ -0,0 +1,156 @@
import { bench, group, run } from "../runner.mjs";
const runAll = !process.argv.includes("--simple");
const small = new Uint8Array(1024);
const medium = new Uint8Array(1024 * 100);
const large = new Uint8Array(1024 * 1024);
for (let i = 0; i < large.length; i++) {
const value = Math.floor(Math.sin(i / 100) * 128 + 128);
if (i < small.length) small[i] = value;
if (i < medium.length) medium[i] = value;
large[i] = value;
}
const format = new Intl.NumberFormat("en-US", { notation: "compact", unit: "byte" });
async function compress(data, format) {
const cs = new CompressionStream(format);
const writer = cs.writable.getWriter();
const reader = cs.readable.getReader();
writer.write(data);
writer.close();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
async function decompress(data, format) {
const ds = new DecompressionStream(format);
const writer = ds.writable.getWriter();
const reader = ds.readable.getReader();
writer.write(data);
writer.close();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
async function roundTrip(data, format) {
const compressed = await compress(data, format);
return await decompress(compressed, format);
}
const formats = ["deflate", "gzip", "deflate-raw"];
if (runAll) formats.push("brotli", "zstd");
// Small data benchmarks (1KB)
group(`CompressionStream ${format.format(small.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(small, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});
// Medium data benchmarks (100KB)
group(`CompressionStream ${format.format(medium.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(medium, fmt));
} catch (e) {}
}
});
// Large data benchmarks (1MB)
group(`CompressionStream ${format.format(large.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(large, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});
const compressedData = {};
for (const fmt of formats) {
try {
compressedData[fmt] = {
small: await compress(small, fmt),
medium: await compress(medium, fmt),
large: await compress(large, fmt),
};
} catch (e) {
// Skip unsupported formats
}
}
group(`DecompressionStream ${format.format(small.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].small, fmt));
}
}
});
group(`DecompressionStream ${format.format(medium.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].medium, fmt));
}
}
});
group(`DecompressionStream ${format.format(large.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].large, fmt));
}
}
});
group(`roundtrip ${format.format(large.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await roundTrip(large, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});
await run();

View File

@@ -245,7 +245,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
### [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)
🔴 Not implemented.
🟢 Fully implemented.
### [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)
@@ -273,7 +273,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
### [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream)
🔴 Not implemented.
🟢 Fully implemented.
### [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)

View File

@@ -0,0 +1,139 @@
#include "config.h"
#include "JSCompressionStream.h"
#include "JSDOMBuiltinConstructor.h"
#include "JSDOMGlobalObjectInlines.h"
#include "WebCoreJSClientData.h"
#include "WebCoreJSBuiltins.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/JSCInlines.h>
namespace WebCore {
using namespace JSC;
static JSC_DECLARE_CUSTOM_GETTER(jsCompressionStreamConstructor);
class JSCompressionStreamPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSCompressionStreamPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
{
JSCompressionStreamPrototype* ptr = new (NotNull, JSC::allocateCell<JSCompressionStreamPrototype>(vm)) JSCompressionStreamPrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCompressionStreamPrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
private:
JSCompressionStreamPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
: JSC::JSNonFinalObject(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCompressionStreamPrototype, JSCompressionStreamPrototype::Base);
using JSCompressionStreamDOMConstructor = JSDOMBuiltinConstructor<JSCompressionStream>;
template<> const ClassInfo JSCompressionStreamDOMConstructor::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStreamDOMConstructor) };
template<> JSValue JSCompressionStreamDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
{
UNUSED_PARAM(vm);
return globalObject.functionPrototype();
}
template<> void JSCompressionStreamDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
{
putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
JSString* nameString = jsNontrivialString(vm, "CompressionStream"_s);
m_originalName.set(vm, this, nameString);
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->prototype, JSCompressionStream::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
}
template<> FunctionExecutable* JSCompressionStreamDOMConstructor::initializeExecutable(VM& vm)
{
return compressionStreamInitializeCompressionStreamCodeGenerator(vm);
}
static const HashTableValue JSCompressionStreamPrototypeTableValues[] = {
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsCompressionStreamConstructor, 0 } },
{ "readable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, compressionStreamReadableCodeGenerator, 0 } },
{ "writable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, compressionStreamWritableCodeGenerator, 0 } },
};
const ClassInfo JSCompressionStreamPrototype::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStreamPrototype) };
void JSCompressionStreamPrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSCompressionStream::info(), JSCompressionStreamPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
const ClassInfo JSCompressionStream::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStream) };
JSC::GCClient::IsoSubspace* JSCompressionStream::subspaceForImpl(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSCompressionStream, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForCompressionStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCompressionStream = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForCompressionStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForCompressionStream = std::forward<decltype(space)>(space); });
}
JSCompressionStream::JSCompressionStream(Structure* structure, JSDOMGlobalObject& globalObject)
: JSDOMObject(structure, globalObject)
{
}
void JSCompressionStream::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
JSObject* JSCompressionStream::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
auto* structure = JSCompressionStreamPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
structure->setMayBePrototype(true);
return JSCompressionStreamPrototype::create(vm, &globalObject, structure);
}
JSObject* JSCompressionStream::prototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return getDOMPrototype<JSCompressionStream>(vm, globalObject);
}
JSValue JSCompressionStream::getConstructor(VM& vm, const JSGlobalObject* globalObject)
{
return getDOMConstructor<JSCompressionStreamDOMConstructor, DOMConstructorID::CompressionStream>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
}
JSC_DEFINE_CUSTOM_GETTER(jsCompressionStreamConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* prototype = jsDynamicCast<JSCompressionStreamPrototype*>(JSValue::decode(thisValue));
if (!prototype)
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(JSCompressionStream::getConstructor(vm, lexicalGlobalObject));
}
} // namespace WebCore

View File

@@ -0,0 +1,40 @@
#pragma once
#include "JSDOMWrapper.h"
namespace WebCore {
class JSCompressionStream : public JSDOMObject {
public:
using Base = JSDOMObject;
static JSCompressionStream* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject)
{
JSCompressionStream* ptr = new (NotNull, JSC::allocateCell<JSCompressionStream>(globalObject->vm())) JSCompressionStream(structure, *globalObject);
ptr->finishCreation(globalObject->vm());
return ptr;
}
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
JSCompressionStream(JSC::Structure*, JSDOMGlobalObject&);
void finishCreation(JSC::VM&);
};
} // namespace WebCore

View File

@@ -0,0 +1,139 @@
#include "config.h"
#include "JSDecompressionStream.h"
#include "JSDOMBuiltinConstructor.h"
#include "JSDOMGlobalObjectInlines.h"
#include "WebCoreJSClientData.h"
#include "WebCoreJSBuiltins.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/JSCInlines.h>
namespace WebCore {
using namespace JSC;
static JSC_DECLARE_CUSTOM_GETTER(jsDecompressionStreamConstructor);
class JSDecompressionStreamPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSDecompressionStreamPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
{
JSDecompressionStreamPrototype* ptr = new (NotNull, JSC::allocateCell<JSDecompressionStreamPrototype>(vm)) JSDecompressionStreamPrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDecompressionStreamPrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
private:
JSDecompressionStreamPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
: JSC::JSNonFinalObject(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDecompressionStreamPrototype, JSDecompressionStreamPrototype::Base);
using JSDecompressionStreamDOMConstructor = JSDOMBuiltinConstructor<JSDecompressionStream>;
template<> const ClassInfo JSDecompressionStreamDOMConstructor::s_info = { "DecompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDecompressionStreamDOMConstructor) };
template<> JSValue JSDecompressionStreamDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
{
UNUSED_PARAM(vm);
return globalObject.functionPrototype();
}
template<> void JSDecompressionStreamDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
{
putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
JSString* nameString = jsNontrivialString(vm, "DecompressionStream"_s);
m_originalName.set(vm, this, nameString);
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->prototype, JSDecompressionStream::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
}
template<> FunctionExecutable* JSDecompressionStreamDOMConstructor::initializeExecutable(VM& vm)
{
return decompressionStreamInitializeDecompressionStreamCodeGenerator(vm);
}
static const HashTableValue JSDecompressionStreamPrototypeTableValues[] = {
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsDecompressionStreamConstructor, 0 } },
{ "readable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, decompressionStreamReadableCodeGenerator, 0 } },
{ "writable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, decompressionStreamWritableCodeGenerator, 0 } },
};
const ClassInfo JSDecompressionStreamPrototype::s_info = { "DecompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDecompressionStreamPrototype) };
void JSDecompressionStreamPrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSDecompressionStream::info(), JSDecompressionStreamPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
const ClassInfo JSDecompressionStream::s_info = { "DecompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDecompressionStream) };
JSC::GCClient::IsoSubspace* JSDecompressionStream::subspaceForImpl(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSDecompressionStream, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForDecompressionStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForDecompressionStream = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForDecompressionStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForDecompressionStream = std::forward<decltype(space)>(space); });
}
JSDecompressionStream::JSDecompressionStream(Structure* structure, JSDOMGlobalObject& globalObject)
: JSDOMObject(structure, globalObject)
{
}
void JSDecompressionStream::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
JSObject* JSDecompressionStream::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
auto* structure = JSDecompressionStreamPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
structure->setMayBePrototype(true);
return JSDecompressionStreamPrototype::create(vm, &globalObject, structure);
}
JSObject* JSDecompressionStream::prototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return getDOMPrototype<JSDecompressionStream>(vm, globalObject);
}
JSValue JSDecompressionStream::getConstructor(VM& vm, const JSGlobalObject* globalObject)
{
return getDOMConstructor<JSDecompressionStreamDOMConstructor, DOMConstructorID::DecompressionStream>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
}
JSC_DEFINE_CUSTOM_GETTER(jsDecompressionStreamConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* prototype = jsDynamicCast<JSDecompressionStreamPrototype*>(JSValue::decode(thisValue));
if (!prototype)
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(JSDecompressionStream::getConstructor(vm, lexicalGlobalObject));
}
} // namespace WebCore

View File

@@ -0,0 +1,40 @@
#pragma once
#include "JSDOMWrapper.h"
namespace WebCore {
class JSDecompressionStream : public JSDOMObject {
public:
using Base = JSDOMObject;
static JSDecompressionStream* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject)
{
JSDecompressionStream* ptr = new (NotNull, JSC::allocateCell<JSDecompressionStream>(globalObject->vm())) JSDecompressionStream(structure, *globalObject);
ptr->finishCreation(globalObject->vm());
return ptr;
}
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
JSDecompressionStream(JSC::Structure*, JSDOMGlobalObject&);
void finishCreation(JSC::VM&);
};
} // namespace WebCore

View File

@@ -78,6 +78,8 @@
#include "JSAbortAlgorithm.h"
#include "JSAbortController.h"
#include "JSAbortSignal.h"
#include "JSCompressionStream.h"
#include "JSDecompressionStream.h"
#include "JSBroadcastChannel.h"
#include "JSBuffer.h"
#include "JSBufferList.h"
@@ -962,9 +964,11 @@ WEBCORE_GENERATED_CONSTRUCTOR_GETTER(AbortSignal);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(BroadcastChannel);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(ByteLengthQueuingStrategy)
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CloseEvent);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CompressionStream);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CountQueuingStrategy)
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CryptoKey);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CustomEvent);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DecompressionStream);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DOMException);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DOMFormData);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DOMURL);

View File

@@ -21,9 +21,9 @@
setInterval functionSetInterval Function 1
setTimeout functionSetTimeout Function 1
structuredClone WebCore::jsFunctionStructuredClone Function 2
global GlobalObject_getGlobalThis PropertyCallback
Bun GlobalObject::m_bunObject CellProperty|DontDelete|ReadOnly
File GlobalObject::m_JSDOMFileConstructor CellProperty
crypto GlobalObject::m_cryptoObject CellProperty
@@ -48,9 +48,11 @@
BroadcastChannel BroadcastChannelConstructorCallback PropertyCallback
ByteLengthQueuingStrategy ByteLengthQueuingStrategyConstructorCallback PropertyCallback
CloseEvent CloseEventConstructorCallback PropertyCallback
CompressionStream CompressionStreamConstructorCallback PropertyCallback
CountQueuingStrategy CountQueuingStrategyConstructorCallback PropertyCallback
CryptoKey CryptoKeyConstructorCallback PropertyCallback
CustomEvent CustomEventConstructorCallback PropertyCallback
DecompressionStream DecompressionStreamConstructorCallback PropertyCallback
DOMException DOMExceptionConstructorCallback PropertyCallback
ErrorEvent ErrorEventConstructorCallback PropertyCallback
Event EventConstructorCallback PropertyCallback

View File

@@ -8,4 +8,6 @@ export default [
["WritableStream", "JSWritableStream.h"],
["TransformStream", "JSTransformStream.h"],
["ArrayBuffer"],
["CompressionStream", "JSCompressionStream.h"],
["DecompressionStream", "JSDecompressionStream.h"],
];

View File

@@ -283,6 +283,8 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForReadableStreamSource;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTransformStream;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTransformStreamDefaultController;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCompressionStream;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDecompressionStream;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWritableStream;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWritableStreamDefaultController;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWritableStreamDefaultWriter;

View File

@@ -198,6 +198,8 @@ enum class DOMConstructorID : uint16_t {
ReadableStreamSource,
TransformStream,
TransformStreamDefaultController,
CompressionStream,
DecompressionStream,
WritableStream,
WritableStreamDefaultController,
WritableStreamDefaultWriter,
@@ -860,7 +862,7 @@ enum class DOMConstructorID : uint16_t {
EventEmitter,
};
static constexpr unsigned numberOfDOMConstructorsBase = 846;
static constexpr unsigned numberOfDOMConstructorsBase = 848;
static constexpr unsigned bunExtraConstructors = 3;

View File

@@ -266,6 +266,8 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForReadableStreamSource;
std::unique_ptr<IsoSubspace> m_subspaceForTransformStream;
std::unique_ptr<IsoSubspace> m_subspaceForTransformStreamDefaultController;
std::unique_ptr<IsoSubspace> m_subspaceForCompressionStream;
std::unique_ptr<IsoSubspace> m_subspaceForDecompressionStream;
std::unique_ptr<IsoSubspace> m_subspaceForWritableStream;
std::unique_ptr<IsoSubspace> m_subspaceForWritableStreamDefaultController;
std::unique_ptr<IsoSubspace> m_subspaceForWritableStreamDefaultWriter;

View File

@@ -532,7 +532,7 @@ declare module "module" {
`;
for (const [name] of jsclasses) {
dts += `\ndeclare function $inherits${name}(value: any): boolean;`;
dts += `\ndeclare function $inherits${name}(value: any): value is ${name};`;
}
return dts;

View File

@@ -0,0 +1,33 @@
export function initializeCompressionStream(this, format) {
const zlib = require("node:zlib");
const stream = require("node:stream");
const builders = {
"deflate": zlib.createDeflate,
"deflate-raw": zlib.createDeflateRaw,
"gzip": zlib.createGzip,
"brotli": zlib.createBrotliCompress,
"zstd": zlib.createZstdCompress,
};
if (!(format in builders))
throw $ERR_INVALID_ARG_VALUE("format", format, "must be one of: " + Object.keys(builders).join(", "));
const handle = builders[format]();
$putByIdDirectPrivate(this, "readable", stream.Readable.toWeb(handle));
$putByIdDirectPrivate(this, "writable", stream.Writable.toWeb(handle));
return this;
}
$getter;
export function readable(this) {
if (!$inheritsCompressionStream(this)) throw $makeGetterTypeError("CompressionStream", "readable");
return $getByIdDirectPrivate(this, "readable");
}
$getter;
export function writable(this) {
if (!$inheritsCompressionStream(this)) throw $makeGetterTypeError("CompressionStream", "writable");
return $getByIdDirectPrivate(this, "writable");
}

View File

@@ -0,0 +1,33 @@
export function initializeDecompressionStream(this, format) {
const zlib = require("node:zlib");
const stream = require("node:stream");
const builders = {
"deflate": zlib.createInflate,
"deflate-raw": zlib.createInflateRaw,
"gzip": zlib.createGunzip,
"brotli": zlib.createBrotliDecompress,
"zstd": zlib.createZstdDecompress,
};
if (!(format in builders))
throw $ERR_INVALID_ARG_VALUE("format", format, "must be one of: " + Object.keys(builders).join(", "));
const handle = builders[format]();
$putByIdDirectPrivate(this, "readable", stream.Readable.toWeb(handle));
$putByIdDirectPrivate(this, "writable", stream.Writable.toWeb(handle));
return this;
}
$getter;
export function readable(this) {
if (!$inheritsDecompressionStream(this)) throw $makeGetterTypeError("DecompressionStream", "readable");
return $getByIdDirectPrivate(this, "readable");
}
$getter;
export function writable(this) {
if (!$inheritsDecompressionStream(this)) throw $makeGetterTypeError("DecompressionStream", "writable");
return $getByIdDirectPrivate(this, "writable");
}

View File

@@ -15,6 +15,6 @@ export default {
CountQueuingStrategy,
TextEncoderStream,
TextDecoderStream,
CompressionStream: undefined,
DecompressionStream: undefined,
CompressionStream,
DecompressionStream,
};

View File

@@ -59,6 +59,8 @@ expectType(node_stream.blob()).is<Promise<Blob>>();
Bun.file("./foo.csv").stream().pipeThrough(new TextDecoderStream()).pipeThrough(new TextEncoderStream());
Bun.file("./foo.csv").stream().pipeThrough(new CompressionStream("gzip")).pipeThrough(new DecompressionStream("gzip"));
Bun.file("./foo.csv")
.stream()
.pipeThrough(new TextDecoderStream())

View File

@@ -0,0 +1,24 @@
'use strict';
require('../common');
const assert = require('assert');
const webstreams = require('stream/web');
assert.strictEqual(ReadableStream, webstreams.ReadableStream);
assert.strictEqual(ReadableStreamDefaultReader, webstreams.ReadableStreamDefaultReader);
assert.strictEqual(ReadableStreamBYOBReader, webstreams.ReadableStreamBYOBReader);
assert.strictEqual(ReadableStreamBYOBRequest, webstreams.ReadableStreamBYOBRequest);
assert.strictEqual(ReadableByteStreamController, webstreams.ReadableByteStreamController);
assert.strictEqual(ReadableStreamDefaultController, webstreams.ReadableStreamDefaultController);
assert.strictEqual(TransformStream, webstreams.TransformStream);
assert.strictEqual(TransformStreamDefaultController, webstreams.TransformStreamDefaultController);
assert.strictEqual(WritableStream, webstreams.WritableStream);
assert.strictEqual(WritableStreamDefaultWriter, webstreams.WritableStreamDefaultWriter);
assert.strictEqual(WritableStreamDefaultController, webstreams.WritableStreamDefaultController);
assert.strictEqual(ByteLengthQueuingStrategy, webstreams.ByteLengthQueuingStrategy);
assert.strictEqual(CountQueuingStrategy, webstreams.CountQueuingStrategy);
assert.strictEqual(TextEncoderStream, webstreams.TextEncoderStream);
assert.strictEqual(TextDecoderStream, webstreams.TextDecoderStream);
assert.strictEqual(CompressionStream, webstreams.CompressionStream);
assert.strictEqual(DecompressionStream, webstreams.DecompressionStream);

View File

@@ -0,0 +1,70 @@
// Flags: --no-warnings
'use strict';
const common = require('../common');
const {
CompressionStream,
DecompressionStream,
} = require('stream/web');
const assert = require('assert');
const dec = new TextDecoder();
async function test(format) {
const gzip = new CompressionStream(format);
const gunzip = new DecompressionStream(format);
assert.strictEqual(gzip[Symbol.toStringTag], 'CompressionStream');
assert.strictEqual(gunzip[Symbol.toStringTag], 'DecompressionStream');
gzip.readable.pipeTo(gunzip.writable).then(common.mustCall());
const reader = gunzip.readable.getReader();
const writer = gzip.writable.getWriter();
const compressed_data = [];
const reader_function = ({ value, done }) => {
if (value)
compressed_data.push(value);
if (!done)
return reader.read().then(reader_function);
assert.strictEqual(dec.decode(Buffer.concat(compressed_data)), 'hello');
};
const reader_promise = reader.read().then(reader_function);
await Promise.all([
reader_promise,
reader_promise.then(() => reader.read().then(({ done }) => assert(done))),
writer.write('hello'),
writer.close(),
]);
}
Promise.all(['gzip', 'deflate', 'deflate-raw', 'brotli', 'zstd'].map((i) => test(i))).then(common.mustCall());
[1, 'hello', false, {}].forEach((i) => {
assert.throws(() => new CompressionStream(i), {
code: 'ERR_INVALID_ARG_VALUE',
});
assert.throws(() => new DecompressionStream(i), {
code: 'ERR_INVALID_ARG_VALUE',
});
});
assert.throws(
() => Reflect.get(CompressionStream.prototype, 'readable', {}), {
name: 'TypeError',
});
assert.throws(
() => Reflect.get(CompressionStream.prototype, 'writable', {}), {
name: 'TypeError',
});
assert.throws(
() => Reflect.get(DecompressionStream.prototype, 'readable', {}), {
name: 'TypeError',
});
assert.throws(
() => Reflect.get(DecompressionStream.prototype, 'writable', {}), {
name: 'TypeError',
});