From 64df1de435f72dbb8bac4e29c027d682c6f5f5ac Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 18 Dec 2024 04:59:07 -0800 Subject: [PATCH] more --- .../BunReadableStreamDefaultController.cpp | 17 +- .../BunReadableStreamDefaultController.h | 7 +- .../BunReadableStreamDefaultReader.cpp | 47 +- .../bindings/BunReadableStreamDefaultReader.h | 2 + src/bun.js/bindings/BunTransformStream.cpp | 361 ++++++++++++ src/bun.js/bindings/BunTransformStream.h | 63 +++ .../BunTransformStreamDefaultController.cpp | 237 ++++++++ .../BunTransformStreamDefaultController.h | 73 +++ src/bun.js/bindings/BunWritableStream.cpp | 525 ++++++++++++++++++ src/bun.js/bindings/BunWritableStream.h | 144 +++++ .../BunWritableStreamDefaultController.cpp | 264 +++++++++ .../BunWritableStreamDefaultController.h | 94 ++++ .../BunWritableStreamDefaultWriter.cpp | 427 ++++++++++++++ .../bindings/BunWritableStreamDefaultWriter.h | 57 ++ 14 files changed, 2288 insertions(+), 30 deletions(-) create mode 100644 src/bun.js/bindings/BunTransformStream.cpp create mode 100644 src/bun.js/bindings/BunTransformStream.h create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultController.cpp create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultController.h create mode 100644 src/bun.js/bindings/BunWritableStream.cpp create mode 100644 src/bun.js/bindings/BunWritableStream.h create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultController.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultController.h create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriter.h diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index a6df2d14a9..81d0754b5f 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -7,10 +7,25 @@ #include "BunReadableStream.h" #include #include +#include "BunReadableStreamDefaultReader.h" +#include "DOMIsoSubspaces.h" +#include "BunClientData.h" + namespace Bun { using namespace JSC; +template +JSC::GCClient::IsoSubspace* JSReadableStreamDefaultController::subspaceFor(JSC::VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSReadableStreamDefaultController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSReadableStreamDefaultController = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSReadableStreamDefaultController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSReadableStreamDefaultController = std::forward(space); }); +} + JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -380,7 +395,7 @@ bool JSReadableStreamDefaultController::shouldCallPull() const return false; // Only pull if we need more chunks - if (reader->numReadRequests() == 0) + if (reader->length() == 0) return false; double desiredSize = m_strategyHWM - m_queueTotalSize; diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h index 1787249b23..7901594a3e 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -19,12 +19,7 @@ public: static constexpr unsigned StructureFlags = Base::StructureFlags; template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return &vm.plainObjectSpace(); - } + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); static JSReadableStreamDefaultController* create(JSC::VM&, JSC::Structure*, JSReadableStream* stream); static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp index 31fad3a734..2acf41a765 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp @@ -1,3 +1,4 @@ +#include "ErrorCode+List.h" #include "root.h" #include @@ -13,7 +14,7 @@ #include "BunReadableStreamDefaultController.h" #include #include "BunReadableStreamDefaultReader.h" - +#include "ErrorCode.h" namespace Bun { using namespace JSC; @@ -25,7 +26,7 @@ public: static JSReadableStreamDefaultReaderPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) { JSReadableStreamDefaultReaderPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderPrototype(vm, globalObject, structure); - ptr->finishCreation(vm, globalObject); + ptr->finishCreation(vm); return ptr; } @@ -47,17 +48,9 @@ private: { } - void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + void finishCreation(JSC::VM&); }; -JSReadableStreamDefaultReader* JSReadableStreamDefaultReader::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSReadableStream* stream) -{ - JSReadableStreamDefaultReader* reader = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReader(vm, structure); - reader->finishCreation(vm); - reader->m_stream.set(vm, reader, stream); - return reader; -} - // JSReadableStreamDefaultReader.cpp static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderClosedGetter); @@ -108,7 +101,7 @@ template void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& visitor) { auto* reader = jsCast(cell); - ASSERT_GC_OBJECT_INHERITS(reader, info()); + ASSERT_GC_OBJECT_INHERITS(reader, JSReadableStreamDefaultReader::info()); Base::visitChildren(reader, visitor); visitor.append(reader->m_stream); visitor.append(reader->m_readyPromise); @@ -118,11 +111,6 @@ void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& vis DEFINE_VISIT_CHILDREN(JSReadableStreamDefaultReader); -void JSReadableStreamDefaultReader::destroy(JSCell* cell) -{ - static_cast(cell)->JSReadableStreamDefaultReader::~JSReadableStreamDefaultReader(); -} - void JSReadableStreamDefaultReader::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); @@ -143,7 +131,7 @@ void JSReadableStreamDefaultReader::releaseLock() return; // Release the stream's reader reference - m_stream->clearReader(); + m_stream->setReader(nullptr); detach(); } @@ -236,7 +224,7 @@ const ClassInfo JSReadableStreamDefaultReaderConstructor::s_info = { "ReadableSt void JSReadableStreamDefaultReaderConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReaderPrototype* prototype) { - Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAdditionMode::WithStructureTransition); putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); ASSERT(inherits(info())); } @@ -245,7 +233,8 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstr { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - return JSValue::encode(throwConstructorCannotBeCalledAsFunctionTypeError(globalObject, scope, "ReadableStreamDefaultReader")); + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "ReadableStreamDefaultReader constructor cannot be called as a function"_s); + return {}; } JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) @@ -268,8 +257,19 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstr return throwVMTypeError(globalObject, scope, "Cannot construct a ReadableStreamDefaultReader for a locked ReadableStream"_s); } - JSObject* newTarget = asObject(callFrame->newTarget()); - Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, getFunctionRealm(globalObject, newTarget)); + JSC::JSObject* newTarget = callFrame->newTarget().getObject(); + JSC::JSObject* constructor = callFrame->jsCallee(); + + auto* structure = defaultGlobalObject(globalObject)->readableStreamDefaultReaderStructure(); + + // TODO: double-check this. + if (!(!newTarget || newTarget == constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } RETURN_IF_EXCEPTION(scope, {}); JSReadableStreamDefaultReader* reader = JSReadableStreamDefaultReader::create(vm, globalObject, structure, stream); @@ -283,9 +283,10 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstr JSValue error = stream->storedError(); if (!error) error = jsUndefined(); + reader->readyPromise()->reject(globalObject, error); } else { - reader->readyPromise()->resolve(globalObject, jsUndefined()); + reader->readyPromise()->fulfillWithNonPromise(globalObject, jsUndefined()); } RELEASE_AND_RETURN(scope, JSValue::encode(reader)); diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.h b/src/bun.js/bindings/BunReadableStreamDefaultReader.h index 00aaf8b9e2..e494248985 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.h @@ -45,6 +45,8 @@ public: bool isActive() const { return !!m_stream; } void detach(); + unsigned length() const { return m_readRequests.get()->length(); } + // Implements ReadableStreamDefaultReader void releaseLock(); diff --git a/src/bun.js/bindings/BunTransformStream.cpp b/src/bun.js/bindings/BunTransformStream.cpp new file mode 100644 index 0000000000..8b2bccb2f8 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStream.cpp @@ -0,0 +1,361 @@ +#include "root.h" + +#include +#include +#include +#include "ErrorCode.h" +#include "BunTransformStream.h" +// #include "BunTransformStreamDefaultController.h" + +namespace Bun { + +using namespace JSC; + +// Prototype implementation +class JSTransformStreamPrototype final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + static JSTransformStreamPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSTransformStreamPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamPrototype, 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: + JSTransformStreamPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +// Constructor implementation +class JSTransformStreamConstructor final : public JSC::InternalFunction { + using Base = JSC::InternalFunction; + +public: + static JSTransformStreamConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamPrototype* prototype) + { + JSTransformStreamConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSTransformStreamConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamReadableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return throwVMTypeError(globalObject, scope, "Cannot get readable property of non-TransformStream"_s); + } + + ASSERT(thisObject->readable()); + return JSValue::encode(thisObject->readable()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamWritableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return throwVMTypeError(globalObject, scope, "Cannot get writable property of non-TransformStream"_s); + } + + ASSERT(thisObject->writable()); + return JSValue::encode(thisObject->writable()); +} + +// Implementing the constructor binding +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamConstructor, + (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* prototype = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(globalObject, scope, "Cannot get constructor for TransformStream"_s); + + return JSValue::encode(globalObject->transformStreamConstructor()); +} + +// All static properties for the prototype +static const HashTableValue JSTransformStreamPrototypeTableValues[] = { + { "readable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, + { "writable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } }, + { "constructor"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamConstructor, nullptr } } +}; + +// And now the constructor implementation +const ClassInfo JSTransformStreamConstructor::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStreamConstructor) +}; + +JSTransformStreamConstructor::JSTransformStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSTransformStreamConstructor::finishCreation(VM& vm, JSTransformStreamPrototype* prototype) +{ + Base::finishCreation(vm, 3, "TransformStream"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, + PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +// Constructor function implementation for both 'new TransformStream()' and TransformStream() call +JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* newTarget = asObject(callFrame->newTarget()); + Structure* structure = JSC::InternalFunction::createSubclassStructure( + globalObject, newTarget, globalObject->transformStreamStructure()); + RETURN_IF_EXCEPTION(scope, {}); + + // Extract constructor arguments per spec: + // new TransformStream(transformer = undefined, writableStrategy = {}, readableStrategy = {}) + JSValue transformerArg = callFrame->argument(0); + JSValue writableStrategyArg = callFrame->argument(1); + JSValue readableStrategyArg = callFrame->argument(2); + + // Create the underlying transform stream + JSTransformStream* transformStream = JSTransformStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + // Set up readable and writable sides with provided strategies + if (!writableStrategyArg.isUndefined()) { + // Apply writable strategy + JSValue highWaterMark = writableStrategyArg.get(globalObject, vm.propertyNames->highWaterMark); + JSValue size = writableStrategyArg.get(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, {}); + // ... apply strategy to writable side + } + + if (!readableStrategyArg.isUndefined()) { + // Apply readable strategy + JSValue highWaterMark = readableStrategyArg.get(globalObject, vm.propertyNames->highWaterMark); + JSValue size = readableStrategyArg.get(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, {}); + // ... apply strategy to readable side + } + + // Handle transformer setup if provided + if (!transformerArg.isUndefined()) { + JSValue transformFn = transformerArg.get(globalObject, vm.propertyNames->transform); + JSValue flushFn = transformerArg.get(globalObject, vm.propertyNames->flush); + JSValue startFn = transformerArg.get(globalObject, vm.propertyNames->start); + RETURN_IF_EXCEPTION(scope, {}); + + // Set up transform algorithm + if (!transformFn.isUndefined()) { + // Install transform function + } + + // Set up flush algorithm + if (!flushFn.isUndefined()) { + // Install flush function + } + + // Call start if present + if (!startFn.isUndefined()) { + JSValue controller = transformStream->controller(); + callData.thisValue = transformerArg; + MarkedArgumentBuffer args; + args.append(controller); + JSValue startResult = call(globalObject, startFn, callData, args); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + RELEASE_AND_RETURN(scope, JSValue::encode(transformStream)); +} + +JSC_DEFINE_HOST_FUNCTION(call, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "Cannot call TransformStream"_s); + return {}; +} + +const ClassInfo JSTransformStream::s_info = { + "TransformStream"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStream) +}; + +template +void JSTransformStream::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_readable); + visitor.append(thisObject->m_writable); + visitor.append(thisObject->m_controller); + visitor.append(thisObject->m_backpressureChangePromise); +} + +DEFINE_VISIT_CHILDREN(JSTransformStream); + +JSTransformStream::JSTransformStream(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +JSTransformStream::~JSTransformStream() +{ + // Clean up any resources +} + +void JSTransformStream::destroy(JSCell* cell) +{ + static_cast(cell)->JSTransformStream::~JSTransformStream(); +} + +void JSTransformStream::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + // Initialize readable/writable sides and controller + auto scope = DECLARE_THROW_SCOPE(vm); + + // Initialize with empty promises that will be fulfilled when ready + m_backpressureChangePromise.set(vm, JSPromise::create(vm, globalObject->promiseStructure())); + + // Set up the controller + m_controller.set(vm, JSTransformStreamDefaultController::create(vm, globalObject, globalObject->transformStreamDefaultControllerStructure())); + + RETURN_IF_EXCEPTION(scope, void()); +} + +void JSTransformStream::enqueue(VM& vm, JSGlobalObject* globalObject, JSValue chunk) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_controller) + m_controller->enqueue(vm, globalObject, chunk); + + RETURN_IF_EXCEPTION(scope, void()); +} + +void JSTransformStream::error(VM& vm, JSGlobalObject* globalObject, JSValue error) +{ + if (m_controller) + m_controller->error(vm, globalObject, error); +} + +void JSTransformStream::terminate(VM& vm, JSGlobalObject* globalObject) +{ + if (m_controller) + m_controller->terminate(vm, globalObject); +} + +JSTransformStream* JSTransformStream::create( + VM& vm, + JSGlobalObject* globalObject, + Structure* structure) +{ + JSTransformStream* ptr = new ( + NotNull, + JSC::allocateCell(vm)) JSTransformStream(vm, structure); + + ptr->finishCreation(vm, globalObject); + return ptr; +} + +// Prototype implementation (JSTransformStreamPrototype.cpp) +static const HashTableValue JSTransformStreamPrototypeTableValues[] = { + { "readable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, + { "writable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } } +}; + +const ClassInfo JSTransformStreamPrototype::s_info = { + "TransformStream"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStreamPrototype) +}; + +void JSTransformStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties( + vm, + JSTransformStream::info(), + JSTransformStreamPrototypeTableValues, + *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStream.h b/src/bun.js/bindings/BunTransformStream.h new file mode 100644 index 0000000000..e166a5469e --- /dev/null +++ b/src/bun.js/bindings/BunTransformStream.h @@ -0,0 +1,63 @@ +#include "root.h" + +#include +#include +#include + +namespace Bun { + +class JSTransformStream final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + // For garbage collection + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + static JSTransformStream* create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure); + + static JSC::Structure* createStructure( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::JSValue prototype) + { + return JSC::Structure::create( + vm, + globalObject, + prototype, + JSC::TypeInfo(JSC::JSType::ObjectType, StructureFlags), + info()); + } + + // Readable side operations + JSC::JSValue readable() { return m_readable.get(); } + JSC::JSValue writable() { return m_writable.get(); } + + // Direct C++ API + void enqueue(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue chunk); + void error(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue error); + void terminate(JSC::VM&, JSC::JSGlobalObject*); + + ~JSTransformStream(); + +private: + JSTransformStream(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + + // The readable and writable sides of the transform stream + JSC::WriteBarrier m_readable; + JSC::WriteBarrier m_writable; + JSC::WriteBarrier m_controller; + + // State flags + bool m_backpressure { false }; + JSC::WriteBarrier m_backpressureChangePromise; +}; + +} diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp new file mode 100644 index 0000000000..ddd42ce5af --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp @@ -0,0 +1,237 @@ +#include "root.h" + +#include "BunTransformStreamDefaultController.h" +#include "BunTransformStream.h" +#include "BunReadableStream.h" +#include "BunWritableStream.h" + +namespace Bun { + +using namespace JSC; + +class JSTransformStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + static JSTransformStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSTransformStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamDefaultControllerPrototype, 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: + JSTransformStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSTransformStreamDefaultControllerConstructor final : public JSC::InternalFunction { + using Base = JSC::InternalFunction; + +public: + static JSTransformStreamDefaultControllerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamDefaultControllerPrototype* prototype) + { + JSTransformStreamDefaultControllerConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultControllerConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSTransformStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamDefaultControllerPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +static const HashTableValue JSTransformStreamDefaultControllerPrototypeTableValues[] = { + { "enqueue"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerEnqueue, 1 } }, + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerError, 1 } }, + { "terminate"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerTerminate, 0 } }, + { "desiredSize"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamDefaultControllerDesiredSize, 0 } }, +}; + +const ClassInfo JSTransformStreamDefaultController::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultController) }; +const ClassInfo JSTransformStreamDefaultControllerConstructor::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerConstructor) }; + +const ClassInfo JSTransformStreamDefaultControllerPrototype::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerPrototype) }; +JSTransformStreamDefaultController* JSTransformStreamDefaultController::create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure, + JSTransformStream* transformStream) +{ + JSTransformStreamDefaultController* controller = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultController(vm, structure); + controller->finishCreation(vm, globalObject, transformStream); + return controller; +} + +void JSTransformStreamDefaultController::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStream* transformStream) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + m_stream.set(vm, this, transformStream); +} + +void JSTransformStreamDefaultControllerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + reifyStaticProperties(vm, info(), JSTransformStreamDefaultControllerPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void JSTransformStreamDefaultControllerConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamDefaultControllerPrototype* prototype) +{ + Base::finishCreation(vm, 2, "TransformStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); + ASSERT(inherits(info())); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); +} + +template +void JSTransformStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + visitor.append(thisObject->m_stream); + visitor.append(thisObject->m_flushPromise); + visitor.append(thisObject->m_transformAlgorithm); + visitor.append(thisObject->m_flushAlgorithm); +} + +DEFINE_VISIT_CHILDREN(JSTransformStreamDefaultController); + +bool JSTransformStreamDefaultController::enqueue(JSC::JSGlobalObject* globalObject, JSC::JSValue chunk) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Implementation following spec's TransformStreamDefaultControllerEnqueue + // This would integrate with the ReadableStream's controller to actually enqueue the chunk + // and handle backpressure + + return true; +} + +void JSTransformStreamDefaultController::error(JSC::JSGlobalObject* globalObject, JSC::JSValue error) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Implementation following spec's TransformStreamDefaultControllerError + // This would propagate the error to both the readable and writable sides +} + +void JSTransformStreamDefaultController::terminate(JSC::JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Implementation following spec's TransformStreamDefaultControllerTerminate + // This would close the readable side and error the writable side +} + +// JavaScript binding implementations + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + // Return the desired size per spec + return JSValue::encode(jsNumber(0)); // Placeholder +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + JSValue chunk = callFrame->argument(0); + + if (!controller->enqueue(globalObject, chunk)) + return JSValue::encode(jsUndefined()); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerError, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + controller->error(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + controller->terminate(globalObject); + return JSValue::encode(jsUndefined()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.h b/src/bun.js/bindings/BunTransformStreamDefaultController.h new file mode 100644 index 0000000000..f49ac0f4f5 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.h @@ -0,0 +1,73 @@ +#include "root.h" + +namespace Bun { + +using namespace JSC; + +// JSTransformStreamDefaultController.h +class JSTransformStream; + +class JSTransformStreamDefaultController final : public JSC::JSDestructibleObject { + using Base = JSC::JSDestructibleObject; + +public: + static constexpr bool needsDestruction = true; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForTransformStreamController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTransformStreamController = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForTransformStreamController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForTransformStreamController = std::forward(space); }); + } + + static JSTransformStreamDefaultController* create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure, + JSTransformStream* transformStream); + + 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()); + } + + // C++ methods for direct manipulation + bool enqueue(JSC::JSGlobalObject*, JSC::JSValue chunk); + void error(JSC::JSGlobalObject*, JSC::JSValue error); + void terminate(JSC::JSGlobalObject*); + JSC::JSValue desiredSize(JSC::JSGlobalObject*); + + // For garbage collection + DECLARE_VISIT_CHILDREN; + +private: + JSTransformStreamDefaultController(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStream* transformStream); + + // Member variables + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_flushPromise; + JSC::WriteBarrier m_transformAlgorithm; + JSC::WriteBarrier m_flushAlgorithm; +}; + +// Function declarations for JavaScript bindings +JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize); +JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue); +JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerError); +JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate); + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp new file mode 100644 index 0000000000..82edc1da1d --- /dev/null +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -0,0 +1,525 @@ +#include "root.h" + +#include "BunWritableStream.h" + +namespace Bun { + +using namespace JSC; + +// JSWritableStreamPrototype bindings +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.abort called on non-WritableStream object"_s); + + JSValue reason = callFrame->argument(0); + return JSValue::encode(stream->abort(globalObject, reason)); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_close, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.close called on non-WritableStream object"_s); + + return JSValue::encode(stream->close(globalObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.getWriter called on non-WritableStream object"_s); + + if (stream->isLocked()) + return throwVMTypeError(globalObject, scope, "Cannot get writer for locked WritableStream"_s); + + Structure* writerStructure = globalObject->writableStreamDefaultWriterStructure(); + auto* writer = JSWritableStreamDefaultWriter::create(vm, globalObject, writerStructure, stream); + RETURN_IF_EXCEPTION(scope, {}); + + stream->setWriter(vm, writer); + return JSValue::encode(writer); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeLockedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.locked called on non-WritableStream object"_s); + + return JSValue::encode(jsBoolean(stream->isLocked())); +} + +// Static hash table of properties +static const HashTableValue JSWritableStreamPrototypeTableValues[] = { + { "abort"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_abort, 1 } }, + { "close"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_close, 0 } }, + { "getWriter"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_getWriter, 0 } }, + { "locked"_s, + static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamPrototypeLockedGetter, nullptr } } +}; + +class JSWritableStreamPrototype final : public JSNonFinalObject { +public: + using Base = JSNonFinalObject; + + static JSWritableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure); + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + auto* structure = Base::createStructure(vm, globalObject, prototype); + structure->setMayBePrototype(true); + return structure; + } + + DECLARE_INFO; + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamPrototype, Base); + return &vm.plainObjectSpace(); + } + +private: + JSWritableStreamPrototype(VM& vm, Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(VM& vm, JSGlobalObject* globalObject); +}; + +class JSWritableStreamConstructor final : public InternalFunction { +public: + using Base = InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSWritableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSWritableStreamPrototype*); + DECLARE_INFO; + + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + if constexpr (mode == SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward(space); }); + } + + static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); + static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); + static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); + +private: + JSWritableStreamConstructor(VM& vm, Structure* structure); + void finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamPrototype* prototype) + { + Base::finishCreation(vm, 1, "WritableStream"_s, PropertyAdditionMode::WithStructureTransition); + this->putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, 0); + } +}; + +// Prototype Implementation +const ClassInfo JSWritableStreamPrototype::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamPrototype) }; + +JSWritableStreamPrototype* JSWritableStreamPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + auto* prototype = new (NotNull, allocateCell(vm)) JSWritableStreamPrototype(vm, structure); + prototype->finishCreation(vm, globalObject); + return prototype; +} + +void JSWritableStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSWritableStream::info(), JSWritableStreamPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// Constructor Implementation +const ClassInfo JSWritableStreamConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamConstructor) }; + +JSWritableStreamConstructor::JSWritableStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +JSWritableStreamConstructor* JSWritableStreamConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamPrototype* prototype) +{ + JSWritableStreamConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue newTarget = callFrame->newTarget(); + if (newTarget.isUndefined()) + return throwVMTypeError(globalObject, scope, "WritableStream constructor must be called with 'new'"_s); + + JSObject* underlyingSink = callFrame->argument(0).getObject(); + JSValue strategy = callFrame->argument(1); + + JSObject* constructor = asObject(newTarget); + Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, globalObject->writableStreamStructure()); + RETURN_IF_EXCEPTION(scope, {}); + + JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + // Initialize with underlying sink if provided + if (underlyingSink) { + // Set up controller with underlying sink... + auto controller = JSWritableStreamDefaultController::create(vm, globalObject, stream, underlyingSink); + RETURN_IF_EXCEPTION(scope, {}); + stream->setController(controller); + } + + return JSValue::encode(stream); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrivateConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + // Similar to above but for internal usage + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Structure* structure = globalObject->writableStreamStructure(); + JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + return JSValue::encode(stream); +} + +// WritableStream implementation +const ClassInfo JSWritableStream::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStream) }; + +JSWritableStream::JSWritableStream(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSWritableStream::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSWritableStream* JSWritableStream::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + JSWritableStream* stream = new (NotNull, allocateCell(vm)) + JSWritableStream(vm, structure); + stream->finishCreation(vm); + return stream; +} + +void JSWritableStream::destroy(JSCell* cell) +{ + static_cast(cell)->JSWritableStream::~JSWritableStream(); +} + +template +void JSWritableStream::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + visitor.append(thisObject->m_controller); + visitor.append(thisObject->m_writer); + visitor.append(thisObject->m_closeRequest); + visitor.append(thisObject->m_inFlightWriteRequest); + visitor.append(thisObject->m_inFlightCloseRequest); + visitor.append(thisObject->m_storedError); +} + +DEFINE_VISIT_CHILDREN(JSWritableStream); + +Structure* JSWritableStream::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, + TypeInfo(ObjectType, StructureFlags), info()); +} + +bool JSWritableStream::isLocked() const +{ + return !!m_writer; +} + +JSValue JSWritableStream::error(JSGlobalObject* globalObject, JSValue error) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_state != State::Writable) + return jsUndefined(); + + m_state = State::Errored; + m_storedError.set(vm, this, error); + + if (m_writer) + m_writer->error(globalObject, error); + + RELEASE_AND_RETURN(scope, jsUndefined()); +} + +namespace Operations { + +JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSPromise* promise = jsDynamicCast(callFrame->argument(1)); + promise->fulfillWithNonPromise(globalObject, jsUndefined()); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSPromise* promise = jsDynamicCast(callFrame->argument(1)); + promise->reject(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +static void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason) +{ + // 1. Assert: stream.[[storedError]] is undefined. + ASSERT(!stream->storedError() || stream->storedError().isUndefined()); + + // 2. Assert: stream.[[state]] is "writable". + ASSERT(stream->state() == JSWritableStream::State::Writable); + + // 3. Let controller be stream.[[writableStreamController]]. + auto* controller = stream->controller(); + ASSERT(controller); + + // 4. Set stream.[[state]] to "erroring". + stream->setState(JSWritableStream::State::Erroring); + + // 5. Set stream.[[storedError]] to reason. + stream->setStoredError(reason); + + // 6. Let writer be stream.[[writer]]. + auto* writer = stream->writer(); + + // 7. If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason). + if (writer) + WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + + // 8. If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true, + // perform ! WritableStreamFinishErroring(stream). + if (!stream->hasOperationMarkedInFlight() && controller->started()) + WritableStreamFinishErroring(stream); +} + +static void WritableStreamFinishErroring(JSWritableStream* stream) +{ + // 1. Assert: stream.[[state]] is "erroring". + ASSERT(stream->state() == JSWritableStream::State::Erroring); + + // 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is false. + ASSERT(!stream->hasOperationMarkedInFlight()); + + // 3. Set stream.[[state]] to "errored". + stream->setState(JSWritableStream::State::Errored); + + // 4. Perform ! WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]). + stream->controller()->errorSteps(); + + JSValue storedError = stream->storedError(); + + // 5. Let writer be stream.[[writer]]. + auto* writer = stream->writer(); + + // 6. If writer is not undefined, + if (writer) { + // a. Let writeRequests be writer.[[writeRequests]]. + // b. Set writer.[[writeRequests]] to an empty List. + // c. For each writeRequest of writeRequests, + // 1. Reject writeRequest with stream.[[storedError]]. + writer->rejectWriteRequests(storedError); + } + + JSPromise* abortPromise = stream->pendingAbortRequestPromise(); + + // 7. Let pendingAbortRequest be stream.[[pendingAbortRequest]]. + // 8. If pendingAbortRequest is undefined, return. + if (!abortPromise) + return; + + // 9. Set stream.[[pendingAbortRequest]] to undefined. + + JSValue abortReason = stream->pendingAbortRequestReason(); + bool wasAlreadyErroring = stream->wasAlreadyErroring(); + stream->clearPendingAbortRequest(); + + // 10. If pendingAbortRequest.[[wasAlreadyErroring]] is true, + if (wasAlreadyErroring) { + // a. Reject pendingAbortRequest.[[promise]] with pendingAbortRequest.[[reason]]. + abortPromise->(abortReason); + // b. Return. + return; + } + + // 11. Let abortAlgorithm be stream.[[writableStreamController]].[[abortAlgorithm]]. + // 12. Let result be the result of performing abortAlgorithm with argument pendingAbortRequest.[[reason]]. + JSValue result = stream->controller()->performAbortAlgorithm(abortReason); + + // 13. Upon fulfillment of result, + // a. Resolve pendingAbortRequest.[[promise]] with undefined. + // 14. Upon rejection of result with reason r, + // a. Reject pendingAbortRequest.[[promise]] with r. + if (JSPromise* resultPromise = jsDynamicCast(result)) { + Bun::performPromiseThen(vm, globalObject, resultPromise, + jsFunctionResolveAbortPromiseWithUndefined, + jsFunctionRejectAbortPromiseWithReason); + } else { + // If not a promise, treat as fulfilled + abortPromise->resolve(jsUndefined()); + } +} + +static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStream* stream, JSValue reason) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Let state be stream.[[state]]. + auto state = stream->state(); + + // 2. If state is "closed" or state is "errored", return a promise resolved with undefined. + if (state == JSWritableStream::State::Closed || state == JSWritableStream::State::Errored) { + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + } + + // 3. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]].[[promise]]. + if (stream->pendingAbortRequest()) + return stream->pendingAbortRequest(); + + // 4. Assert: state is "writable" or state is "erroring". + ASSERT(state == JSWritableStream::State::Writable || state == JSWritableStream::State::Erroring); + + // 5. Let wasAlreadyErroring be false. + bool wasAlreadyErroring = false; + + // 6. If state is "erroring", + if (state == JSWritableStream::State::Erroring) { + // a. Set wasAlreadyErroring to true. + wasAlreadyErroring = true; + // b. Set reason to undefined. + reason = jsUndefined(); + } + + // 7. Let promise be a new promise. + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + + // 8. Set stream.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason, + // [[wasAlreadyErroring]]: wasAlreadyErroring}. + stream->setPendingAbortRequest(promise, reason, wasAlreadyErroring); + + // 9. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). + if (!wasAlreadyErroring) { + WritableStreamStartErroring(stream, reason); + } + + // 10. If stream.[[state]] is "errored", perform ! WritableStreamFinishErroring(stream). + if (stream->state() == JSWritableStream::State::Errored) { + WritableStreamFinishErroring(stream); + } + + // 11. Return promise. + return promise; +} + +} + +JSValue JSWritableStream::abort(JSGlobalObject* globalObject, JSValue reason) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception. + if (isLocked()) + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot abort a locked WritableStream"_s)); + + // 2. Return ! WritableStreamAbort(this, reason). + return Operations::WritableStreamAbort(globalObject, this, reason); +} + +JSValue JSWritableStream::close(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Cannot close locked stream + if (isLocked()) + return throwVMTypeError(globalObject, scope, "Cannot close a locked WritableStream"_s); + + // Cannot close unless in writable state + if (m_state != State::Writable) + return throwVMTypeError(globalObject, scope, "Cannot close stream in non-writable state"_s); + + // Cannot close if already closing + if (m_closeRequest || m_inFlightCloseRequest) + return throwVMTypeError(globalObject, scope, "Cannot close an already closing stream"_s); + + // Create close promise + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + m_closeRequest.set(vm, this, promise); + + // If we have in-flight write request, wait for it to finish + if (m_inFlightWriteRequest) { + RELEASE_AND_RETURN(scope, promise); + } + + // Note: The controller just queues up the close operation + m_controller->close(globalObject); + + m_inFlightCloseRequest.set(vm, this, m_closeRequest.get()); + m_closeRequest.clear(); + + RELEASE_AND_RETURN(scope, m_inFlightCloseRequest.get()); +} + +} diff --git a/src/bun.js/bindings/BunWritableStream.h b/src/bun.js/bindings/BunWritableStream.h new file mode 100644 index 0000000000..b00fee376e --- /dev/null +++ b/src/bun.js/bindings/BunWritableStream.h @@ -0,0 +1,144 @@ +#pragma once + +#include "root.h" + +#include +#include +#include "JavaScriptCore/JSCast.h" +#include +#include +#include +#include +#include "DOMIsoSubspaces.h" +#include "BunClientData.h" + +namespace Bun { + +class JSWritableStreamDefaultController; +class JSWritableStreamDefaultWriter; +class UnderlyingSink; + +using namespace JSC; + +// Main WritableStream object implementation +class JSWritableStream final : public JSDestructibleObject { +public: + using Base = JSDestructibleObject; + static constexpr bool needsDestruction = true; + + static JSWritableStream* create(VM&, JSGlobalObject*, Structure*); + + DECLARE_INFO; + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + if constexpr (mode == SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForWritableStream.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWritableStream = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForWritableStream.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForWritableStream = std::forward(space); }); + } + + static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); + + // Internal state tracking + enum class State : uint8_t { + Writable, + Erroring, + Errored, + Closing, + Closed + }; + + JSWritableStreamDefaultController* controller() { return m_controller.get(); } + JSPromise* closeRequest() { return m_closeRequest.get(); } + JSPromise* inFlightWriteRequest() { return m_inFlightWriteRequest.get(); } + JSValue storedError() const { return m_storedError.get(); } + State state() const { return m_state; } + bool backpressure() const { return m_backpressure; } + JSWritableStreamDefaultWriter* writer() { return m_writer.get(); } + + // Public C++ API + JSValue error(JSGlobalObject*, JSValue error); + bool isLocked() const; + JSValue abort(JSGlobalObject*, JSValue reason); + JSValue close(JSGlobalObject*); + void setController(JSC::VM& vm, JSWritableStreamDefaultController* controller) + { + m_controller.set(vm, this, controller); + } + void setWriter(JSC::VM& vm, JSWritableStreamDefaultWriter* writer) + { + m_writer.set(vm, this, writer); + } + + static JSObject* createPrototype(VM&, JSGlobalObject*); + static JSObject* createConstructor(VM&, JSGlobalObject*, JSValue); + + DECLARE_VISIT_CHILDREN; + + void setPendingAbortRequest(JSC::VM& vm, JSPromise* promise, JSValue reason, bool wasAlreadyErroring) + { + m_pendingAbortRequestPromise.set(vm, this, promise); + m_pendingAbortRequestReason.set(vm, this, reason); + m_wasAlreadyErroring = wasAlreadyErroring; + } + + JSPromise* pendingAbortRequestPromise() { return m_pendingAbortRequestPromise.get(); } + JSValue pendingAbortRequestReason() { return m_pendingAbortRequestReason.get(); } + bool wasAlreadyErroring() { return m_wasAlreadyErroring; } + + void clearPendingAbortRequest() + { + m_pendingAbortRequestPromise.clear(); + m_pendingAbortRequestReason.clear(); + m_wasAlreadyErroring = false; + } + + void setStoredError(JSC::VM& vm, JSValue error) + { + m_storedError.set(vm, this, error); + } + + void clearStoredError() + { + m_storedError.clear(); + } + + void setState(State state) + { + m_state = state; + } + + void setBackpressure(bool backpressure) + { + m_backpressure = backpressure; + } + + bool hasOperationMarkedInFlight() const { return m_inFlightWriteRequest || m_inFlightCloseRequest; } + +private: + JSWritableStream(VM&, Structure*); + void finishCreation(VM&); + static void destroy(JSCell*); + + // Internal state tracking + State m_state { State::Writable }; + bool m_backpressure { false }; + + WriteBarrier m_controller; + WriteBarrier m_writer; + WriteBarrier m_closeRequest; + WriteBarrier m_inFlightWriteRequest; + WriteBarrier m_inFlightCloseRequest; + WriteBarrier m_pendingAbortRequestPromise; + WriteBarrier m_pendingAbortRequestReason; + WriteBarrier m_storedError; + + bool m_wasAlreadyErroring { false }; +}; + +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp new file mode 100644 index 0000000000..8ab85c670f --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -0,0 +1,264 @@ +#include "root.h" + +#include +#include +#include +#include "JSAbortController.h" + +#include "BunWritableStreamDefaultController.h" +#include "BunWritableStream.h" +#include "JSAbortSignal.h" +#include "IDLTypes.h" +#include "JSDOMBinding.h" + +namespace Bun { + +class JSWritableStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSWritableStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSWritableStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultControllerPrototype, 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: + JSWritableStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSWritableStreamDefaultControllerConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSWritableStreamDefaultControllerConstructor* create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure, + JSWritableStreamDefaultControllerPrototype* prototype); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForStreamConstructor.get(); }, + [](auto& spaces, auto&& space) { + spaces.m_clientSubspaceForStreamConstructor = std::forward(space); + }, + [](auto& spaces) { return spaces.m_subspaceForStreamConstructor.get(); }, + [](auto& spaces, auto&& space) { + spaces.m_subspaceForStreamConstructor = std::forward(space); + }); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, + JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSWritableStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultControllerPrototype*); +}; + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerErrorFunction, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) { + scope.throwException(globalObject, createTypeError(globalObject, "WritableStreamDefaultController.prototype.error called on non-WritableStreamDefaultController"_s)); + return {}; + } + + return JSValue::encode(controller->error(callFrame->argument(0))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetSignal, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + scope.throwException(lexicalGlobalObject, createTypeError(lexicalGlobalObject, "WritableStreamDefaultController.prototype.signal called on non-WritableStreamDefaultController"_s)); + return {}; + } + + return JSValue::encode(thisObject->abortSignal()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetDesiredSize, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + scope.throwException(lexicalGlobalObject, createTypeError(lexicalGlobalObject, "WritableStreamDefaultController.prototype.desiredSize called on non-WritableStreamDefaultController"_s)); + return {}; + } + + switch (thisObject->stream()->state()) { + case JSWritableStream::State::Errored: + return JSValue::encode(jsNull()); + case JSWritableStream::State::Closed: + return JSValue::encode(jsNumber(0)); + default: + return JSValue::encode(jsNumber(thisObject->getDesiredSize())); + } +} + +static const HashTableValue JSWritableStreamDefaultControllerPrototypeTableValues[] = { + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } }, + { "signal"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultControllerGetSignal, 0 } }, +}; + +void JSWritableStreamDefaultControllerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSWritableStreamDefaultController::info(), JSWritableStreamDefaultControllerPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +const JSC::ClassInfo JSWritableStreamDefaultControllerPrototype::s_info = { + "WritableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultControllerPrototype) +}; + +// JSWritableStreamDefaultController.cpp + +JSWritableStreamDefaultController* JSWritableStreamDefaultController::create( + JSC::VM& vm, + JSC::Structure* structure, + JSWritableStream* stream, + double highWaterMark, + JSC::JSObject* underlyingSinkObj) +{ + JSWritableStreamDefaultController* controller = new ( + NotNull, JSC::allocateCell(vm)) + JSWritableStreamDefaultController(vm, structure); + + controller->finishCreation(vm); + return controller; +} + +void JSWritableStreamDefaultController::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + m_queue.set(vm, JSC::constructEmptyArray(vm, nullptr)); + m_abortController.set(vm, WebCore::JSAbortController::create(vm, nullptr, nullptr)); +} + +JSC::JSValue JSWritableStreamDefaultController::abortSignal() const +{ + auto& vm = this->globalObject()->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + return WebCore::toJS>(this->globalObject(), defaultGlobalObject(this->globalObject()), throwScope, m_abortController->wrapped().signal()); +} + +JSC::JSValue JSWritableStreamDefaultController::error(JSC::JSValue reason) +{ + auto* globalObject = JSC::jsCast(m_stream->globalObject()); + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_stream->state() != JSWritableStream::State::Writable) + return JSC::jsUndefined(); + + performWritableStreamDefaultControllerError(this, reason); + + RELEASE_AND_RETURN(scope, JSC::jsUndefined()); +} + +bool JSWritableStreamDefaultController::shouldCallWrite() const +{ + if (!m_started) + return false; + + if (m_writing) + return false; + + if (m_inFlightWriteRequest) + return false; + + if (m_stream->state() != JSWritableStream::State::Writable) + return false; + + return true; +} + +double JSWritableStreamDefaultController::getDesiredSize() const +{ + return m_strategyHWM - m_queueTotalSize; +} + +template +void JSWritableStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSWritableStreamDefaultController* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + thisObject->visitAdditionalChildren(visitor); +} + +template +void JSWritableStreamDefaultController::visitAdditionalChildren(Visitor& visitor) +{ + visitor.append(m_stream); + visitor.append(m_abortAlgorithm); + visitor.append(m_closeAlgorithm); + visitor.append(m_writeAlgorithm); + visitor.append(m_strategySizeAlgorithm); + visitor.append(m_queue); + visitor.append(m_abortController); +} + +DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultController); +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultController); + +const JSC::ClassInfo JSWritableStreamDefaultController::s_info = { + "WritableStreamDefaultController"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultController) +}; +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h new file mode 100644 index 0000000000..bcd8039b8f --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -0,0 +1,94 @@ +#include "root.h" + +#include +#include +#include + +namespace WebCore { +class JSAbortController; +} + +namespace Bun { + +class JSWritableStream; + +class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr bool needsDestruction = true; + + static JSWritableStreamDefaultController* create( + JSC::VM& vm, + JSC::Structure* structure, + JSWritableStream* stream, + double highWaterMark, + JSC::JSObject* underlyingSinkObj); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + 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()); + } + + // JavaScript-facing methods + JSC::JSValue error(JSC::JSValue reason); + + // C++-facing methods + bool shouldCallWrite() const; + double getDesiredSize() const; + + // For garbage collection + DECLARE_VISIT_CHILDREN; + + JSC::JSValue abortSignal() const; + + template void visitAdditionalChildren(Visitor&); + + JSWritableStream* stream() const { return m_stream.get(); } + JSC::JSPromise* abortAlgorithm() const { return m_abortAlgorithm.get(); } + JSC::JSPromise* closeAlgorithm() const { return m_closeAlgorithm.get(); } + JSC::JSPromise* writeAlgorithm() const { return m_writeAlgorithm.get(); } + + void setStream(JSC::VM& vm, JSWritableStream* stream) { m_stream.set(vm, this, stream); } + void setAbortAlgorithm(JSC::VM& vm, JSC::JSPromise* abortAlgorithm) { m_abortAlgorithm.set(vm, this, abortAlgorithm); } + void setCloseAlgorithm(JSC::VM& vm, JSC::JSPromise* closeAlgorithm) { m_closeAlgorithm.set(vm, this, closeAlgorithm); } + void setWriteAlgorithm(JSC::VM& vm, JSC::JSPromise* writeAlgorithm) { m_writeAlgorithm.set(vm, this, writeAlgorithm); } + + void clearQueue() { m_queue.clear(); } + + ~JSWritableStreamDefaultController(); + static void destroy(JSC::JSCell* cell) + { + static_cast(cell)->JSWritableStreamDefaultController::~JSWritableStreamDefaultController(); + } + +private: + JSWritableStreamDefaultController(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); + + // Internal slots per spec + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_abortAlgorithm; + JSC::WriteBarrier m_closeAlgorithm; + JSC::WriteBarrier m_writeAlgorithm; + + double m_strategyHWM { 1.0 }; + JSC::WriteBarrier m_strategySizeAlgorithm; + JSC::WriteBarrier m_queue; + double m_queueTotalSize { 0.0 }; + bool m_started { false }; + bool m_writing { false }; + bool m_inFlightWriteRequest { false }; + bool m_closeRequested { false }; + JSC::WriteBarrier m_abortController; +}; + +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp new file mode 100644 index 0000000000..e2f74f9cfe --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -0,0 +1,427 @@ +#include "root.h" + +#include "BunWritableStreamDefaultWriter.h" +#include "BunWritableStream.h" +#include "JSDOMWrapper.h" +#include + +namespace Bun { + +using namespace JSC; + +class JSWritableStreamDefaultWriter; +class JSWritableStreamDefaultWriterPrototype; + +class JSWritableStreamDefaultWriterConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSWritableStreamDefaultWriterConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward(space); }); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + +private: + JSWritableStreamDefaultWriterConstructor(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultWriterPrototype*); +}; + +class JSWritableStreamDefaultWriterPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSWritableStreamDefaultWriterPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSWritableStreamDefaultWriterPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultWriterPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultWriterPrototype, 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: + JSWritableStreamDefaultWriterPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock); + +// Property attributes for standard WritableStreamDefaultWriter prototype properties +static const unsigned ProtoAccessorDontDelete = PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor; +static const unsigned ProtoFunctionDontEnum = PropertyAttribute::DontEnum | PropertyAttribute::Function; + +// Table of prototype properties and methods +static const HashTableValue JSWritableStreamDefaultWriterPrototypeTableValues[] = { + { "closed"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterClosedGetter, nullptr } }, + { "ready"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterReadyGetter, nullptr } }, + { "desiredSize"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterDesiredSizeGetter, nullptr } }, + { "write"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterWrite, 1 } }, + { "abort"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterAbort, 1 } }, + { "close"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterClose, 0 } }, + { "releaseLock"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterReleaseLock, 0 } }, +}; + +const ClassInfo JSWritableStreamDefaultWriterPrototype::s_info = { + "WritableStreamDefaultWriter"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterPrototype) +}; + +void JSWritableStreamDefaultWriterPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, info(), JSWritableStreamDefaultWriterPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// Getter implementations +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(writer->closed()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(writer->ready()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(jsNumber(writer->desiredSize())); +} + +// Additional JS method implementation +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + writer->release(); + return JSValue::encode(jsUndefined()); +} + +const ClassInfo JSWritableStreamDefaultWriterConstructor::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterConstructor) +}; + +JSWritableStreamDefaultWriterConstructor::JSWritableStreamDefaultWriterConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSWritableStreamDefaultWriterConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamDefaultWriterPrototype* prototype) +{ + Base::finishCreation(vm, 1, "WritableStreamDefaultWriter"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSWritableStreamDefaultWriterConstructor* JSWritableStreamDefaultWriterConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype) +{ + JSWritableStreamDefaultWriterConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamDefaultWriterConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +// This is called when constructing a new writer with new WritableStreamDefaultWriter(stream) +EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!callFrame->argumentCount()) { + throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor requires a WritableStream argument"_s); + return encodedJSValue(); + } + + JSValue streamValue = callFrame->argument(0); + JSWritableStream* stream = jsDynamicCast(streamValue); + if (!stream) { + throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor argument must be a WritableStream"_s); + return encodedJSValue(); + } + + // Check if stream is locked + if (stream->locked()) { + throwTypeError(lexicalGlobalObject, scope, "Cannot construct a WritableStreamDefaultWriter for a locked WritableStream"_s); + return encodedJSValue(); + } + + Structure* structure = globalObject->WritableStreamDefaultWriterStructure(); + JSWritableStreamDefaultWriter* writer = JSWritableStreamDefaultWriter::create(vm, structure, stream); + return JSValue::encode(writer); +} + +// This handles direct calls to WritableStreamDefaultWriter as a function, which should throw an error +EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(globalObject, scope, "WritableStreamDefaultWriter constructor cannot be called as a function"_s); +} + +const ClassInfo JSWritableStreamDefaultWriter::s_info = { + "WritableStreamDefaultWriter"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriter) +}; + +JSWritableStreamDefaultWriter::JSWritableStreamDefaultWriter(VM& vm, Structure* structure, JSWritableStream* stream) + : Base(vm, structure) + , m_stream(vm, this, stream) + , m_closedPromise(vm, this, JSPromise::create(vm, globalObject->promiseStructure())) + , m_readyPromise(vm, this, JSPromise::create(vm, globalObject->promiseStructure())) +{ +} + +JSWritableStreamDefaultWriter* JSWritableStreamDefaultWriter::create(VM& vm, Structure* structure, JSWritableStream* stream) +{ + JSWritableStreamDefaultWriter* writer = new ( + NotNull, + allocateCell(vm)) JSWritableStreamDefaultWriter(vm, structure, stream); + + writer->finishCreation(vm); + return writer; +} + +void JSWritableStreamDefaultWriter::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +void JSWritableStreamDefaultWriter::destroy(JSCell* cell) +{ + static_cast(cell)->JSWritableStreamDefaultWriter::~JSWritableStreamDefaultWriter(); +} + +template +void JSWritableStreamDefaultWriter::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* writer = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(writer, info()); + + Base::visitChildren(writer, visitor); + writer->visitAdditionalChildren(visitor); +} + +DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultWriter); + +template +void JSWritableStreamDefaultWriter::visitAdditionalChildren(Visitor& visitor) +{ + visitor.append(m_stream); + visitor.append(m_closedPromise); + visitor.append(m_readyPromise); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultWriter); + +// JS Interface Methods + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue chunk = callFrame->argument(0); + + JSValue error; + if (!writer->write(globalObject, chunk, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue error; + if (!writer->close(globalObject, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue reason = callFrame->argument(0); + + JSValue error; + if (!writer->abort(globalObject, reason, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +// Non-JS Methods for C++ Use + +bool JSWritableStreamDefaultWriter::write(JSGlobalObject* globalObject, JSValue chunk, JSValue* error) +{ + VM& vm = globalObject->vm(); + + if (!m_stream) { + if (error) + *error = createTypeError(globalObject, "Writer has no associated stream"_s); + return false; + } + + return m_stream->write(globalObject, chunk, error); +} + +bool JSWritableStreamDefaultWriter::close(JSGlobalObject* globalObject, JSValue* error) +{ + VM& vm = globalObject->vm(); + + if (!m_stream) { + if (error) + *error = createTypeError(globalObject, "Writer has no associated stream"_s); + return false; + } + + return m_stream->close(globalObject, error); +} + +bool JSWritableStreamDefaultWriter::abort(JSGlobalObject* globalObject, JSValue reason, JSValue* error) +{ + VM& vm = globalObject->vm(); + + if (!m_stream) { + if (error) + *error = createTypeError(globalObject, "Writer has no associated stream"_s); + return false; + } + + return m_stream->abort(globalObject, reason, error); +} + +void JSWritableStreamDefaultWriter::release() +{ + m_stream.clear(); + m_closedPromise->reject(vm(), jsUndefined()); + m_readyPromise->reject(vm(), jsUndefined()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h new file mode 100644 index 0000000000..a619397a4e --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -0,0 +1,57 @@ + +#pragma once + +#include "root.h" + +#include +#include +#include + +namespace Bun { + +class JSWritableStream; + +class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr bool needsDestruction = true; + + static JSWritableStreamDefaultWriter* create(JSC::VM&, JSC::Structure*, JSWritableStream*); + static JSWritableStreamDefaultWriter* createForSubclass(JSC::VM&, JSC::Structure*, JSWritableStream*); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + 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()); + } + + DECLARE_INFO; + + // JavaScript-visible properties + JSC::JSPromise* closed() { return m_closedPromise.get(); } + JSC::JSPromise* ready() { return m_readyPromise.get(); } + double desiredSize(); + + // Internal APIs for C++ use + JSWritableStream* stream() { return m_stream.get(); } + void release(); // For releaseLock() + bool write(JSC::JSGlobalObject*, JSC::JSValue chunk, JSC::JSValue* error = nullptr); + bool abort(JSC::JSGlobalObject*, JSC::JSValue reason = JSC::JSValue(), JSC::JSValue* error = nullptr); + bool close(JSC::JSGlobalObject*, JSC::JSValue* error = nullptr); + + void visitAdditionalChildren(JSC::SlotVisitor&); + +protected: + JSWritableStreamDefaultWriter(JSC::VM&, JSC::Structure*, JSWritableStream*); + void finishCreation(JSC::VM&); + static void destroy(JSC::JSCell*); + +private: + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_closedPromise; + JSC::WriteBarrier m_readyPromise; +}; + +} // namespace Bun