This commit is contained in:
Jarred Sumner
2024-12-18 04:59:07 -08:00
parent fbe1895f58
commit 64df1de435
14 changed files with 2288 additions and 30 deletions

View File

@@ -7,10 +7,25 @@
#include "BunReadableStream.h"
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/Completion.h>
#include "BunReadableStreamDefaultReader.h"
#include "DOMIsoSubspaces.h"
#include "BunClientData.h"
namespace Bun {
using namespace JSC;
template<typename CellType, JSC::SubspaceAccess mode>
JSC::GCClient::IsoSubspace* JSReadableStreamDefaultController::subspaceFor(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSReadableStreamDefaultController, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSReadableStreamDefaultController.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSReadableStreamDefaultController = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSReadableStreamDefaultController.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSReadableStreamDefaultController = std::forward<decltype(space)>(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;

View File

@@ -19,12 +19,7 @@ public:
static constexpr unsigned StructureFlags = Base::StructureFlags;
template<typename CellType, JSC::SubspaceAccess mode>
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);

View File

@@ -1,3 +1,4 @@
#include "ErrorCode+List.h"
#include "root.h"
#include <JavaScriptCore/JSObject.h>
@@ -13,7 +14,7 @@
#include "BunReadableStreamDefaultController.h"
#include <JavaScriptCore/Completion.h>
#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<JSReadableStreamDefaultReaderPrototype>(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<JSReadableStreamDefaultReader>(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<typename Visitor>
void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* reader = jsCast<JSReadableStreamDefaultReader*>(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<JSReadableStreamDefaultReader*>(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));

View File

@@ -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();

View File

@@ -0,0 +1,361 @@
#include "root.h"
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSPromise.h>
#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<JSTransformStreamPrototype>(vm)) JSTransformStreamPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSTransformStreamConstructor>(vm)) JSTransformStreamConstructor(vm, structure);
constructor->finishCreation(vm, globalObject, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSTransformStream*>(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<JSTransformStream*>(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<JSTransformStreamPrototype*>(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<unsigned>(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly),
NoIntrinsic,
{ HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } },
{ "writable"_s,
static_cast<unsigned>(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly),
NoIntrinsic,
{ HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } },
{ "constructor"_s,
static_cast<unsigned>(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<typename Visitor>
void JSTransformStream::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSTransformStream*>(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<JSTransformStream*>(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<JSTransformStream>(vm)) JSTransformStream(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
// Prototype implementation (JSTransformStreamPrototype.cpp)
static const HashTableValue JSTransformStreamPrototypeTableValues[] = {
{ "readable"_s,
static_cast<unsigned>(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly),
NoIntrinsic,
{ HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } },
{ "writable"_s,
static_cast<unsigned>(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

View File

@@ -0,0 +1,63 @@
#include "root.h"
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/JSArray.h>
namespace Bun {
class JSTransformStream final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
// For garbage collection
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
template<typename, JSC::SubspaceAccess mode>
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<JSC::JSObject> m_readable;
JSC::WriteBarrier<JSC::JSObject> m_writable;
JSC::WriteBarrier<JSTransformStreamDefaultController> m_controller;
// State flags
bool m_backpressure { false };
JSC::WriteBarrier<JSC::JSPromise> m_backpressureChangePromise;
};
}

View File

@@ -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<JSTransformStreamDefaultControllerPrototype>(vm))
JSTransformStreamDefaultControllerPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSTransformStreamDefaultControllerConstructor>(vm))
JSTransformStreamDefaultControllerConstructor(vm, structure);
constructor->finishCreation(vm, globalObject, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerEnqueue, 1 } },
{ "error"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerError, 1 } },
{ "terminate"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerTerminate, 0 } },
{ "desiredSize"_s, static_cast<unsigned>(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<JSTransformStreamDefaultController>(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<typename Visitor>
void JSTransformStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSTransformStreamDefaultController*>(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<JSTransformStreamDefaultController*>(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<JSTransformStreamDefaultController*>(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<JSTransformStreamDefaultController*>(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<JSTransformStreamDefaultController*>(callFrame->thisValue());
if (UNLIKELY(!controller))
return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s);
controller->terminate(globalObject);
return JSValue::encode(jsUndefined());
}
} // namespace Bun

View File

@@ -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<typename CellType, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl<JSTransformStreamDefaultController, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForTransformStreamController.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTransformStreamController = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForTransformStreamController.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForTransformStreamController = std::forward<decltype(space)>(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<JSTransformStream> m_stream;
JSC::WriteBarrier<JSC::JSPromise> m_flushPromise;
JSC::WriteBarrier<JSC::Unknown> m_transformAlgorithm;
JSC::WriteBarrier<JSC::Unknown> 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

View File

@@ -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<JSWritableStream*>(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<JSWritableStream*>(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<JSWritableStream*>(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<JSWritableStream*>(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<unsigned>(PropertyAttribute::Function),
NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_abort, 1 } },
{ "close"_s,
static_cast<unsigned>(PropertyAttribute::Function),
NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_close, 0 } },
{ "getWriter"_s,
static_cast<unsigned>(PropertyAttribute::Function),
NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_getWriter, 0 } },
{ "locked"_s,
static_cast<unsigned>(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<typename CellType, SubspaceAccess>
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<typename CellType, SubspaceAccess mode>
static GCClient::IsoSubspace* subspaceFor(VM& vm)
{
if constexpr (mode == SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSWritableStreamConstructor,
WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForConstructor.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward<decltype(space)>(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<JSWritableStreamPrototype>(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<JSWritableStreamConstructor>(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<JSWritableStream>(vm))
JSWritableStream(vm, structure);
stream->finishCreation(vm);
return stream;
}
void JSWritableStream::destroy(JSCell* cell)
{
static_cast<JSWritableStream*>(cell)->JSWritableStream::~JSWritableStream();
}
template<typename Visitor>
void JSWritableStream::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSWritableStream*>(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<JSPromise*>(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<JSPromise*>(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<JSPromise*>(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());
}
}

View File

@@ -0,0 +1,144 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/JSObjectInlines.h>
#include "JavaScriptCore/JSCast.h"
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/WriteBarrier.h>
#include <JavaScriptCore/Completion.h>
#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<typename CellType, SubspaceAccess mode>
static GCClient::IsoSubspace* subspaceFor(VM& vm)
{
if constexpr (mode == SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSWritableStream, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForWritableStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWritableStream = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForWritableStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForWritableStream = std::forward<decltype(space)>(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<JSWritableStreamDefaultController> m_controller;
WriteBarrier<JSWritableStreamDefaultWriter> m_writer;
WriteBarrier<JSPromise> m_closeRequest;
WriteBarrier<JSPromise> m_inFlightWriteRequest;
WriteBarrier<JSPromise> m_inFlightCloseRequest;
WriteBarrier<JSPromise> m_pendingAbortRequestPromise;
WriteBarrier<Unknown> m_pendingAbortRequestReason;
WriteBarrier<Unknown> m_storedError;
bool m_wasAlreadyErroring { false };
};
}

View File

@@ -0,0 +1,264 @@
#include "root.h"
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSPromise.h>
#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<JSWritableStreamDefaultControllerPrototype>(vm)) JSWritableStreamDefaultControllerPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSWritableStreamDefaultControllerConstructor,
WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForStreamConstructor.get(); },
[](auto& spaces, auto&& space) {
spaces.m_clientSubspaceForStreamConstructor = std::forward<decltype(space)>(space);
},
[](auto& spaces) { return spaces.m_subspaceForStreamConstructor.get(); },
[](auto& spaces, auto&& space) {
spaces.m_subspaceForStreamConstructor = std::forward<decltype(space)>(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<JSWritableStreamDefaultController*>(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<JSWritableStreamDefaultController*>(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<JSWritableStreamDefaultController*>(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<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } },
{ "signal"_s, static_cast<unsigned>(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<JSWritableStreamDefaultController>(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<WebCore::IDLInterface<WebCore::AbortSignal>>(this->globalObject(), defaultGlobalObject(this->globalObject()), throwScope, m_abortController->wrapped().signal());
}
JSC::JSValue JSWritableStreamDefaultController::error(JSC::JSValue reason)
{
auto* globalObject = JSC::jsCast<JSC::JSGlobalObject*>(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<typename Visitor>
void JSWritableStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSWritableStreamDefaultController* thisObject = jsCast<JSWritableStreamDefaultController*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
thisObject->visitAdditionalChildren(visitor);
}
template<typename Visitor>
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)
};
}

View File

@@ -0,0 +1,94 @@
#include "root.h"
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/JSArray.h>
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<typename, JSC::SubspaceAccess mode>
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<typename Visitor> 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<JSWritableStreamDefaultController*>(cell)->JSWritableStreamDefaultController::~JSWritableStreamDefaultController();
}
private:
JSWritableStreamDefaultController(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&);
// Internal slots per spec
JSC::WriteBarrier<JSWritableStream> m_stream;
JSC::WriteBarrier<JSC::JSPromise> m_abortAlgorithm;
JSC::WriteBarrier<JSC::JSPromise> m_closeAlgorithm;
JSC::WriteBarrier<JSC::JSPromise> m_writeAlgorithm;
double m_strategyHWM { 1.0 };
JSC::WriteBarrier<JSC::JSObject> m_strategySizeAlgorithm;
JSC::WriteBarrier<JSC::JSObject> 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<WebCore::JSAbortController> m_abortController;
};
}

View File

@@ -0,0 +1,427 @@
#include "root.h"
#include "BunWritableStreamDefaultWriter.h"
#include "BunWritableStream.h"
#include "JSDOMWrapper.h"
#include <wtf/NeverDestroyed.h>
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<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSWritableStreamDefaultWriterConstructor, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward<decltype(space)>(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<JSWritableStreamDefaultWriterPrototype>(vm)) JSWritableStreamDefaultWriterPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSWritableStreamDefaultWriter*>(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<JSWritableStreamDefaultWriter*>(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<JSWritableStreamDefaultWriter*>(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<JSWritableStreamDefaultWriter*>(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<JSWritableStreamDefaultWriterConstructor>(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<JSWritableStream*>(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<JSWritableStreamDefaultWriter>(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<JSWritableStreamDefaultWriter*>(cell)->JSWritableStreamDefaultWriter::~JSWritableStreamDefaultWriter();
}
template<typename Visitor>
void JSWritableStreamDefaultWriter::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* writer = jsCast<JSWritableStreamDefaultWriter*>(cell);
ASSERT_GC_OBJECT_INHERITS(writer, info());
Base::visitChildren(writer, visitor);
writer->visitAdditionalChildren(visitor);
}
DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultWriter);
template<typename Visitor>
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<JSWritableStreamDefaultWriter*>(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<JSWritableStreamDefaultWriter*>(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<JSWritableStreamDefaultWriter*>(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

View File

@@ -0,0 +1,57 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/JSArray.h>
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<typename CellType, JSC::SubspaceAccess>
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<JSWritableStream> m_stream;
JSC::WriteBarrier<JSC::JSPromise> m_closedPromise;
JSC::WriteBarrier<JSC::JSPromise> m_readyPromise;
};
} // namespace Bun