diff --git a/src/browser/tests/streams/readable_stream.html b/src/browser/tests/streams/readable_stream.html
index 3d00d6cf..c82fd985 100644
--- a/src/browser/tests/streams/readable_stream.html
+++ b/src/browser/tests/streams/readable_stream.html
@@ -301,3 +301,74 @@
testing.expectEqual(false, data3.done);
})();
+
+
+
+
+
+
diff --git a/src/browser/webapi/streams/ReadableStreamDefaultController.zig b/src/browser/webapi/streams/ReadableStreamDefaultController.zig
index c2f750bc..18228475 100644
--- a/src/browser/webapi/streams/ReadableStreamDefaultController.zig
+++ b/src/browser/webapi/streams/ReadableStreamDefaultController.zig
@@ -33,11 +33,13 @@ pub const Chunk = union(enum) {
// the order matters, sorry.
uint8array: js.TypedArray(u8),
string: []const u8,
+ js_value: js.Value.Global,
pub fn dupe(self: Chunk, allocator: std.mem.Allocator) !Chunk {
return switch (self) {
.string => |str| .{ .string = try allocator.dupe(u8, str) },
.uint8array => |arr| .{ .uint8array = try arr.dupe(allocator) },
+ .js_value => |val| .{ .js_value = val },
};
}
};
@@ -98,6 +100,40 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
ls.toLocal(resolver).resolve("stream enqueue", result);
}
+/// Enqueue a raw JS value, preserving its type (number, bool, object, etc.).
+/// Used by the JS-facing API; internal Zig callers should use enqueue(Chunk).
+pub fn enqueueValue(self: *ReadableStreamDefaultController, value: js.Value) !void {
+ if (self._stream._state != .readable) {
+ return error.StreamNotReadable;
+ }
+
+ if (self._pending_reads.items.len == 0) {
+ const persisted = try value.persist();
+ try self._queue.append(self._arena, .{ .js_value = persisted });
+ return;
+ }
+
+ const resolver = self._pending_reads.orderedRemove(0);
+ const persisted = try value.persist();
+ const result = ReadableStreamDefaultReader.ReadResult{
+ .done = false,
+ .value = .{ .js_value = persisted },
+ };
+
+ if (comptime IS_DEBUG) {
+ if (self._page.js.local == null) {
+ log.fatal(.bug, "null context scope", .{ .src = "ReadableStreamDefaultController.enqueueValue", .url = self._page.url });
+ std.debug.assert(self._page.js.local != null);
+ }
+ }
+
+ var ls: js.Local.Scope = undefined;
+ self._page.js.localScope(&ls);
+ defer ls.deinit();
+
+ ls.toLocal(resolver).resolve("stream enqueue value", result);
+}
+
pub fn close(self: *ReadableStreamDefaultController) !void {
if (self._stream._state != .readable) {
return error.StreamNotReadable;
@@ -176,7 +212,7 @@ pub const JsApi = struct {
pub var class_id: bridge.ClassId = undefined;
};
- pub const enqueue = bridge.function(ReadableStreamDefaultController.enqueue, .{});
+ pub const enqueue = bridge.function(ReadableStreamDefaultController.enqueueValue, .{});
pub const close = bridge.function(ReadableStreamDefaultController.close, .{});
pub const @"error" = bridge.function(ReadableStreamDefaultController.doError, .{});
pub const desiredSize = bridge.accessor(ReadableStreamDefaultController.getDesiredSize, null, .{});
diff --git a/src/browser/webapi/streams/ReadableStreamDefaultReader.zig b/src/browser/webapi/streams/ReadableStreamDefaultReader.zig
index 053080c4..2d3c5bbe 100644
--- a/src/browser/webapi/streams/ReadableStreamDefaultReader.zig
+++ b/src/browser/webapi/streams/ReadableStreamDefaultReader.zig
@@ -44,11 +44,13 @@ pub const ReadResult = struct {
empty,
string: []const u8,
uint8array: js.TypedArray(u8),
+ js_value: js.Value.Global,
pub fn fromChunk(chunk: ReadableStreamDefaultController.Chunk) Chunk {
return switch (chunk) {
.string => |s| .{ .string = s },
.uint8array => |arr| .{ .uint8array = arr },
+ .js_value => |val| .{ .js_value = val },
};
}
};
diff --git a/src/browser/webapi/streams/TransformStream.zig b/src/browser/webapi/streams/TransformStream.zig
index 6e51515d..c0ca34b6 100644
--- a/src/browser/webapi/streams/TransformStream.zig
+++ b/src/browser/webapi/streams/TransformStream.zig
@@ -178,6 +178,11 @@ pub const TransformStreamDefaultController = struct {
try self._stream._readable._controller.enqueue(chunk);
}
+ /// Enqueue a raw JS value, preserving its type. Used by the JS-facing API.
+ pub fn enqueueValue(self: *TransformStreamDefaultController, value: js.Value) !void {
+ try self._stream._readable._controller.enqueueValue(value);
+ }
+
pub fn doError(self: *TransformStreamDefaultController, reason: []const u8) !void {
try self._stream._readable._controller.doError(reason);
}
@@ -195,7 +200,7 @@ pub const TransformStreamDefaultController = struct {
pub var class_id: bridge.ClassId = undefined;
};
- pub const enqueue = bridge.function(TransformStreamDefaultController.enqueue, .{});
+ pub const enqueue = bridge.function(TransformStreamDefaultController.enqueueValue, .{});
pub const @"error" = bridge.function(TransformStreamDefaultController.doError, .{});
pub const terminate = bridge.function(TransformStreamDefaultController.terminate, .{});
};